feat: 설계 문서 추가

This commit is contained in:
DongHyeonka
2026-08-13 21:09:52 +09:00
parent 0a6dd0e419
commit 3b5aee50e3
33 changed files with 45861 additions and 0 deletions
@@ -0,0 +1,5 @@
20a39913d84b179fb407811af4ee23bbcad8eebadff893add530cab97fa90878 ./README.md
b84784d8321ea826af7c9788693008fccd4f2bf437816d2d47a515caac098f04 ./VALIDATION.md
176b0c86e11e6d4d8440285dd723e5555616ff9a69a7a9419250c2ce2b67f565 ./docs/superpowers/plans/2026-08-10-notification-platform-implementation-plan.md
fe7b57c6573ef7305033fe9402333d3c10f7a03ccf421dce5f353e2616ac8677 ./docs/superpowers/specs/2026-08-10-notification-platform-design.md
2109766403adf001bb1dcbfe8679d362557f71433532336ff72b90976f3cfe10 ./validate_notification_docs.py
@@ -0,0 +1,60 @@
# Notification Delivery Platform Superpowers Package
이 패키지는 Backend Skeleton의 `notification` 기술 모듈을 구현하기 위한 설계서와 TDD 구현 계획서다.
## 파일
```text
docs/superpowers/specs/2026-08-10-notification-platform-design.md
docs/superpowers/plans/2026-08-10-notification-platform-implementation-plan.md
VALIDATION.md
validate_notification_docs.py
MANIFEST.sha256
```
## 핵심 계약
```text
NotificationRequest
→ RecipientDelivery
→ DeliveryAttempt
→ ProviderEvent append-only ledger
→ channel-specific projection
→ SubmissionOutcome / DeliveryOutcome / EvidenceLevel
```
- `submit()` 성공은 durable acceptance만 뜻한다.
- Provider acceptance와 delivery/read evidence를 분리한다.
- 결과를 확정할 수 없으면 `AMBIGUOUS`로 보존한다.
- ambiguous attempt의 자동 retry와 cross-channel fallback은 기본 차단한다.
- FCM은 FID를 primary target으로, registration token을 legacy compatibility로 둔다.
- Callback은 signature 검증 후 원장에 append하고 중복·역순·누락을 처리한다.
- Contact Point는 AES-256-GCM 암호화와 HMAC-SHA-256 equality fingerprint를 사용한다.
## 구현 기준 가정
실제 Backend Skeleton 저장소가 입력되지 않았기 때문에 다음 가정을 문서에 명시했다.
```text
Java 21
Gradle Kotlin DSL
Spring Framework 6.2 common line + Spring 7.0 compatibility job
PostgreSQL 16
JPA + Flyway
root package: io.backend.skeleton.notification
module root: modules/notification
```
실제 저장소에서는 dependency catalog와 package/file path를 기존 규칙에 맞게 매핑하되 공개 계약과 신뢰성 의미론은 유지한다.
## 실행
1. 설계서를 검토한다.
2. 계획서 Task 1부터 순서대로 실행한다.
3. 각 Task에서 failing test → red 확인 → 최소 구현 → green 확인 → commit 순서를 지킨다.
4. Task마다 specification review와 code-quality review를 분리한다.
5. 마지막 release gate에서 contract, chaos, security, performance, compatibility job을 모두 실행한다.
## 검증 범위
`VALIDATION.md`는 문서 구조, Task 연속성, 파일 경로 중복, 미확정 표현, 핵심 계약, ZIP CRC를 검증한 결과다. 실제 Gradle compile과 Provider integration은 Backend Skeleton 저장소에서 계획서의 명령을 실행해야 한다.
@@ -0,0 +1,590 @@
# Notification Superpowers 문서 정적 검증
- 결과: **PASS**
- 실행 검사: **567개**
- 통과: **567개**
- 실패: **0개**
- 설계서: **4,770행**
- 구현 계획서: **5,325행**
- 구현 Task: **50개**
- Create 경로: **322개**
- 설계 SHA-256: `fe7b57c6573ef7305033fe9402333d3c10f7a03ccf421dce5f353e2616ac8677`
- 계획 SHA-256: `176b0c86e11e6d4d8440285dd723e5555616ff9a69a7a9419250c2ce2b67f565`
## 검사 결과
| 검사 | 결과 | 상세 |
|---|---:|---|
| 설계서 존재 | PASS | /mnt/data/notification-platform-design.md |
| 구현 계획서 존재 | PASS | /mnt/data/notification-platform-implementation-plan.md |
| 설계서 최소 상세도 | PASS | 4,770 lines |
| 계획서 최소 상세도 | PASS | 5,325 lines |
| 설계서 코드 fence 균형 | PASS | 482 |
| 계획서 코드 fence 균형 | PASS | 308 |
| 설계 핵심 계약: NotificationRequest | PASS | |
| 설계 핵심 계약: RecipientDelivery | PASS | |
| 설계 핵심 계약: DeliveryAttempt | PASS | |
| 설계 핵심 계약: ProviderEvent | PASS | |
| 설계 핵심 계약: EvidenceLevel | PASS | |
| 설계 핵심 계약: SubmissionOutcome | PASS | |
| 설계 핵심 계약: DeliveryOutcome | PASS | |
| 설계 핵심 계약: AMBIGUOUS | PASS | |
| 설계 핵심 계약: append-only | PASS | |
| 설계 핵심 계약: FCM_FID | PASS | |
| 설계 핵심 계약: FCM_REGISTRATION_TOKEN_LEGACY | PASS | |
| 설계 핵심 계약: AES-256-GCM | PASS | |
| 설계 핵심 계약: HMAC-SHA-256 | PASS | |
| 설계 핵심 계약: FOR UPDATE SKIP LOCKED | PASS | |
| 설계 핵심 계약: SMTP Adapter | PASS | |
| 설계 핵심 계약: Amazon SES Adapter | PASS | |
| 설계 핵심 계약: Twilio Adapter | PASS | |
| 설계 핵심 계약: FCM Adapter | PASS | |
| 설계 핵심 계약: APNs Adapter | PASS | |
| 설계 핵심 계약: Web Push Adapter | PASS | |
| 설계 핵심 계약: In-App Inbox | PASS | |
| 설계 핵심 계약: Reconciliation | PASS | |
| 설계 핵심 계약: N4 Admin Plane | PASS | |
| 설계 핵심 계약: 비지원 범위 | PASS | |
| 설계 핵심 계약: 완료 정의 | PASS | |
| exactlyOnce 단순 옵션 금지 명시 | PASS | |
| 금지 보장 부재: guaranteedDelivery=true | PASS | |
| 금지 보장 부재: APNs HTTP 200 = DELIVERED | PASS | |
| 금지 보장 부재: FCM send success = DEVICE_DELIVERED | PASS | |
| Task 1~50 연속성 | PASS | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50] |
| Task chunk 수 | PASS | 50 |
| Task 1 필수 항목: **Files:** | PASS | |
| Task 1 필수 항목: **Interfaces:** | PASS | |
| Task 1 필수 항목: **Implementation requirements:** | PASS | |
| Task 1 필수 항목: **Step 1: | PASS | |
| Task 1 필수 항목: **Step 2: | PASS | |
| Task 1 필수 항목: **Step 3: | PASS | |
| Task 1 필수 항목: **Step 4: | PASS | |
| Task 1 필수 항목: **Step 5: | PASS | |
| Task 1 필수 항목: Expected: | PASS | |
| Task 1 필수 항목: git commit -m | PASS | |
| Task 2 필수 항목: **Files:** | PASS | |
| Task 2 필수 항목: **Interfaces:** | PASS | |
| Task 2 필수 항목: **Implementation requirements:** | PASS | |
| Task 2 필수 항목: **Step 1: | PASS | |
| Task 2 필수 항목: **Step 2: | PASS | |
| Task 2 필수 항목: **Step 3: | PASS | |
| Task 2 필수 항목: **Step 4: | PASS | |
| Task 2 필수 항목: **Step 5: | PASS | |
| Task 2 필수 항목: Expected: | PASS | |
| Task 2 필수 항목: git commit -m | PASS | |
| Task 3 필수 항목: **Files:** | PASS | |
| Task 3 필수 항목: **Interfaces:** | PASS | |
| Task 3 필수 항목: **Implementation requirements:** | PASS | |
| Task 3 필수 항목: **Step 1: | PASS | |
| Task 3 필수 항목: **Step 2: | PASS | |
| Task 3 필수 항목: **Step 3: | PASS | |
| Task 3 필수 항목: **Step 4: | PASS | |
| Task 3 필수 항목: **Step 5: | PASS | |
| Task 3 필수 항목: Expected: | PASS | |
| Task 3 필수 항목: git commit -m | PASS | |
| Task 4 필수 항목: **Files:** | PASS | |
| Task 4 필수 항목: **Interfaces:** | PASS | |
| Task 4 필수 항목: **Implementation requirements:** | PASS | |
| Task 4 필수 항목: **Step 1: | PASS | |
| Task 4 필수 항목: **Step 2: | PASS | |
| Task 4 필수 항목: **Step 3: | PASS | |
| Task 4 필수 항목: **Step 4: | PASS | |
| Task 4 필수 항목: **Step 5: | PASS | |
| Task 4 필수 항목: Expected: | PASS | |
| Task 4 필수 항목: git commit -m | PASS | |
| Task 5 필수 항목: **Files:** | PASS | |
| Task 5 필수 항목: **Interfaces:** | PASS | |
| Task 5 필수 항목: **Implementation requirements:** | PASS | |
| Task 5 필수 항목: **Step 1: | PASS | |
| Task 5 필수 항목: **Step 2: | PASS | |
| Task 5 필수 항목: **Step 3: | PASS | |
| Task 5 필수 항목: **Step 4: | PASS | |
| Task 5 필수 항목: **Step 5: | PASS | |
| Task 5 필수 항목: Expected: | PASS | |
| Task 5 필수 항목: git commit -m | PASS | |
| Task 6 필수 항목: **Files:** | PASS | |
| Task 6 필수 항목: **Interfaces:** | PASS | |
| Task 6 필수 항목: **Implementation requirements:** | PASS | |
| Task 6 필수 항목: **Step 1: | PASS | |
| Task 6 필수 항목: **Step 2: | PASS | |
| Task 6 필수 항목: **Step 3: | PASS | |
| Task 6 필수 항목: **Step 4: | PASS | |
| Task 6 필수 항목: **Step 5: | PASS | |
| Task 6 필수 항목: Expected: | PASS | |
| Task 6 필수 항목: git commit -m | PASS | |
| Task 7 필수 항목: **Files:** | PASS | |
| Task 7 필수 항목: **Interfaces:** | PASS | |
| Task 7 필수 항목: **Implementation requirements:** | PASS | |
| Task 7 필수 항목: **Step 1: | PASS | |
| Task 7 필수 항목: **Step 2: | PASS | |
| Task 7 필수 항목: **Step 3: | PASS | |
| Task 7 필수 항목: **Step 4: | PASS | |
| Task 7 필수 항목: **Step 5: | PASS | |
| Task 7 필수 항목: Expected: | PASS | |
| Task 7 필수 항목: git commit -m | PASS | |
| Task 8 필수 항목: **Files:** | PASS | |
| Task 8 필수 항목: **Interfaces:** | PASS | |
| Task 8 필수 항목: **Implementation requirements:** | PASS | |
| Task 8 필수 항목: **Step 1: | PASS | |
| Task 8 필수 항목: **Step 2: | PASS | |
| Task 8 필수 항목: **Step 3: | PASS | |
| Task 8 필수 항목: **Step 4: | PASS | |
| Task 8 필수 항목: **Step 5: | PASS | |
| Task 8 필수 항목: Expected: | PASS | |
| Task 8 필수 항목: git commit -m | PASS | |
| Task 9 필수 항목: **Files:** | PASS | |
| Task 9 필수 항목: **Interfaces:** | PASS | |
| Task 9 필수 항목: **Implementation requirements:** | PASS | |
| Task 9 필수 항목: **Step 1: | PASS | |
| Task 9 필수 항목: **Step 2: | PASS | |
| Task 9 필수 항목: **Step 3: | PASS | |
| Task 9 필수 항목: **Step 4: | PASS | |
| Task 9 필수 항목: **Step 5: | PASS | |
| Task 9 필수 항목: Expected: | PASS | |
| Task 9 필수 항목: git commit -m | PASS | |
| Task 10 필수 항목: **Files:** | PASS | |
| Task 10 필수 항목: **Interfaces:** | PASS | |
| Task 10 필수 항목: **Implementation requirements:** | PASS | |
| Task 10 필수 항목: **Step 1: | PASS | |
| Task 10 필수 항목: **Step 2: | PASS | |
| Task 10 필수 항목: **Step 3: | PASS | |
| Task 10 필수 항목: **Step 4: | PASS | |
| Task 10 필수 항목: **Step 5: | PASS | |
| Task 10 필수 항목: Expected: | PASS | |
| Task 10 필수 항목: git commit -m | PASS | |
| Task 11 필수 항목: **Files:** | PASS | |
| Task 11 필수 항목: **Interfaces:** | PASS | |
| Task 11 필수 항목: **Implementation requirements:** | PASS | |
| Task 11 필수 항목: **Step 1: | PASS | |
| Task 11 필수 항목: **Step 2: | PASS | |
| Task 11 필수 항목: **Step 3: | PASS | |
| Task 11 필수 항목: **Step 4: | PASS | |
| Task 11 필수 항목: **Step 5: | PASS | |
| Task 11 필수 항목: Expected: | PASS | |
| Task 11 필수 항목: git commit -m | PASS | |
| Task 12 필수 항목: **Files:** | PASS | |
| Task 12 필수 항목: **Interfaces:** | PASS | |
| Task 12 필수 항목: **Implementation requirements:** | PASS | |
| Task 12 필수 항목: **Step 1: | PASS | |
| Task 12 필수 항목: **Step 2: | PASS | |
| Task 12 필수 항목: **Step 3: | PASS | |
| Task 12 필수 항목: **Step 4: | PASS | |
| Task 12 필수 항목: **Step 5: | PASS | |
| Task 12 필수 항목: Expected: | PASS | |
| Task 12 필수 항목: git commit -m | PASS | |
| Task 13 필수 항목: **Files:** | PASS | |
| Task 13 필수 항목: **Interfaces:** | PASS | |
| Task 13 필수 항목: **Implementation requirements:** | PASS | |
| Task 13 필수 항목: **Step 1: | PASS | |
| Task 13 필수 항목: **Step 2: | PASS | |
| Task 13 필수 항목: **Step 3: | PASS | |
| Task 13 필수 항목: **Step 4: | PASS | |
| Task 13 필수 항목: **Step 5: | PASS | |
| Task 13 필수 항목: Expected: | PASS | |
| Task 13 필수 항목: git commit -m | PASS | |
| Task 14 필수 항목: **Files:** | PASS | |
| Task 14 필수 항목: **Interfaces:** | PASS | |
| Task 14 필수 항목: **Implementation requirements:** | PASS | |
| Task 14 필수 항목: **Step 1: | PASS | |
| Task 14 필수 항목: **Step 2: | PASS | |
| Task 14 필수 항목: **Step 3: | PASS | |
| Task 14 필수 항목: **Step 4: | PASS | |
| Task 14 필수 항목: **Step 5: | PASS | |
| Task 14 필수 항목: Expected: | PASS | |
| Task 14 필수 항목: git commit -m | PASS | |
| Task 15 필수 항목: **Files:** | PASS | |
| Task 15 필수 항목: **Interfaces:** | PASS | |
| Task 15 필수 항목: **Implementation requirements:** | PASS | |
| Task 15 필수 항목: **Step 1: | PASS | |
| Task 15 필수 항목: **Step 2: | PASS | |
| Task 15 필수 항목: **Step 3: | PASS | |
| Task 15 필수 항목: **Step 4: | PASS | |
| Task 15 필수 항목: **Step 5: | PASS | |
| Task 15 필수 항목: Expected: | PASS | |
| Task 15 필수 항목: git commit -m | PASS | |
| Task 16 필수 항목: **Files:** | PASS | |
| Task 16 필수 항목: **Interfaces:** | PASS | |
| Task 16 필수 항목: **Implementation requirements:** | PASS | |
| Task 16 필수 항목: **Step 1: | PASS | |
| Task 16 필수 항목: **Step 2: | PASS | |
| Task 16 필수 항목: **Step 3: | PASS | |
| Task 16 필수 항목: **Step 4: | PASS | |
| Task 16 필수 항목: **Step 5: | PASS | |
| Task 16 필수 항목: Expected: | PASS | |
| Task 16 필수 항목: git commit -m | PASS | |
| Task 17 필수 항목: **Files:** | PASS | |
| Task 17 필수 항목: **Interfaces:** | PASS | |
| Task 17 필수 항목: **Implementation requirements:** | PASS | |
| Task 17 필수 항목: **Step 1: | PASS | |
| Task 17 필수 항목: **Step 2: | PASS | |
| Task 17 필수 항목: **Step 3: | PASS | |
| Task 17 필수 항목: **Step 4: | PASS | |
| Task 17 필수 항목: **Step 5: | PASS | |
| Task 17 필수 항목: Expected: | PASS | |
| Task 17 필수 항목: git commit -m | PASS | |
| Task 18 필수 항목: **Files:** | PASS | |
| Task 18 필수 항목: **Interfaces:** | PASS | |
| Task 18 필수 항목: **Implementation requirements:** | PASS | |
| Task 18 필수 항목: **Step 1: | PASS | |
| Task 18 필수 항목: **Step 2: | PASS | |
| Task 18 필수 항목: **Step 3: | PASS | |
| Task 18 필수 항목: **Step 4: | PASS | |
| Task 18 필수 항목: **Step 5: | PASS | |
| Task 18 필수 항목: Expected: | PASS | |
| Task 18 필수 항목: git commit -m | PASS | |
| Task 19 필수 항목: **Files:** | PASS | |
| Task 19 필수 항목: **Interfaces:** | PASS | |
| Task 19 필수 항목: **Implementation requirements:** | PASS | |
| Task 19 필수 항목: **Step 1: | PASS | |
| Task 19 필수 항목: **Step 2: | PASS | |
| Task 19 필수 항목: **Step 3: | PASS | |
| Task 19 필수 항목: **Step 4: | PASS | |
| Task 19 필수 항목: **Step 5: | PASS | |
| Task 19 필수 항목: Expected: | PASS | |
| Task 19 필수 항목: git commit -m | PASS | |
| Task 20 필수 항목: **Files:** | PASS | |
| Task 20 필수 항목: **Interfaces:** | PASS | |
| Task 20 필수 항목: **Implementation requirements:** | PASS | |
| Task 20 필수 항목: **Step 1: | PASS | |
| Task 20 필수 항목: **Step 2: | PASS | |
| Task 20 필수 항목: **Step 3: | PASS | |
| Task 20 필수 항목: **Step 4: | PASS | |
| Task 20 필수 항목: **Step 5: | PASS | |
| Task 20 필수 항목: Expected: | PASS | |
| Task 20 필수 항목: git commit -m | PASS | |
| Task 21 필수 항목: **Files:** | PASS | |
| Task 21 필수 항목: **Interfaces:** | PASS | |
| Task 21 필수 항목: **Implementation requirements:** | PASS | |
| Task 21 필수 항목: **Step 1: | PASS | |
| Task 21 필수 항목: **Step 2: | PASS | |
| Task 21 필수 항목: **Step 3: | PASS | |
| Task 21 필수 항목: **Step 4: | PASS | |
| Task 21 필수 항목: **Step 5: | PASS | |
| Task 21 필수 항목: Expected: | PASS | |
| Task 21 필수 항목: git commit -m | PASS | |
| Task 22 필수 항목: **Files:** | PASS | |
| Task 22 필수 항목: **Interfaces:** | PASS | |
| Task 22 필수 항목: **Implementation requirements:** | PASS | |
| Task 22 필수 항목: **Step 1: | PASS | |
| Task 22 필수 항목: **Step 2: | PASS | |
| Task 22 필수 항목: **Step 3: | PASS | |
| Task 22 필수 항목: **Step 4: | PASS | |
| Task 22 필수 항목: **Step 5: | PASS | |
| Task 22 필수 항목: Expected: | PASS | |
| Task 22 필수 항목: git commit -m | PASS | |
| Task 23 필수 항목: **Files:** | PASS | |
| Task 23 필수 항목: **Interfaces:** | PASS | |
| Task 23 필수 항목: **Implementation requirements:** | PASS | |
| Task 23 필수 항목: **Step 1: | PASS | |
| Task 23 필수 항목: **Step 2: | PASS | |
| Task 23 필수 항목: **Step 3: | PASS | |
| Task 23 필수 항목: **Step 4: | PASS | |
| Task 23 필수 항목: **Step 5: | PASS | |
| Task 23 필수 항목: Expected: | PASS | |
| Task 23 필수 항목: git commit -m | PASS | |
| Task 24 필수 항목: **Files:** | PASS | |
| Task 24 필수 항목: **Interfaces:** | PASS | |
| Task 24 필수 항목: **Implementation requirements:** | PASS | |
| Task 24 필수 항목: **Step 1: | PASS | |
| Task 24 필수 항목: **Step 2: | PASS | |
| Task 24 필수 항목: **Step 3: | PASS | |
| Task 24 필수 항목: **Step 4: | PASS | |
| Task 24 필수 항목: **Step 5: | PASS | |
| Task 24 필수 항목: Expected: | PASS | |
| Task 24 필수 항목: git commit -m | PASS | |
| Task 25 필수 항목: **Files:** | PASS | |
| Task 25 필수 항목: **Interfaces:** | PASS | |
| Task 25 필수 항목: **Implementation requirements:** | PASS | |
| Task 25 필수 항목: **Step 1: | PASS | |
| Task 25 필수 항목: **Step 2: | PASS | |
| Task 25 필수 항목: **Step 3: | PASS | |
| Task 25 필수 항목: **Step 4: | PASS | |
| Task 25 필수 항목: **Step 5: | PASS | |
| Task 25 필수 항목: Expected: | PASS | |
| Task 25 필수 항목: git commit -m | PASS | |
| Task 26 필수 항목: **Files:** | PASS | |
| Task 26 필수 항목: **Interfaces:** | PASS | |
| Task 26 필수 항목: **Implementation requirements:** | PASS | |
| Task 26 필수 항목: **Step 1: | PASS | |
| Task 26 필수 항목: **Step 2: | PASS | |
| Task 26 필수 항목: **Step 3: | PASS | |
| Task 26 필수 항목: **Step 4: | PASS | |
| Task 26 필수 항목: **Step 5: | PASS | |
| Task 26 필수 항목: Expected: | PASS | |
| Task 26 필수 항목: git commit -m | PASS | |
| Task 27 필수 항목: **Files:** | PASS | |
| Task 27 필수 항목: **Interfaces:** | PASS | |
| Task 27 필수 항목: **Implementation requirements:** | PASS | |
| Task 27 필수 항목: **Step 1: | PASS | |
| Task 27 필수 항목: **Step 2: | PASS | |
| Task 27 필수 항목: **Step 3: | PASS | |
| Task 27 필수 항목: **Step 4: | PASS | |
| Task 27 필수 항목: **Step 5: | PASS | |
| Task 27 필수 항목: Expected: | PASS | |
| Task 27 필수 항목: git commit -m | PASS | |
| Task 28 필수 항목: **Files:** | PASS | |
| Task 28 필수 항목: **Interfaces:** | PASS | |
| Task 28 필수 항목: **Implementation requirements:** | PASS | |
| Task 28 필수 항목: **Step 1: | PASS | |
| Task 28 필수 항목: **Step 2: | PASS | |
| Task 28 필수 항목: **Step 3: | PASS | |
| Task 28 필수 항목: **Step 4: | PASS | |
| Task 28 필수 항목: **Step 5: | PASS | |
| Task 28 필수 항목: Expected: | PASS | |
| Task 28 필수 항목: git commit -m | PASS | |
| Task 29 필수 항목: **Files:** | PASS | |
| Task 29 필수 항목: **Interfaces:** | PASS | |
| Task 29 필수 항목: **Implementation requirements:** | PASS | |
| Task 29 필수 항목: **Step 1: | PASS | |
| Task 29 필수 항목: **Step 2: | PASS | |
| Task 29 필수 항목: **Step 3: | PASS | |
| Task 29 필수 항목: **Step 4: | PASS | |
| Task 29 필수 항목: **Step 5: | PASS | |
| Task 29 필수 항목: Expected: | PASS | |
| Task 29 필수 항목: git commit -m | PASS | |
| Task 30 필수 항목: **Files:** | PASS | |
| Task 30 필수 항목: **Interfaces:** | PASS | |
| Task 30 필수 항목: **Implementation requirements:** | PASS | |
| Task 30 필수 항목: **Step 1: | PASS | |
| Task 30 필수 항목: **Step 2: | PASS | |
| Task 30 필수 항목: **Step 3: | PASS | |
| Task 30 필수 항목: **Step 4: | PASS | |
| Task 30 필수 항목: **Step 5: | PASS | |
| Task 30 필수 항목: Expected: | PASS | |
| Task 30 필수 항목: git commit -m | PASS | |
| Task 31 필수 항목: **Files:** | PASS | |
| Task 31 필수 항목: **Interfaces:** | PASS | |
| Task 31 필수 항목: **Implementation requirements:** | PASS | |
| Task 31 필수 항목: **Step 1: | PASS | |
| Task 31 필수 항목: **Step 2: | PASS | |
| Task 31 필수 항목: **Step 3: | PASS | |
| Task 31 필수 항목: **Step 4: | PASS | |
| Task 31 필수 항목: **Step 5: | PASS | |
| Task 31 필수 항목: Expected: | PASS | |
| Task 31 필수 항목: git commit -m | PASS | |
| Task 32 필수 항목: **Files:** | PASS | |
| Task 32 필수 항목: **Interfaces:** | PASS | |
| Task 32 필수 항목: **Implementation requirements:** | PASS | |
| Task 32 필수 항목: **Step 1: | PASS | |
| Task 32 필수 항목: **Step 2: | PASS | |
| Task 32 필수 항목: **Step 3: | PASS | |
| Task 32 필수 항목: **Step 4: | PASS | |
| Task 32 필수 항목: **Step 5: | PASS | |
| Task 32 필수 항목: Expected: | PASS | |
| Task 32 필수 항목: git commit -m | PASS | |
| Task 33 필수 항목: **Files:** | PASS | |
| Task 33 필수 항목: **Interfaces:** | PASS | |
| Task 33 필수 항목: **Implementation requirements:** | PASS | |
| Task 33 필수 항목: **Step 1: | PASS | |
| Task 33 필수 항목: **Step 2: | PASS | |
| Task 33 필수 항목: **Step 3: | PASS | |
| Task 33 필수 항목: **Step 4: | PASS | |
| Task 33 필수 항목: **Step 5: | PASS | |
| Task 33 필수 항목: Expected: | PASS | |
| Task 33 필수 항목: git commit -m | PASS | |
| Task 34 필수 항목: **Files:** | PASS | |
| Task 34 필수 항목: **Interfaces:** | PASS | |
| Task 34 필수 항목: **Implementation requirements:** | PASS | |
| Task 34 필수 항목: **Step 1: | PASS | |
| Task 34 필수 항목: **Step 2: | PASS | |
| Task 34 필수 항목: **Step 3: | PASS | |
| Task 34 필수 항목: **Step 4: | PASS | |
| Task 34 필수 항목: **Step 5: | PASS | |
| Task 34 필수 항목: Expected: | PASS | |
| Task 34 필수 항목: git commit -m | PASS | |
| Task 35 필수 항목: **Files:** | PASS | |
| Task 35 필수 항목: **Interfaces:** | PASS | |
| Task 35 필수 항목: **Implementation requirements:** | PASS | |
| Task 35 필수 항목: **Step 1: | PASS | |
| Task 35 필수 항목: **Step 2: | PASS | |
| Task 35 필수 항목: **Step 3: | PASS | |
| Task 35 필수 항목: **Step 4: | PASS | |
| Task 35 필수 항목: **Step 5: | PASS | |
| Task 35 필수 항목: Expected: | PASS | |
| Task 35 필수 항목: git commit -m | PASS | |
| Task 36 필수 항목: **Files:** | PASS | |
| Task 36 필수 항목: **Interfaces:** | PASS | |
| Task 36 필수 항목: **Implementation requirements:** | PASS | |
| Task 36 필수 항목: **Step 1: | PASS | |
| Task 36 필수 항목: **Step 2: | PASS | |
| Task 36 필수 항목: **Step 3: | PASS | |
| Task 36 필수 항목: **Step 4: | PASS | |
| Task 36 필수 항목: **Step 5: | PASS | |
| Task 36 필수 항목: Expected: | PASS | |
| Task 36 필수 항목: git commit -m | PASS | |
| Task 37 필수 항목: **Files:** | PASS | |
| Task 37 필수 항목: **Interfaces:** | PASS | |
| Task 37 필수 항목: **Implementation requirements:** | PASS | |
| Task 37 필수 항목: **Step 1: | PASS | |
| Task 37 필수 항목: **Step 2: | PASS | |
| Task 37 필수 항목: **Step 3: | PASS | |
| Task 37 필수 항목: **Step 4: | PASS | |
| Task 37 필수 항목: **Step 5: | PASS | |
| Task 37 필수 항목: Expected: | PASS | |
| Task 37 필수 항목: git commit -m | PASS | |
| Task 38 필수 항목: **Files:** | PASS | |
| Task 38 필수 항목: **Interfaces:** | PASS | |
| Task 38 필수 항목: **Implementation requirements:** | PASS | |
| Task 38 필수 항목: **Step 1: | PASS | |
| Task 38 필수 항목: **Step 2: | PASS | |
| Task 38 필수 항목: **Step 3: | PASS | |
| Task 38 필수 항목: **Step 4: | PASS | |
| Task 38 필수 항목: **Step 5: | PASS | |
| Task 38 필수 항목: Expected: | PASS | |
| Task 38 필수 항목: git commit -m | PASS | |
| Task 39 필수 항목: **Files:** | PASS | |
| Task 39 필수 항목: **Interfaces:** | PASS | |
| Task 39 필수 항목: **Implementation requirements:** | PASS | |
| Task 39 필수 항목: **Step 1: | PASS | |
| Task 39 필수 항목: **Step 2: | PASS | |
| Task 39 필수 항목: **Step 3: | PASS | |
| Task 39 필수 항목: **Step 4: | PASS | |
| Task 39 필수 항목: **Step 5: | PASS | |
| Task 39 필수 항목: Expected: | PASS | |
| Task 39 필수 항목: git commit -m | PASS | |
| Task 40 필수 항목: **Files:** | PASS | |
| Task 40 필수 항목: **Interfaces:** | PASS | |
| Task 40 필수 항목: **Implementation requirements:** | PASS | |
| Task 40 필수 항목: **Step 1: | PASS | |
| Task 40 필수 항목: **Step 2: | PASS | |
| Task 40 필수 항목: **Step 3: | PASS | |
| Task 40 필수 항목: **Step 4: | PASS | |
| Task 40 필수 항목: **Step 5: | PASS | |
| Task 40 필수 항목: Expected: | PASS | |
| Task 40 필수 항목: git commit -m | PASS | |
| Task 41 필수 항목: **Files:** | PASS | |
| Task 41 필수 항목: **Interfaces:** | PASS | |
| Task 41 필수 항목: **Implementation requirements:** | PASS | |
| Task 41 필수 항목: **Step 1: | PASS | |
| Task 41 필수 항목: **Step 2: | PASS | |
| Task 41 필수 항목: **Step 3: | PASS | |
| Task 41 필수 항목: **Step 4: | PASS | |
| Task 41 필수 항목: **Step 5: | PASS | |
| Task 41 필수 항목: Expected: | PASS | |
| Task 41 필수 항목: git commit -m | PASS | |
| Task 42 필수 항목: **Files:** | PASS | |
| Task 42 필수 항목: **Interfaces:** | PASS | |
| Task 42 필수 항목: **Implementation requirements:** | PASS | |
| Task 42 필수 항목: **Step 1: | PASS | |
| Task 42 필수 항목: **Step 2: | PASS | |
| Task 42 필수 항목: **Step 3: | PASS | |
| Task 42 필수 항목: **Step 4: | PASS | |
| Task 42 필수 항목: **Step 5: | PASS | |
| Task 42 필수 항목: Expected: | PASS | |
| Task 42 필수 항목: git commit -m | PASS | |
| Task 43 필수 항목: **Files:** | PASS | |
| Task 43 필수 항목: **Interfaces:** | PASS | |
| Task 43 필수 항목: **Implementation requirements:** | PASS | |
| Task 43 필수 항목: **Step 1: | PASS | |
| Task 43 필수 항목: **Step 2: | PASS | |
| Task 43 필수 항목: **Step 3: | PASS | |
| Task 43 필수 항목: **Step 4: | PASS | |
| Task 43 필수 항목: **Step 5: | PASS | |
| Task 43 필수 항목: Expected: | PASS | |
| Task 43 필수 항목: git commit -m | PASS | |
| Task 44 필수 항목: **Files:** | PASS | |
| Task 44 필수 항목: **Interfaces:** | PASS | |
| Task 44 필수 항목: **Implementation requirements:** | PASS | |
| Task 44 필수 항목: **Step 1: | PASS | |
| Task 44 필수 항목: **Step 2: | PASS | |
| Task 44 필수 항목: **Step 3: | PASS | |
| Task 44 필수 항목: **Step 4: | PASS | |
| Task 44 필수 항목: **Step 5: | PASS | |
| Task 44 필수 항목: Expected: | PASS | |
| Task 44 필수 항목: git commit -m | PASS | |
| Task 45 필수 항목: **Files:** | PASS | |
| Task 45 필수 항목: **Interfaces:** | PASS | |
| Task 45 필수 항목: **Implementation requirements:** | PASS | |
| Task 45 필수 항목: **Step 1: | PASS | |
| Task 45 필수 항목: **Step 2: | PASS | |
| Task 45 필수 항목: **Step 3: | PASS | |
| Task 45 필수 항목: **Step 4: | PASS | |
| Task 45 필수 항목: **Step 5: | PASS | |
| Task 45 필수 항목: Expected: | PASS | |
| Task 45 필수 항목: git commit -m | PASS | |
| Task 46 필수 항목: **Files:** | PASS | |
| Task 46 필수 항목: **Interfaces:** | PASS | |
| Task 46 필수 항목: **Implementation requirements:** | PASS | |
| Task 46 필수 항목: **Step 1: | PASS | |
| Task 46 필수 항목: **Step 2: | PASS | |
| Task 46 필수 항목: **Step 3: | PASS | |
| Task 46 필수 항목: **Step 4: | PASS | |
| Task 46 필수 항목: **Step 5: | PASS | |
| Task 46 필수 항목: Expected: | PASS | |
| Task 46 필수 항목: git commit -m | PASS | |
| Task 47 필수 항목: **Files:** | PASS | |
| Task 47 필수 항목: **Interfaces:** | PASS | |
| Task 47 필수 항목: **Implementation requirements:** | PASS | |
| Task 47 필수 항목: **Step 1: | PASS | |
| Task 47 필수 항목: **Step 2: | PASS | |
| Task 47 필수 항목: **Step 3: | PASS | |
| Task 47 필수 항목: **Step 4: | PASS | |
| Task 47 필수 항목: **Step 5: | PASS | |
| Task 47 필수 항목: Expected: | PASS | |
| Task 47 필수 항목: git commit -m | PASS | |
| Task 48 필수 항목: **Files:** | PASS | |
| Task 48 필수 항목: **Interfaces:** | PASS | |
| Task 48 필수 항목: **Implementation requirements:** | PASS | |
| Task 48 필수 항목: **Step 1: | PASS | |
| Task 48 필수 항목: **Step 2: | PASS | |
| Task 48 필수 항목: **Step 3: | PASS | |
| Task 48 필수 항목: **Step 4: | PASS | |
| Task 48 필수 항목: **Step 5: | PASS | |
| Task 48 필수 항목: Expected: | PASS | |
| Task 48 필수 항목: git commit -m | PASS | |
| Task 49 필수 항목: **Files:** | PASS | |
| Task 49 필수 항목: **Interfaces:** | PASS | |
| Task 49 필수 항목: **Implementation requirements:** | PASS | |
| Task 49 필수 항목: **Step 1: | PASS | |
| Task 49 필수 항목: **Step 2: | PASS | |
| Task 49 필수 항목: **Step 3: | PASS | |
| Task 49 필수 항목: **Step 4: | PASS | |
| Task 49 필수 항목: **Step 5: | PASS | |
| Task 49 필수 항목: Expected: | PASS | |
| Task 49 필수 항목: git commit -m | PASS | |
| Task 50 필수 항목: **Files:** | PASS | |
| Task 50 필수 항목: **Interfaces:** | PASS | |
| Task 50 필수 항목: **Implementation requirements:** | PASS | |
| Task 50 필수 항목: **Step 1: | PASS | |
| Task 50 필수 항목: **Step 2: | PASS | |
| Task 50 필수 항목: **Step 3: | PASS | |
| Task 50 필수 항목: **Step 4: | PASS | |
| Task 50 필수 항목: **Step 5: | PASS | |
| Task 50 필수 항목: Expected: | PASS | |
| Task 50 필수 항목: git commit -m | PASS | |
| Create 경로 중복 없음 | PASS | |
| Create 경로 충분성 | PASS | 322 |
| 미확정 표현 없음: TODO | PASS | 0 |
| 미확정 표현 없음: TBD | PASS | 0 |
| 미확정 표현 없음: FIXME | PASS | 0 |
| 미확정 표현 없음: fill in details | PASS | 0 |
| 미확정 표현 없음: implement later | PASS | 0 |
| 미확정 표현 없음: concrete assertion below | PASS | 0 |
| 미확정 표현 없음: 유사 작업 참조 | PASS | 0 |
| 계획 핵심 회귀 테스트: providerAcceptanceIsNotDelivery | PASS | |
| 계획 핵심 회귀 테스트: concurrentSameRequestReturnsOneNotificationId | PASS | |
| 계획 핵심 회귀 테스트: providerAcceptsThenResponseIsLostRecordsAmbiguousAndBlocksFallback | PASS | |
| 계획 핵심 회귀 테스트: deliveredBeforeSentNeverDowngrades | PASS | |
| 계획 핵심 회귀 테스트: fcmInstallationAndLegacyTokenAreDifferentTypes | PASS | |
| 계획 핵심 회귀 테스트: http200IsProviderAcceptedNotDelivered | PASS | |
| 계획 핵심 회귀 테스트: ttlHeaderIsRequiredAndAcceptanceIsNotDelivery | PASS | |
| 계획 핵심 회귀 테스트: websocketFailureDoesNotRollbackInboxItem | PASS | |
| 계획 핵심 회귀 테스트: metricTagsNeverContainHighCardinalityIdentifiers | PASS | |
| 계획 핵심 회귀 테스트: acceptedThenResponseLossIsAmbiguousForEveryApplicableAdapter | PASS | |
| 계획 핵심 회귀 테스트: notificationPerformanceTest | PASS | |
| 설계·계획 날짜 일치 | PASS | |
| Java 21 가정 명시 | PASS | |
| 실제 저장소 부재 가정 명시 | PASS | |
| Provider SDK 공개 금지 | PASS | |
| Core async CompletionStage | PASS | |
| FCM FID 우선 | PASS | |
| Ambiguous fallback 금지 | PASS | |
| ProviderEvent 원장 | PASS | |
| ZIP CRC 무결성 | PASS | None |
| ZIP 필수 파일 | PASS | [] |
## 검증 범위
- 이 검증은 Markdown 설계서와 구현 계획서의 구조·정합성·필수 계약·경로 중복·미확정 표현·패키지 CRC를 검사한다.
- 실제 Backend Skeleton 저장소가 입력되지 않았으므로 Gradle compile, Provider sandbox, PostgreSQL integration, chaos, performance test 실행 결과는 포함하지 않는다.
- 구현 시에는 계획의 각 Task가 지정한 red-green TDD 명령을 실제 저장소에서 실행해야 한다.
@@ -0,0 +1,188 @@
#!/usr/bin/env python3
from __future__ import annotations
from collections import Counter
from pathlib import Path
import hashlib
import re
import sys
import zipfile
ROOT = Path('/mnt/data')
DESIGN = ROOT / 'notification-platform-design.md'
PLAN = ROOT / 'notification-platform-implementation-plan.md'
REPORT = ROOT / 'notification-superpowers-validation.md'
PACKAGE = ROOT / 'notification-superpowers-package.zip'
checks: list[tuple[str, bool, str]] = []
def check(name: str, condition: bool, detail: str = '') -> None:
checks.append((name, bool(condition), detail))
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open('rb') as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b''):
digest.update(chunk)
return digest.hexdigest()
def main() -> int:
check('설계서 존재', DESIGN.is_file(), str(DESIGN))
check('구현 계획서 존재', PLAN.is_file(), str(PLAN))
if not DESIGN.is_file() or not PLAN.is_file():
return write_report()
design = DESIGN.read_text(encoding='utf-8')
plan = PLAN.read_text(encoding='utf-8')
design_lines = design.count('\n') + 1
plan_lines = plan.count('\n') + 1
check('설계서 최소 상세도', design_lines >= 3000, f'{design_lines:,} lines')
check('계획서 최소 상세도', plan_lines >= 4000, f'{plan_lines:,} lines')
check('설계서 코드 fence 균형', design.count('```') % 2 == 0, str(design.count('```')))
check('계획서 코드 fence 균형', plan.count('```') % 2 == 0, str(plan.count('```')))
required_design_terms = [
'NotificationRequest', 'RecipientDelivery', 'DeliveryAttempt',
'ProviderEvent', 'EvidenceLevel', 'SubmissionOutcome', 'DeliveryOutcome',
'AMBIGUOUS', 'append-only', 'FCM_FID', 'FCM_REGISTRATION_TOKEN_LEGACY',
'AES-256-GCM', 'HMAC-SHA-256', 'FOR UPDATE SKIP LOCKED',
'SMTP Adapter', 'Amazon SES Adapter', 'Twilio Adapter', 'FCM Adapter',
'APNs Adapter', 'Web Push Adapter', 'In-App Inbox', 'Reconciliation',
'N4 Admin Plane', '비지원 범위', '완료 정의'
]
for term in required_design_terms:
check(f'설계 핵심 계약: {term}', term in design)
check('exactlyOnce 단순 옵션 금지 명시',
'`exactlyOnce=true` 같은 단순 옵션을 두는 것은 잘못된 추상화' in design)
forbidden_design_claims = [
'guaranteedDelivery=true',
'APNs HTTP 200 = DELIVERED',
'FCM send success = DEVICE_DELIVERED',
]
for phrase in forbidden_design_claims:
check(f'금지 보장 부재: {phrase}', phrase not in design)
tasks = [int(x) for x in re.findall(r'^### Task (\d+):', plan, re.MULTILINE)]
check('Task 1~50 연속성', tasks == list(range(1, 51)), str(tasks))
task_chunks = re.split(r'(?=^### Task \d+:)', plan, flags=re.MULTILINE)[1:]
check('Task chunk 수', len(task_chunks) == 50, str(len(task_chunks)))
required_task_sections = [
'**Files:**', '**Interfaces:**', '**Implementation requirements:**',
'**Step 1:', '**Step 2:', '**Step 3:', '**Step 4:', '**Step 5:',
'Expected:', 'git commit -m'
]
for index, chunk in enumerate(task_chunks, 1):
for marker in required_task_sections:
check(f'Task {index} 필수 항목: {marker}', marker in chunk)
creates = re.findall(r'^- Create: `([^`]+)`', plan, re.MULTILINE)
duplicate_creates = sorted(path for path, count in Counter(creates).items() if count > 1)
check('Create 경로 중복 없음', not duplicate_creates, ', '.join(duplicate_creates))
check('Create 경로 충분성', len(creates) >= 200, str(len(creates)))
red_flags = {
'TODO': r'\bTODO\b',
'TBD': r'\bTBD\b',
'FIXME': r'\bFIXME\b',
'fill in details': r'fill in details',
'implement later': r'implement later',
'concrete assertion below': r'concrete assertion below',
'유사 작업 참조': r'Similar to Task',
}
for label, pattern in red_flags.items():
matches = re.findall(pattern, plan, re.IGNORECASE)
check(f'미확정 표현 없음: {label}', not matches, str(len(matches)))
key_plan_terms = [
'providerAcceptanceIsNotDelivery',
'concurrentSameRequestReturnsOneNotificationId',
'providerAcceptsThenResponseIsLostRecordsAmbiguousAndBlocksFallback',
'deliveredBeforeSentNeverDowngrades',
'fcmInstallationAndLegacyTokenAreDifferentTypes',
'http200IsProviderAcceptedNotDelivered',
'ttlHeaderIsRequiredAndAcceptanceIsNotDelivery',
'websocketFailureDoesNotRollbackInboxItem',
'metricTagsNeverContainHighCardinalityIdentifiers',
'acceptedThenResponseLossIsAmbiguousForEveryApplicableAdapter',
'notificationPerformanceTest',
]
for term in key_plan_terms:
check(f'계획 핵심 회귀 테스트: {term}', term in plan)
check('설계·계획 날짜 일치', '2026-08-10' in design and '2026-08-10' in plan)
check('Java 21 가정 명시', 'Java 21' in design and 'Java 21' in plan)
check('실제 저장소 부재 가정 명시', '실제 저장소가 제공되지 않아' in design)
check('Provider SDK 공개 금지', 'Provider SDK' in design and 'raw SDK client' in plan)
check('Core async CompletionStage', 'CompletionStage' in design and 'CompletionStage' in plan)
check('FCM FID 우선', 'FID 우선' in design and 'FCM primary target은 FID' in plan)
check('Ambiguous fallback 금지', 'ambiguousAttemptExists = true' in design and '`AMBIGUOUS` attempt가 있는 recipient' in plan)
check('ProviderEvent 원장', 'append-only ledger' in plan and 'ProviderEvent 원장' in design)
if PACKAGE.is_file():
try:
with zipfile.ZipFile(PACKAGE) as archive:
bad = archive.testzip()
names = set(archive.namelist())
required = {
'notification-superpowers-package/docs/superpowers/specs/2026-08-10-notification-platform-design.md',
'notification-superpowers-package/docs/superpowers/plans/2026-08-10-notification-platform-implementation-plan.md',
'notification-superpowers-package/README.md',
'notification-superpowers-package/VALIDATION.md',
'notification-superpowers-package/validate_notification_docs.py',
'notification-superpowers-package/MANIFEST.sha256',
}
check('ZIP CRC 무결성', bad is None, str(bad))
check('ZIP 필수 파일', required.issubset(names), str(sorted(required - names)))
except zipfile.BadZipFile as exc:
check('ZIP 열기', False, str(exc))
else:
check('ZIP 패키지 존재', False, str(PACKAGE))
return write_report(design_lines, plan_lines, len(creates))
def write_report(design_lines: int = 0, plan_lines: int = 0, create_count: int = 0) -> int:
passed = sum(1 for _, ok, _ in checks if ok)
failed = [(name, detail) for name, ok, detail in checks if not ok]
status = 'PASS' if not failed else 'FAIL'
rows = [
'# Notification Superpowers 문서 정적 검증', '',
f'- 결과: **{status}**',
f'- 실행 검사: **{len(checks)}개**',
f'- 통과: **{passed}개**',
f'- 실패: **{len(failed)}개**',
f'- 설계서: **{design_lines:,}행**',
f'- 구현 계획서: **{plan_lines:,}행**',
f'- 구현 Task: **50개**',
f'- Create 경로: **{create_count:,}개**',
f'- 설계 SHA-256: `{sha256(DESIGN) if DESIGN.exists() else "missing"}`',
f'- 계획 SHA-256: `{sha256(PLAN) if PLAN.exists() else "missing"}`',
'', '## 검사 결과', '',
'| 검사 | 결과 | 상세 |', '|---|---:|---|'
]
for name, ok, detail in checks:
safe = detail.replace('|', '\\|').replace('\n', ' ')[:500]
rows.append(f'| {name} | {"PASS" if ok else "FAIL"} | {safe} |')
rows.extend(['', '## 검증 범위', '',
'- 이 검증은 Markdown 설계서와 구현 계획서의 구조·정합성·필수 계약·경로 중복·미확정 표현·패키지 CRC를 검사한다.',
'- 실제 Backend Skeleton 저장소가 입력되지 않았으므로 Gradle compile, Provider sandbox, PostgreSQL integration, chaos, performance test 실행 결과는 포함하지 않는다.',
'- 구현 시에는 계획의 각 Task가 지정한 red-green TDD 명령을 실제 저장소에서 실행해야 한다.',
])
if failed:
rows.extend(['', '## 실패 항목', ''])
rows.extend(f'- **{name}**: {detail}' for name, detail in failed)
REPORT.write_text('\n'.join(rows) + '\n', encoding='utf-8')
print(f'{status}: {passed}/{len(checks)} checks passed')
if failed:
for name, detail in failed:
print(f'FAIL: {name}: {detail}', file=sys.stderr)
return 1
return 0
if __name__ == '__main__':
raise SystemExit(main())