# DNP — Production Readiness Execution Checklist (Mechanical)

**Date:** 2025-12-24  
**Repo:** `daily-notification-plugin/`  
**Goal:** Prove the plugin is "shippable" by running a deterministic sequence of checks across **TypeScript**, **Android**, **iOS**, and **Docs/Drift**.

---

## 0) One-time setup

### 0.1 Confirm you're at repo root
```bash
pwd
ls
```

**✅ Expected to include folders like:**
- `src/`
- `android/`
- `ios/`
- `docs/`
- `scripts/`

### 0.2 Capture current revision (for receipts)
```bash
git rev-parse HEAD
git status --porcelain
```

**Record output into a log note** (or paste into your progress doc).

---

## 1) Repo-wide "truth checks" (fast)

### 1.1 Core code must have **zero TODO markers**
```bash
grep -RIn --exclude-dir=docs --exclude-dir=test-apps --exclude-dir=node_modules --exclude-dir=.git "TODO:" ios android src packages lib scripts tests || true
```

**✅ Pass condition:**
- No matches.

**If any show up:** treat as "production code TODO" and resolve.

### 1.2 Docs/test-apps TODOs are allowed, but must be measurable
```bash
npm run todo:scan
```

**✅ Pass condition:**
- `docs/TODO-CLASSIFICATION.md` and `docs/todo-scan.json` get updated successfully.

**Verify split reporting:**
- Check that `docs/todo-scan.json` includes `coreCount` and `docsCount` fields.

---

## 2) TypeScript layer: contract + build sanity

### 2.1 Unit tests
```bash
npm test
```

**✅ Pass condition:**
- exits 0.

**Expected output:**
```
Test Suites: 8 passed, 8 total
Tests:       115 passed, 115 total
```

### 2.2 Typecheck
```bash
npm run typecheck
```

**✅ Pass condition:**
- exits 0.

**Expected output:**
```
> @timesafari/daily-notification-plugin@1.0.11 typecheck
> tsc --noEmit
```

### 2.3 Lint (if present)
```bash
npm run lint
```

**✅ Pass condition:**
- exits 0 OR the project explicitly does not have a lint script.

---

## 3) Android: build + worker behavior

### 3.1 Compile sanity
```bash
cd android
./gradlew :assembleDebug
cd ..
```

**✅ Pass condition:**
- build succeeds.

**Expected output:**
```
BUILD SUCCESSFUL in Xs
```

### 3.2 Locate the fetch worker entrypoint
**File:**
- `android/src/main/java/com/timesafari/dailynotification/DailyNotificationFetchWorker.java`

**Search anchors:**
```bash
grep -n "class DailyNotificationFetchWorker" android/src/main/java/com/timesafari/dailynotification/DailyNotificationFetchWorker.java
grep -n "public Result doWork()" android/src/main/java/com/timesafari/dailynotification/DailyNotificationFetchWorker.java
grep -n "interface FetchWorkerMetrics" android/src/main/java/com/timesafari/dailynotification/DailyNotificationFetchWorker.java
grep -n "final class NoopFetchWorkerMetrics" android/src/main/java/com/timesafari/dailynotification/DailyNotificationFetchWorker.java
grep -n "private boolean isRetryable" android/src/main/java/com/timesafari/dailynotification/DailyNotificationFetchWorker.java
```

**✅ Pass condition:**
- Those anchors exist.
- `doWork()` increments metrics and records duration on every return path:
  - `metrics.incRun()`
  - `metrics.observeDurationMs(...)`
  - `metrics.incSuccess()` / `metrics.incFailure()` / `metrics.incRetry()`

### 3.3 Rolling window logic must not be stubbed
**File:**
- `android/src/main/java/com/timesafari/dailynotification/DailyNotificationRollingWindow.java`

**Search anchors:**
```bash
grep -n "private int countPendingNotifications()" android/src/main/java/com/timesafari/dailynotification/DailyNotificationRollingWindow.java
grep -n "private int countNotificationsForDate" android/src/main/java/com/timesafari/dailynotification/DailyNotificationRollingWindow.java
grep -n "private List<NotificationContent> getNotificationsForDate" android/src/main/java/com/timesafari/dailynotification/DailyNotificationRollingWindow.java
grep -n "private long\[\] dateBoundsMillis" android/src/main/java/com/timesafari/dailynotification/DailyNotificationRollingWindow.java
```

**✅ Pass condition:**
- none of these return placeholder defaults like `return 0;` / `return new ArrayList<>();` without logic.

**Verify implementation:**
```bash
grep -A 5 "countPendingNotifications()" android/src/main/java/com/timesafari/dailynotification/DailyNotificationRollingWindow.java | head -10
```

Should show actual logic (storage access, date calculations), not just `return 0;`.

### 3.4 Android smoke test (manual, deterministic)
You need a host app (likely under `test-apps/`).

**Procedure:**
1. Install test host app on emulator/device.
2. Use a test UI action "Schedule notification in 2 minutes" (or equivalent).
3. Observe logs.

**Log capture:**
```bash
adb logcat | grep -i "DailyNotification\|dnp\|timesafari"
```

**✅ Pass condition:**
- notification schedules successfully
- pending count increases
- no retry storm (worker shouldn't loop endlessly)

> **Note:** If the test app doesn't expose a "+2 minutes" button, add it: that becomes your permanent "smoke lever."

---

## 4) iOS: build + scheduler behavior

### 4.1 Workspace exists
```bash
ls ios | grep -E "xcworkspace|xcodeproj" || true
```

**✅ Pass condition:**
- you see `DailyNotificationPlugin.xcworkspace` (or equivalent).

### 4.2 iOS build/test sanity
```bash
cd ios
xcodebuild -workspace DailyNotificationPlugin.xcworkspace -scheme DailyNotificationPlugin -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15' test
cd ..
```

**✅ Pass condition:**
- tests pass (or if there are no tests wired, build succeeds).

**Alternative (build only):**
```bash
cd ios
xcodebuild -workspace DailyNotificationPlugin.xcworkspace -scheme DailyNotificationPlugin -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15' build
cd ..
```

### 4.3 Scheduler must enforce TTL + call fetch scheduler hooks
**File:**
- `ios/Plugin/DailyNotificationScheduler.swift`

**Search anchors:**
```bash
grep -n "validateBeforeArming" ios/Plugin/DailyNotificationScheduler.swift
grep -n "protocol DailyNotificationFetchScheduling" ios/Plugin/DailyNotificationScheduler.swift
grep -n "NoopFetcherScheduler" ios/Plugin/DailyNotificationScheduler.swift
grep -n "fetchScheduler.scheduleFetch" ios/Plugin/DailyNotificationScheduler.swift
grep -n "fetchScheduler.scheduleImmediateFetch" ios/Plugin/DailyNotificationScheduler.swift
```

**✅ Pass condition:**
- TTL enforcement is present and returns false when invalid
- the old Phase-2 TODO lines are gone
- fetch scheduling calls are real method calls (even if Noop)

**Verify TTL enforcement:**
```bash
grep -A 10 "validateBeforeArming" ios/Plugin/DailyNotificationScheduler.swift | head -15
```

Should show actual validation logic, not just `return true;`.

### 4.4 SQLite persistence must be real (not stubs)
**File:**
- `ios/Plugin/DailyNotificationDatabase.swift`

**Search anchors:**
```bash
grep -n "func saveNotificationContent" ios/Plugin/DailyNotificationDatabase.swift
grep -n "INSERT OR REPLACE INTO" ios/Plugin/DailyNotificationDatabase.swift
grep -n "func deleteNotificationContent" ios/Plugin/DailyNotificationDatabase.swift
grep -n "func clearAllNotifications" ios/Plugin/DailyNotificationDatabase.swift
```

**✅ Pass condition:**
- SQL is executed for save/delete/clear (no placeholder prints-only).

**Verify SQL execution:**
```bash
grep -A 5 "INSERT OR REPLACE INTO" ios/Plugin/DailyNotificationDatabase.swift
```

Should show actual SQLite3 calls (`sqlite3_exec` or similar), not just `print()`.

### 4.5 Rolling window must use UNUserNotificationCenter pending requests
**File:**
- `ios/Plugin/DailyNotificationRollingWindow.swift`

**Search anchors:**
```bash
grep -n "UNUserNotificationCenter.current().getPendingNotificationRequests" ios/Plugin/DailyNotificationRollingWindow.swift
grep -n "fetchPendingRequestsSync" ios/Plugin/DailyNotificationRollingWindow.swift
grep -n "countPendingNotifications" ios/Plugin/DailyNotificationRollingWindow.swift
grep -n "countNotificationsForDate" ios/Plugin/DailyNotificationRollingWindow.swift
grep -n "getNotificationsForDate" ios/Plugin/DailyNotificationRollingWindow.swift
```

**✅ Pass condition:**
- functions do real work and don't return placeholder constants.

**Verify implementation:**
```bash
grep -A 10 "countPendingNotifications" ios/Plugin/DailyNotificationRollingWindow.swift | head -15
```

Should show actual `UNUserNotificationCenter` calls, not just `return 0;`.

---

## 5) Cross-platform behavior checklist (what must match)

### 5.1 "What is pending?" definition is consistent
**Expected behavior:**
- Android pending count: scheduledTime >= now from storage truth
- iOS pending count: UNNotificationCenter pending request count

**✅ Pass condition:**
- Both counts increase after scheduling a future notification and decrease after delivery/cancel.

**Test procedure:**
1. Schedule notification for +2 minutes on Android
2. Check pending count (should be > 0)
3. Schedule notification for +2 minutes on iOS
4. Check pending count (should be > 0)
5. Cancel all notifications
6. Check pending count (should be 0)

### 5.2 "Count for date" definition is consistent
**Expected behavior:**
- Date is `YYYY-MM-DD` local calendar day
- Android uses date bounds (midnight→midnight)
- iOS uses `nextTriggerDate()` and formats to date string

**✅ Pass condition:**
- scheduling a notification for "tomorrow morning" increments tomorrow's date bucket, not today.

**Test procedure:**
1. Get current date: `date +%Y-%m-%d`
2. Schedule notification for tomorrow 9:00 AM
3. Check count for today (should be unchanged)
4. Check count for tomorrow (should be +1)

### 5.3 TTL behavior is consistent
**Expected behavior:**
- TTL invalid → schedule is skipped (or returns false) and logs explain it.

**✅ Pass condition:**
- both platforms refuse to arm stale content in equivalent circumstances (if TTL logic exists on Android too; if not, document the difference).

**Test procedure:**
1. Create content with `fetchedAt` = 2 days ago
2. Set TTL = 1 day
3. Attempt to schedule
4. Verify schedule fails with TTL error log

---

## 6) Logging + observability receipts (minimal, but mandatory)

### 6.1 Required log lines (choose exact strings and standardize)
Create/confirm a short standard list like:
- `DNP: scheduling notification`
- `DNP: TTL validation failed`
- `DNP: rolling window count pending=`
- `DNP: fetch worker start`
- `DNP: fetch worker success itemsFetched= itemsSaved=`
- `DNP: fetch worker retry reason=`

**✅ Pass condition:**
- You can grep both Android logcat and iOS console for these.

**Verify logging:**
```bash
# Android
adb logcat | grep -i "DNP:"

# iOS (requires device/simulator console)
# Check Xcode console output or device logs
```

### 6.2 Decision logging for failures
When a schedule fails, logs must answer:
- why it failed
- whether it will retry
- what data was rejected (id / slot / date)

**✅ Pass condition:**
- at least one failure path is testable and produces a complete explanation.

**Test procedure:**
1. Attempt to schedule with invalid TTL
2. Check logs for:
   - Error reason
   - Notification ID
   - TTL value
   - Scheduled time

---

## 7) Release packaging sanity

### 7.1 Ensure `scripts/todo-scan.js` is executable
```bash
ls -l scripts/todo-scan.js
```

**✅ Pass condition:**
- executable bit set OR npm script runs regardless.

### 7.2 Clean archive recipe (no junk)
```bash
tar czvf daily-notification-plugin-release.tar.gz \
  --exclude=.git \
  --exclude=node_modules \
  --exclude=.venv \
  --exclude=dist \
  --exclude=build \
  --exclude='*.tar.gz' \
  daily-notification-plugin/
```

**✅ Pass condition:**
- archive created successfully.

**Verify archive contents:**
```bash
tar tzf daily-notification-plugin-release.tar.gz | head -20
```

Should show source files, not build artifacts.

---

## 8) Stop conditions (fail fast rules)
**Stop and fix before proceeding if any occur:**
- Any TODO marker found in `ios/`, `android/`, `src/` (core code)
- Android `assembleDebug` fails
- iOS `xcodebuild test` fails (unless tests are explicitly not configured, in which case: build must succeed)
- Smoke scheduling fails to deliver a notification in ≤ 3 minutes

---

## 9) Final "ready" declaration (what you can say to yourself)
**You may mark "READY" only if:**
- ✅ TypeScript tests + typecheck pass
- ✅ Android builds
- ✅ iOS builds/tests
- ✅ One Android + one iOS smoke schedule succeeds
- ✅ TTL behavior is verified (at least iOS)
- ✅ todo-scan runs and docs reflect reality
- ✅ Core code has zero TODOs
- ✅ Logging is consistent and grep-able

---

## 10) Quick reference: Expected file anchors

### Android
- `android/src/main/java/com/timesafari/dailynotification/DailyNotificationFetchWorker.java` - Worker with metrics
- `android/src/main/java/com/timesafari/dailynotification/DailyNotificationRollingWindow.java` - Rolling window logic
- `android/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.kt` - Main plugin (thin adapter)

### iOS
- `ios/Plugin/DailyNotificationScheduler.swift` - Scheduler with TTL + fetch hooks
- `ios/Plugin/DailyNotificationDatabase.swift` - SQLite persistence
- `ios/Plugin/DailyNotificationRollingWindow.swift` - Rolling window with UNUserNotificationCenter
- `ios/Plugin/DailyNotificationPlugin.swift` - Main plugin (thin adapter)

### TypeScript
- `src/` - Core TypeScript implementation
- `packages/` - Internal packages
- `tests/` - Unit tests

---

## 11) Troubleshooting common issues

### Issue: Android build fails
**Check:**
- Java version: `java -version` (should be 11+)
- Gradle wrapper: `./gradlew --version`
- Android SDK: `echo $ANDROID_HOME`

### Issue: iOS build fails
**Check:**
- Xcode version: `xcodebuild -version`
- Scheme exists: `xcodebuild -list -workspace DailyNotificationPlugin.xcworkspace`
- Simulator available: `xcrun simctl list devices`

### Issue: TODO scan shows core TODOs
**Action:**
1. Run: `npm run todo:scan`
2. Check `docs/todo-scan.json` for `coreCount`
3. If > 0, grep for TODOs in core directories
4. Resolve or move to docs/test-apps

### Issue: Logs not appearing
**Check:**
- Android: `adb logcat -c` (clear) then `adb logcat | grep DNP`
- iOS: Xcode console or device logs
- Verify log tags match expected patterns

---

**Last Updated:** 2025-12-24  
**Status:** Active production readiness checklist

