Files
daily-notification-plugin/docs/progress/PRODUCTION-READINESS-RUNBOOK.md
Matthew Raymer cdbe51f46a feat(docs): add production readiness runbook and enhanced TODO scan
Add comprehensive production readiness checklist and improve TODO scanning.

Changes:
- Production Readiness Runbook (docs/progress/PRODUCTION-READINESS-RUNBOOK.md)
  - Complete mechanical execution checklist for TypeScript, Android, iOS
  - File anchors and search commands for verification
  - Cross-platform behavior consistency checks
  - Logging and observability requirements
  - Release packaging sanity checks
  - Troubleshooting guide
  - Quick reference for expected file anchors
- Enhanced TODO Scan Script (scripts/todo-scan.js)
  - Split reporting: core code vs docs/test-apps
  - Core code count (should be 0)
  - Docs/test-apps count (expected to be large)
  - Enhanced JSON output with summary statistics
  - Improved console output with visual indicators
  - Clear separation of production code vs planning artifacts

Implementation Details:
- Core code detection: ios/Plugin/, android/src/main/, src/, packages/, lib/
- Docs/test-apps detection: docs/, test-apps/, tests/, *Tests/
- JSON output includes summary with coreCount, docsTestCount, otherCount
- Markdown output includes summary section with split counts
- Console output shows visual indicators (/⚠️) for quick assessment

Benefits:
- Clear visibility into production code TODOs (should be 0)
- Acceptable TODOs in docs/test-apps are clearly separated
- Production readiness checklist provides deterministic verification
- File anchors enable quick verification of implementation completeness

Verification:
- TODO scan runs successfully
- JSON output includes summary statistics
- Markdown output includes split summary
- Console output shows visual indicators
2025-12-24 08:19:48 +00:00

14 KiB

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

pwd
ls

Expected to include folders like:

  • src/
  • android/
  • ios/
  • docs/
  • scripts/

0.2 Capture current revision (for receipts)

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

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

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

npm test

Pass condition:

  • exits 0.

Expected output:

Test Suites: 8 passed, 8 total
Tests:       115 passed, 115 total

2.2 Typecheck

npm run typecheck

Pass condition:

  • exits 0.

Expected output:

> @timesafari/daily-notification-plugin@1.0.11 typecheck
> tsc --noEmit

2.3 Lint (if present)

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

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:

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:

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:

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:

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

ls ios | grep -E "xcworkspace|xcodeproj" || true

Pass condition:

  • you see DailyNotificationPlugin.xcworkspace (or equivalent).

4.2 iOS build/test sanity

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):

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:

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:

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:

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:

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:

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:

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:

# 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

ls -l scripts/todo-scan.js

Pass condition:

  • executable bit set OR npm script runs regardless.

7.2 Clean archive recipe (no junk)

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:

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