# 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 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