Skip to content

Astradial Phone (iOS) — Agile Delivery Plan & UAT Test Cases

Living document. Owns the road from feature-complete to App Store launch, following: Plan → Gather requirements → Develop → UAT (per feature) → Staging test (entire app) → Launch.

Last updated: 2026-07-02. Per-build regression checklist (run every build): test-plan.md. Status board / continuation notes: status.md. Open decisions: decisions.md.

Legend: ✅ pass · ❌ fail · ⬜ not run · 🚫 blocked · P1 = launch-blocker, P2 = should-fix, P3 = nice-to-have.


Phase 1 — Plan

Item Value
Scope iOS softphone (branded linphone-iphone) for Astradial cloud PBX users
Environments Staging: stagesip.astradial.com:5080 / API stagepbx.astradial.com · Prod: devsip.astradial.com:5060 / API devpbx.astradial.com (app default base)
Builds Debug → APNs sandbox, token env dev · Release/TestFlight → APNs production, token env prod
Test devices iPhone 12 mini (primary, in hand) · one additional device ≥ iOS 17 before launch
Roles Claude: sim-testable flows + build gate, log analysis, fixes · Hari: all 🅓 device-only rows (push, CallKit, audio)
Exit criteria (whole plan) All P1 UAT cases ✅ on staging AND prod, zero open P1 defects, Phase 5 regression ✅, Launch checklist ✅

Phase gate: this table agreed → requirements below frozen for v1.

Phase 2 — Gather requirements

Feature inventory = requirement list. Acceptance criterion for each = its UAT section passing.

# Requirement UAT section v1?
R1 Sign in (Firebase → org JWT), SIP line attach, logout AUTH
R2 SIP provisioning: QR, manual (host:port), SIP-user dropdown PROV
R3 Outbound calls with full in-call controls OUT
R4 Inbound calls: foreground, background, killed (VoIP push wake), locked INB / WAKE
R5 CallKit correctness: answer/decline/end from every surface, native call UI CK
R6 Audio: earpiece/speaker/bluetooth, mute, DTMF, hold, proximity AUD
R7 Resilience: network flap, re-registration, offline UX NET
R8 Call history (platform CDR fetch) HIST
R9 Contacts (org directory) CONT
R10 Platform integration: VoIP token registration, analytics tiles, tickets PLAT
R11 Settings, dark mode, Help MISC
R12 Conversations (chat), Meetings, Recordings tabs MISC ⬜ decide — hide or ship (see decisions.md)

Phase gate: every v1=✅ row maps to UAT cases below; R12 decision made before Phase 5.

Phase 3 — Develop

Definition of done per feature: code merged to app repo master + xcodebuild green + covered UAT rows runnable. Current engineering state (2026-07-02):

Work item State
VoIP push wake — app side (refreshRegisters on empty call-id, wake keepalive, placeholder re-keying, answer-before-INVITE) ✅ built, on device; uncommitted in working tree
VoIP push wake — server side (APNs sender, /asterisk/voip-push-wait, dialplan CURL) ✅ merged to main; live staging + prod (prod env armed 2026-06-30, .env.bak-20260630-233937)
Firebase early-config race fix ✅ built, on device; uncommitted; verify in next capture
SDK log level ⚠ temporarily .Message for debugging — revert to .Warning before release build
Hangup from iPhone doesn't end far side (DEF-001) ❌ open — next dev item
Prod wake token seeding + regen 🚫 blocked on prod endpoint + device token (option B agreed)
Known launch bugs from test-plan.md (first-launch splash hang, first-call hang) ❌ open, P1

Phase gate: working tree committed, DEF-001 fixed, P1 bugs fixed → enter Phase 4 formally.

Phase 4 — UAT, per feature (device unless marked 🅢)

Run on staging/Debug first; repeat the P1 rows on prod/Release before launch. Record date + build in the Status column.

AUTH — Sign-in & accounts

ID P Steps Expected Status
AUTH-01 P1 Fresh install → sign in with valid Firebase account Org JWT obtained; MD tabs (History/Tickets/Analytics) populate
AUTH-02 P1 Attach SIP user from dropdown Registers within ~5 s; status chip shows user
AUTH-03 P1 Log out SIP line cleared (unREGISTER), chip gone, no stale org data
AUTH-04 P2 Wrong password Clear error, no hang
AUTH-05 P2 Skip login Dialer usable; gated tabs show sign-in prompt
AUTH-06 P2 Multi-account switch Old line cleared, new org's SIP line restored
AUTH-07 P2 Sign Out Full reset: Firebase + saved accounts (Keychain) + all prefs/caches wiped; app returns to first onboarding screen
AUTH-08 P2 Settings → Startup Page = Keypad/Tickets/Analytics, relaunch App opens on the chosen tab; persists across launches; role default when unset (owner→Analytics, agent→Tickets)

PROV — SIP provisioning 🅢 (registration result on device)

ID P Steps Expected Status
PROV-01 P1 QR scan from editor Account created + registers
PROV-02 P1 Manual entry with custom host:port (staging :5080) Registers
PROV-03 P2 Quick-connect list contents Every user with sip_port='wss' + an asterisk_endpoint, regardless of routing; AI agents (sip_port='5080') hidden
PROV-04 P1 Switch SIP user Old contact removed from Asterisk (pjsip show contacts), new registers
PROV-05 P1 Set a wss user's routing to phone number, reopen Quick connect User still appears and Proceed registers (routing ≠ visibility gate)
PROV-06 P2 Settings → Connect Line → Choose → pick user → Proceed Pushes the connect screen; pops back to Settings once registered
PROV-07 P3 Tap Proceed Button does not resize or shift text (opacity-only press)

OUT — Outbound calls

ID P Steps Expected Status
OUT-01 P1 First call immediately after cold launch Connects + 2-way audio (known-bug row) ❌ open bug
OUT-02 P1 Subsequent call Connects + 2-way audio
OUT-03 P1 Hang up from app Far side ends immediately (BYE sent)
OUT-04 P1 Far side hangs up App call ends, UI returns cleanly
OUT-05 P2 Call invalid/unroutable number Proper error tone/message, no stuck screen
OUT-06 P2 Duration timer Counts up, not stuck at 0:00
OUT-07 P2 Outbound caller-ID Far side sees the org's configured outbound DID

INB — Inbound, app foreground / background

ID P Steps Expected Status
INB-01 P1 App open, call arrives CallKit rings; answer → 2-way audio
INB-02 P1 Decline Caller leg follows org failover (voicemail/announce), not dead air
INB-03 P1 App backgrounded (not killed), call arrives Full-screen CallKit; answer works
INB-04 P1 Answer from lock screen (backgrounded) Audio connects
INB-05 P2 Second inbound during active call (call waiting) CallKit offers hold&accept / decline; both behave

WAKE — Inbound, app KILLED (VoIP push wake) — headline feature

Staging validation 2026-06-30, iPhone 12 mini, Debug/sandbox: WAKE-01..04 ✅ (log d1.rtf: push → REGISTER 200 ~1 s → INVITE ~3 s → StreamsRunning).

ID P Steps Expected Status
WAKE-01 P1 Swipe-kill app → call the extension Push wakes app; CallKit rings in ≤ ~5 s ✅ stg 06-30
WAKE-02 P1 Answer after a moment of ringing Connects, 2-way audio ✅ stg 06-30
WAKE-03 P1 Answer from locked screen Connects, 2-way audio ✅ stg 06-30
WAKE-04 P1 Caller hangs up before answer Ring stops, call screen dismisses cleanly ✅ stg 06-30
WAKE-05 P1 Hang up from iPhone after answering a wake call Far side ends immediately (BYE) ✅ our stack (07-03, packet-proven); far-side stuck UI = third-party client bug (Error 83)
WAKE-06 P1 Answer instantly (before INVITE lands) Deferred-accept connects it; no dead call ✅ stg 07-03 (phantom-kill fix)
WAKE-07 P1 Don't answer at all Placeholder ends ≤ 30 s; no stuck CallKit call; caller hits failover
WAKE-08 P2 Kill app, phone on WiFi off / LTE only Wake still works over cellular
WAKE-13 P1 Phone fully offline (airplane) → call Failover to phone number in ~5-6s (wake v2: no ack → no) ✅ stg 07-03
WAKE-14 P1 After WAKE-13, internet returns No fake call (apns-expiration:0 drops undelivered pushes) ✅ stg 07-03
WAKE-09 P2 After device reboot + first unlock Wake works (iOS requires one unlock)
WAKE-10 P1 Prod repeat of WAKE-01..05 (Release build, prod APNs, prod extension) Same as staging 🚫 token seed pending
WAKE-11 P2 App already foreground when push would fire No duplicate ring (gateway skips push when registered)
WAKE-12 P3 Two devices, same user Sane behaviour: both ring or documented single-device rule

CK — CallKit correctness

ID P Steps Expected Status
CK-01 P1 Native call UI shows caller name/number for inbound Real caller-ID, updates from placeholder on wake calls
CK-02 P1 End call from native (lock-screen) UI vs in-app red button Both end both legs — log [Hangup]/CXEndCallAction ⬜ (DEF-001 related)
CK-03 P2 Native phone call arrives during Astradial call Hold/interleave sanely; our call survives or ends cleanly
CK-04 P2 Recents: Astradial calls in iOS call log Present with correct handle (or documented off)

AUD — Audio & in-call controls

ID P Steps Expected Status
AUD-01 P1 Mute / unmute Far side stops/starts hearing you
AUD-02 P1 Speaker toggle Route changes; survives screen lock
AUD-03 P1 DTMF in IVR Digits register (test against org IVR)
AUD-04 P2 Hold / resume Far side hears silence/MOH; resume restores audio
AUD-05 P2 Bluetooth headset connect mid-call Route follows; disconnect falls back
AUD-06 P2 Proximity sensor Screen off at ear, on when away

NET — Network resilience

ID P Steps Expected Status
NET-01 P1 WiFi → LTE mid-call Call survives (or reconnects) within a few s
NET-02 P1 Airplane mode 10 s mid-call → off Recovers or ends cleanly; app re-registers after
NET-03 P2 Launch offline UI reachable, offline state shown, no splash hang
NET-04 P2 Registration expiry / stale NAT App re-registers without user action; inbound still works after 1 h idle

HIST / CONT / PLAT / MISC — App surfaces 🅢 (data needs device+org)

ID P Steps Expected Status
HIST-01 P1 History tab after test calls CDRs appear (dated fetch; self-heal path works)
HIST-02 P2 Tap history entry → call back Dials correct number
CONT-01 P2 Contacts tab Org directory listed, no device-contacts permission prompt
CONT-02 P2 Call a contact Dials
PLAT-01 P1 Fresh install + SIP attach posts VoIP token Row in platform DB with right env (dev/prod); HTTP 200 in log ✅ stg 06-29
PLAT-02 P2 Analytics tiles load Numbers match editor
PLAT-03 P2 Tickets: badge, open, change status Syncs with platform
MISC-01 P2 Dark mode across all tabs No unreadable/white-flash screens
MISC-02 P2 Settings toggles persist across relaunch Persisted
MISC-03 P3 Help screens open No dead links
MISC-04 P1 Launch checks (A1–A6 of test-plan.md) Per that doc — includes the two open P1 launch bugs ❌ 2 open

Phase gate: all P1 rows ✅ on staging/Debug → Phase 5. (Prod/Release P1 repeats may run in parallel with Phase 5 once tokens are seeded.)

Phase 5 — Staging test, entire app (regression + soak)

Full-pass, one sitting, on a staging org that mirrors prod (queues, IVR, failover, recording):

ID Scenario Status
REG-01 Entire test-plan.md release gate (A–H) in order
REG-02 Full P1 UAT sweep above, same build, no code changes between
REG-03 Cross-feature: receive wake call → while in-call browse History/Tickets → hang up → history updates
REG-04 Queue member scenario: extension in a queue, killed app — queue call wakes it; agent pause/unpause via *5 if applicable
REG-05 24 h soak: app killed overnight, first morning call wakes it (token + registration longevity)
REG-06 Battery: killed between calls, no abnormal drain overnight (iOS battery screen)
REG-07 Two-user test: two devices, A calls B both directions, hangups both directions clean
REG-08 Recording-enabled call: indicator behaviour + recording lands platform-side

Phase gate: REG-01..08 ✅, zero P1 defects open, build ID frozen → Launch.

Phase 6 — Launch

# Gate Status
L-00 GPLv3 licensing resolved — app is a linphone-iphone (GPLv3) fork; GPL and App Store terms are legally incompatible (FSF position; why Belledonne sells commercial SDK licenses). Options: buy Belledonne commercial license, or accept the legal risk knowingly. Tracked in decisions.md. Blocks submission.
L-01 SDK log level reverted to .Warning; debug ★★★ VOIP_TOKEN line removed/DEBUG-only confirmed
L-02 All working-tree changes committed + pushed (app repo)
L-03 Release build (prod APNs entitlement production, API base devpbx.astradial.com) archives + TestFlight upload
L-04 Prod E2E: WAKE-01..05 on TestFlight build against prod extension (real token self-seed → regen run → test). Note: token POST does not auto-regen the dialplan — run regen-all-org-configs.js or ship the auto-regen fix first
L-05 Rollback plan written: prod .env backup exists (.env.bak-20260630-233937); pulling APNS_*/INTERNAL_API_URL + regen reverts wake feature server-side; app rollback = previous TestFlight build
L-06 App Store assets: screenshots, privacy manifest/labels (mic, contacts-none, push), review notes incl. demo SIP account for Apple review
L-07 Post-launch monitoring: APNs error alerting (push-4xx reasons in api logs), Crashlytics watch for first 48 h
L-09 PrivacyInfo.xcprivacy — none exists in the repo (audit 07-03); required by Apple since May 2024 (UserDefaults/File-timestamp API declarations + data-collection categories for Firebase/Crashlytics). Rejection risk without it.
L-10 ITSAppUsesNonExemptEncryption=false in Info.plist (standard-crypto exemption) — missing today, App Store Connect will block/ask every upload
L-11 Verify mic permission string reaches the built Info.plist (not in source Info.plist — likely INFOPLIST_KEY_ build setting; confirm on archive) + review remaining org.linphone.phone URL-scheme/category ids (cosmetic, check QR flow before renaming)
L-12 Real GoogleService-Info.plist in the archive (repo copies were replaced with dummies in 4ff9f2013; root-level copy present — verify it's the real prod one) + Crashlytics dSYM upload
L-13 Review-features pass: R12 decision (hide unfinished Conversations/Meetings/Recordings tabs — reviewers reject empty/broken surfaces), DEF-006 Quick Connect empty-list fixed (reviewer's first login experience), demo SIP account + review notes for Apple
L-08 Docs: update status.md + this file to "launched vX.Y"; troubleshooting entries for any defect found in UAT

Defect log

ID Sev Description Found Status
DEF-001 P1 iPhone hangup doesn't end far side 2026-06-30 UAT Closed 07-03: our stack packet-proven correct; far-side client (Telephone 1.6) ignores BYE — troubleshooting Error 83
DEF-002 P2 Firebase "default app not configured" race on push-launch 2026-06-30 log d1 Verified fixed 07-03 (0 occurrences in R5 capture)
DEF-003 P1 First-launch 10-min splash hang (device-only) test-plan.md Open
DEF-004 P1 First outbound call after launch hangs; second works test-plan.md Open
DEF-005 P2 Platform: token POST didn't trigger dialplan regen 2026-06-30 review Fixed PR #494 (regen on token-presence flip)
DEF-007 P1 Background-answer: in-call UI ~10s late (liblinphone phantom pushkit session blocked accept) 2026-07-03 R5 Fixed + verified 07-03 (phantom-kill on IncomingReceived, app 06d4044a8)
DEF-008 P2 "Wrong username or password" toast on every app open (PUBLISH 489 + unscoped toast) 2026-07-03 Fixed (publish off + toast scoped to interactive logins) — verify on next install

Traceability

R1→AUTH · R2→PROV · R3→OUT · R4→INB+WAKE · R5→CK · R6→AUD · R7→NET · R8→HIST · R9→CONT · R10→PLAT · R11/R12→MISC. Launch = Phase 6 only.