Push Gateway Real-Device Test Runbook (SC#1 / D-20)
This runbook documents the manual real-device test procedure for the Phase 29
opaque-wake → tailnet-fetch → ack loop. It corresponds to the automated scaffold
in internal/push/integration_test.go.
Purpose
The integration test (SC#1, D-20) validates the full end-to-end wake path on real de-Googled Android hardware with the screen off and Doze active. This cannot be automated in CI because it requires a physical phone, a running tailnet, and a human observer to confirm screen wake and notification rendering.
Prerequisites
Before running the test, ensure all of the following are true:
- De-Googled Android device with ntfy app installed and UnifiedPush enabled (ntfy → Settings → Enable UnifiedPush).
- Device enrolled in abysslink:
abysslink enroll phone --applycompleted successfully and the device appears inabysslink device list. - abysslinkd running on the rig:
abysslink daemon enable --apply(confirm withabysslink status— daemon counter fields must be present). - Tailnet connection established: rig and phone are on the same tailnet
(
tailscale statusshows both as active peers). - Phone screen off before running the test (locks the device after enrolling).
- Content store healthy:
abysslink doctormust report no FATAL in thepush-gatewayorcontent-storegroups. - ntfy endpoint URL available: retrieve the enrolled device's endpoint from
abysslink device list(thepush_token/ ntfy endpoint field).
Environment Setup
# Required: the ntfy endpoint URL the phone is subscribed to.
# Obtain from: abysslink device list --json | jq -r '.[] | select(.name=="phone") | .push_token'
export PUSH_TEST_NTFY_ENDPOINT="https://ntfy.example.com/your-topic"
# Required: the enrolled device ULID.
# Obtain from: abysslink device list --json | jq -r '.[] | select(.name=="phone") | .id'
export PUSH_TEST_DEVICE_ID="01J2A8X3Q0B1ABCDEFGH"
# Optional: override daemon socket path (defaults to daemon.SocketPath() — XDG_RUNTIME_DIR or /tmp/abysslink).
# export PUSH_TEST_DAEMON_SOCKET="/run/user/1000/abysslink/abysslinkd.sock"
Security note:
PUSH_TEST_NTFY_ENDPOINTis a secret-class value (D-17). Do not echo it to logs, commit it to git, or include it in CI environment variables. Use a secrets manager or pass it interactively for ad-hoc runs.
Test Invocation
The -tags integration flag is required; without it the file is excluded from the
default make test run and does not compile.
Expected Outcome
Automated portion (asserted by the test)
| Counter | Expected |
|---|---|
gateway_queued |
Increments by ≥ 1 within 30 seconds of POST /notify |
gateway_sent |
Increments by ≥ 1 (may take one retry loop cycle, ~5 s) |
Manual portion (human observer required)
- Phone screen wakes within 60 seconds of the push dispatch (Doze FCM/UnifiedPush high-priority wake signal).
- Notification visible on the lock screen showing a safe generic title (no content body — opaque-wake invariant).
- Tapping the notification opens the deep link and the phone fetches the
actual content from the tailnet content store over HTTPS (
https://<rig-ts.net>:2587/content/<token>). abysslink statusshowsack_receivedincremented after the phone fetches content.
Doze Test Procedure
To confirm the wake path works under Android's battery-optimization Doze mode:
- Connect the test phone via USB (ADB access required).
- Force the device into Doze before running the test:
- Lock the phone (screen off) and wait 5 seconds.
- Run the integration test:
- Observe the phone — it must wake within 60 seconds of the push dispatch.
- If using UnifiedPush/ntfy: ntfy uses Firebase or UnifiedPush high-priority wake signal which is exempt from Doze (maintenance window not required).
- If using APNs (experimental): high-priority alert push is Doze-exempt.
- Exit Doze after the test:
Failure Modes and Diagnostics
| Symptom | Likely cause | Diagnostic |
|---|---|---|
GET /status fails |
abysslinkd not running | abysslink daemon enable --apply |
gateway_queued does not increment |
Outbox not wired | check wirePushOutbox in main.go logs |
gateway_sent stays 0 |
No UnifiedPush gateway registered | check wirePushOutbox logs; UnifiedPushGateway must be in gateways map |
| Phone does not wake | ntfy app not subscribed to endpoint | verify ntfy → subscriptions; re-enroll |
| Phone wakes but no content | Content store not healthy | abysslink doctor → push-gateway group |
| Doze: phone wakes after >60s | ntfy not using high-priority wake | update ntfy app; check server X-Priority: urgent header |
Phase Summary Recording
After running this runbook, record the outcome in
.planning/phases/29-push-gateway-outbox/29-05-SUMMARY.md under a
Real-Device Test Results section:
## Real-Device Test Results (SC#1 / D-20)
- **Date:** YYYY-MM-DD
- **Device model:** <e.g. Google Pixel 7a running GrapheneOS>
- **Android version:** <e.g. Android 14 / GrapheneOS 2024.xx.xx>
- **ntfy version:** <e.g. ntfy 2.11.0 (F-Droid build)>
- **Doze behavior:** <e.g. Phone woke within 12 seconds under forced Doze>
- **gateway_queued increment:** <yes/no + delta>
- **gateway_sent increment:** <yes/no + delta>
- **ack_received increment:** <yes/no + delta>
- **Outcome:** PASS / FAIL / PARTIAL
- **Notes:** <any deviations or observations>
This runbook must be re-executed before Phase 33 (Distribution & Public Launch) to confirm the full wake path works on real hardware prior to the Show HN launch gate (LNCH-05).