Hardware Smoke Runbook (Connected-Device Execution Plan)
Owner: Android/Terminal QA · Created: 2026-06-21 · Status: active execution
This is the single comprehensive plan for proving PeakPOS on real hardware. It consolidates:
- the live device bench (three Android devices connected via ADB on 2026-06-21),
- the peripheral capability matrix derived from
DevicePlatformFactoryand the per-platform overrides inapps/android/app/src/main/java/com/myriad/pinpointpos/device/, - every Current Changes Tracker item marked "needs testing" (Monday board
18417721170: Status = Needs manual test or Manual Test = Required / Blocked), - the open hardware-relevant GitHub issues.
It supersedes the "no device connected" status recorded in
checklists/android-pos.md (2026-06-07 ADB inventory).
For exhaustive per-surface negative-case matrices (mTLS denial, idempotency
boundaries, scope checks) this runbook links to the existing checklists rather
than duplicating them.
Record per-case outcomes in the Execution log (section 9); the case entries
below describe what to prove, not a checkbox state. Monday items are referenced as
M:<itemId> → https://myriad-peak.monday.com/boards/18417721170/pulses/<itemId>.
1. Device bench inventory (verified 2026-06-21)
| # | Device | OS | Serial (transport) | Auto-detected platform | Current build | Notes |
|---|---|---|---|---|---|---|
| D1 | LANDI C20Pro | Android 13 (SDK 33) | 2589CCM07231 wireless (t4; dup t5) | LandiDevicePlatform (OmniDriver com.sdksuite.omnidriver) | 0.0.19 / vc 2750 (release) | Device-admin → adb uninstall fails; no .test pkg; VP3350 card reader; customer-display flicker is a known vendor com.ms.msdisplay crash |
| D2 | Nexgo N92 | Android 13 (SDK 33) | N92 USB (t9; wireless dup t10) | NexgoDevicePlatform (Build.MANUFACTURER == "Nexgo") | 0.0.0-dev / vc 1 (DEBUG) | .test pkg present; Nexgo-specific App Check path; no cash-drawer capability exposed |
| D3 | Samsung Galaxy Tab A9+ (SM-X230) | Android 16 (SDK 36) | R5GL32DXJ2L USB (t6) | GenericDevicePlatform (fallback) | 0.0.0-dev / vc 1 (DEBUG) | .test pkg present; no built-in peripherals — external BT/USB only; best big-screen customer-display surface |
1.1 Capability matrix (from source)
Source of truth: DevicePlatform overrides per platform class.
A capability is only usable in-app when the platform's has<X>() returns true —
MainActivity gates each peripheral on it. The table reflects in-app behavior on
current main, not just whether a <X>() object can be constructed.
| Capability | D1 C20Pro (LANDI) | D2 N92 (Nexgo) | D3 Tab A9+ (Generic) |
|---|---|---|---|
| Card reader (card-present) | yes — LandiCardReader (VP3350) | yes — NexgoCardReader | no — external only |
| Receipt printer | yes — Landi 58 mm built-in | yes — Nexgo thermal | no — BT/USB external |
| Label printer | external Zebra (BT) | built-in text-only — NexgoLabelPrinter strips ZPL (NexgoZplUtils); barcodes are not scannable → use external Zebra | no — BT external |
| Cash drawer | yes — LandiCashDrawer (wired via #1770) | no — platform exposes no cashDrawer() | no — platform exposes no cashDrawer() |
| Barcode scanner | no (in-app) — hasBarcodeScanner() is false, so the wedge is never subscribed | yes — NexgoBarcodeScanner | no (in-app) — Generic exposes no scanner |
| Beeper / LED | yes | no | no |
In-app capability gates (current
main): barcode scanning works in-app only on D2 —LandiDevicePlatform.hasBarcodeScanner()is hardcodedfalseandGenericDevicePlatformexposes no scanner, so D1/D3 never subscribe the keyboard wedge (the same gate pattern that #1770 fixed for the LANDI cash drawer; the scanner gate is still open). Cash drawer works in-app only on D1 (hasCashDrawer()true since #1770); D2/D3 expose nocashDrawer(). HW-20 / HW-40 device scope follows these gates.
1.2 ADB targeting cheat sheet
Transport IDs drift across reconnects — always pin by serial. Duplicate
connections (D2 USB+wireless, D1 two TLS) make bare adb install ambiguous.
D1=2589CCM07231 # LANDI C20Pro (wireless; use the primary TLS entry)
D2=N92 # Nexgo N92 (USB preferred)
D3=R5GL32DXJ2L # Samsung Tab A9+ (USB)
adb devices -l # re-derive transports each session
adb disconnect adb-0123456789ABCDEF-IEKY4S._adb-tls-connect._tcp # drop redundant N92 wireless link
adb -s "$D1" shell ... # target a device explicitly
2. Pre-flight (once per session, before any test case)
Two distinct artifacts — do not conflate them:
- Release sign-off APK = the explicitly signed output produced from
//apps/android:android_app_prod_unsigned.apk(R8/ProGuard, release resources,prod.keystore).prod.keystoreis written from Secret Manager and is CI-only (android.yml); it is absent on this bench, so the release-signed APK cannot be produced locally. The release/ProGuard sign-off is a CI-gated artifact.- Local functional build =
//apps/android:android_app_local_staging(dev.keystore, debuggable,0.0.0-dev). This is what installs on the bench for functional cases — it is not the ProGuard/release build, so it does not cover R8-shrink-specific behavior.Match every claim to the artifact under test.
- PF-1 Clean ADB. Run
adb devices -l; drop the redundant N92 wireless link (cheat sheet above). Confirm exactly one usable target per physical device. - PF-2 Build. Local functional (debug) build for the bench:
bazel build //apps/android:android_app_local_staging(ANDROID_NDK_HOME/ANDROID_HOMEset). The true release APK//apps/android:android_app_prod_unsigned.apkplus explicit production signing is the R8/ProGuard check and is CI/keystore-gated — do not claim ProGuard sign-off from the debug build. Record APK path + SHA + which artifact it is. - PF-3 Install. The debug build (
dev.keystore) installs in place:adb -s "$D2" install -r -d <apk>·adb -s "$D3" install -r -d <apk>. If you instead deploy the CI release APK (prod.keystore), its signature differs from any installeddev.keystorebuild, so-ris rejected —adb -s "$Dn" uninstall com.myriad.pinpointposfirst (clears app data). D1 is device-admin (uninstall blocked) and runs a release build today: keep D1 on its release APK, or remove device-admin in Settings before replacing it — neveradb uninstallD1. - PF-4 App Check + staging. The App Check provider depends on the build. The debug build (or
enable_debug_app_check=true) uses the debug provider → register each device's App Check debug token. The explicitly production-signed release build sets non-debug App Check: D1/D3 use Play Integrity, D2 uses the Nexgo HMAC path — debug tokens do not apply there. Match the token/provider to the build under test and point the app at staging; a provider mismatch can masquerade as a credential block. - PF-5 Automated device smokes (debug build).
bazel test //apps/android/app/src/androidTest:android_app_launch_smoke_testandbazel test //apps/android/app/src/androidTest:gateway_sdk_activation_cache_device_smoke_testboth depend on//apps/android:android_app_local_stagingand installbazel-bin/apps/android/android_app_local_staging.apk— i.e. they exercise the debug build (not the release APK) and will reinstall it (redeploy your release APK afterward if you had one). On a multi-device bench both fail with "multiple devices" unless you setANDROID_SERIAL=<serial>— run one invocation per D1/D2/D3. D1 also needs the.testAPK (samedev.keystore) for the activation-cache smoke. Acache_hitproves encrypted persistence; a live-activation timeout is a device dependency, not a persistence failure.
3. On-bench test catalog (Bucket A — provable on D1/D2/D3 now)
Each case lists priority · target device(s) · Monday/GH refs · steps · expected. Capture per-device pass/fail + evidence in the section 9 execution log.
Session & lifecycle
HW-01 — 30-second background / relogin grace · P0 · D1·D2·D3 · M:12273180051, M:12273262760 · PR #1756
- Steps: Sign in; background the app < 30 s → foreground; then background ≥ 30 s → foreground.
- Expected: < 30 s resumes with no prompt; ≥ 30 s shows PIN-only lock overlay; Firebase auth + POS state preserved behind overlay; any operator PIN unlocks.
HW-02 — Launch, login, PIN, shift baseline · P0 · D1·D2·D3
- Steps: Cold launch → terminal selection → operator login → PIN unlock → start shift.
- Expected: Reaches sale screen; shift open recorded server-side. See
android-pos.mdfor negative cases.
HW-03 — Catalog refresh without app restart · P1 · D1·D2 · M:12327661562 · PR #1739
- Steps: Edit a product/price backend-side; return to the POS sale screen.
- Expected: Change appears without restarting the app.
Payments
HW-10 — Card-present sale · P0 · D1 (LandiCardReader/VP3350)·D2 (NexgoCardReader)
- Steps: Ring a sale → tender Card. Run tap, chip, swipe. Exercise approve, decline, and cancel mid-read.
- Expected: Reader prompts; approval prints/records; decline + cancel handled cleanly; transaction lands in backend. (D3 has no built-in reader — card tender only with an external reader.)
HW-11 — Refunds & returns manager approval matrix · P0 · D1 · M:12273284367, M:12273262586
- Steps: Refund a prior sale via card-present, cash, and split tenders; trigger manager approval.
- Expected: Each refund path requires approval, updates server state, links to the original
transaction. Cross-references terminal refund idempotency in
android-pos.md.
HW-12 — EBT acceptance decision · P0 (decision) · D1 (EBT-capable) · GH #1766
- Steps: Determine whether Gateway handles EBT on this terminal; if enabled, run a balance
inquiry + EBT tender. See
android-pos.md. - Expected: Documented answer (in-scope? how?) before any EBT support claim.
HW-13 — Offline queue + recovery (no double-charge) · P0 · D1·D2·D3
- Steps: Sale with network off → queue → restore network → confirm replay; kill app during a pending payment and recover.
- Expected: Exactly-once replay, no duplicate charge; idempotency per
android-pos.md.
Cash drawer
HW-20 — Cash drawer kick · P0 · D1 only (in-app) · M:12273262559, M:12273284344 · GH #1760
- Scope: in-app, only D1 has a cash drawer (
LandiCashDrawer, wired via #1770). D2/D3 expose nocashDrawer()— the drawer is not reachable from the app on those platforms, so mark HW-20 N/A for D2/D3 unless a separate low-level ESC/POS path is added. - Pre-flight (D1): confirm the APK contains PR #1770 or later; record APK variant, version, SHA, branch/SHA, tester, timestamp, and environment. Confirm the physical drawer is connected to the LANDI-compatible drawer port or printer/drawer harness used by the site.
- Evidence capture: clear existing logcat with
adb -s "$D1" logcat -c, start a pinned LANDI logcat capture before testing, and save the output aslandi-cash-drawer-hw20.log. IncludeLandiCashDrawer,LandiDevicePlatform,OmniDriver, andCashBoxlines when triaging failures. - Steps (D1):
- Open Settings, Hardware or connected-device diagnostics, Cash Drawer, then tap the drawer test/open action and observe the physical drawer.
- Complete a small test sale tendered as Cash and observe the drawer at sale completion.
- If a no-sale/drawer-open action is available outside Settings, run it once and observe the physical drawer.
- Expected: drawer kicks open via OmniDriver
getCashBox().openBox()on each trigger. The UI surfaces a clear failure if OmniDriver or the drawer path fails. The code gate (#1760) is fixed onmain(#1770); this case is the physical confirmation required before closing #1760. - Failure triage: confirm the device is a LANDI C20Pro, not a Nexgo, generic tablet, or emulator; confirm the drawer/cable/harness opens with a known-good vendor/device test if available; record whether the app hid the cash drawer control, showed the control but failed, or reported success while the drawer stayed closed. Keep #1760 open until the physical drawer opens on-device or a new code/hardware finding explains the failure.
Printers
HW-30 — Receipt print + reprint · P0 · D1 (Landi 58 mm)·D2 (Nexgo thermal)
- Steps: Complete a sale → print receipt; reprint from Transaction Detail.
- Expected: 32-col / 58 mm layout intact, no truncation, barcode prints.
HW-31 — Label printer hardware proof · P2 · external Zebra (BT) for scannable; D2 built-in = text-only · M:12273162897, M:12273180162
- Scope note: D2's built-in printer goes through
NexgoLabelPrinter, which strips ZPL to plain text (NexgoZplUtils) — a ZPL barcode will not print a scannable barcode there. Prove the scannable-barcode path on an external Zebra (BT); use D2's built-in only to confirm the text-only fallback renders product/price/date. - Steps: pair/connect printer; print product/shelf labels; scan the printed barcode (external Zebra).
- Expected: scannable barcode on external Zebra; correct product/price/date formatting; offline errors surfaced; no silent fall-through to the receipt printer. D2 built-in: text-only, not scannable.
Barcode scanner & scan-data
HW-40 — Barcode scan into cart · P0 · D2 only (in-app) · D1/D3 blocked by scanner gate
- Scope:
MainActivitysubscribes the scanner only whenhasBarcodeScanner()is true — that is only D2 (NexgoBarcodeScanner). D1 (hasBarcodeScanner()hardcodedfalse) and D3 (Generic, no scanner) never subscribe the wedge, so scans don't reach the cart. Mark D1/D3 blocked on the in-app scanner gate (an open gap analogous to the #1770 cash-drawer fix — flag for a follow-up). - Steps (D2): scan UPCs into cart with the built-in scanner.
- Expected: correct item added; multi-scan and unknown-UPC handled.
HW-41 — PO scan import via AI · P1 · D2 (scanner) + AI · GH #1763 (fixed by #1776)
- Steps: scan a purchase order; confirm it routes to an AI reorder draft with the right products/quantities.
- Expected: import succeeds. Code landed on
mainvia #1776; this case is the on-device confirmation.
Customer-facing display
HW-50 — Advertisements idle/between-transaction rotation · P0 · D1·D3 · M:12273284372
- Steps: Configure the staged ad through the retail checklist, idle the terminal, and complete sales; observe ad rotation + impression capture.
- Expected: Ads rotate on idle and between transactions; impressions recorded. (D1 flicker =
known vendor
com.ms.msdisplayissue, not a regression.)
HW-51 — Loyalty entry on display + cross-store balance · P1 · D1 (2-store) · M:12273178045, M:12273288780 · PR #1740/#1721
- Steps: Customer enters phone/email on the customer-facing display; check loyalty balance reflects cross-store activity across two stores.
- Expected: Entry captured; per-store + shared balance correct.
HW-52 — Store type + merchant logo on idle · P2 · D1·D3 · M:12273172511, M:12273161853 · PR #1728
- Steps: Configure store type + merchant logo; observe requested display locations; test unknown/unset store type and missing/corrupt/slow logo.
- Expected: Correct display; Peak logo fallback on missing/failed image.
HW-53 — Customer review screen · P1 · D1·D3 · M:12273153564 · PR #1687
- Steps: Complete a sale; confirm the post-sale review prompt renders on the customer-facing display.
- Expected: Review screen displays and captures input.
HW-54 — Basket + payment-status mirroring / privacy idle · P1 · D1
- Steps: Build a basket; observe the display mirrors items + running total + payment status; confirm privacy-safe idle screen between customers.
- Expected: Accurate mirroring; no prior-customer data on idle.
Compliance
HW-60 — Restricted sale + ID scan + manager override + compliance attach · P0 · D2 (scanner) for the ID-scan leg; D1 for display/override · M:12273161616
- Scanner caveat: the ID-scan leg needs an active barcode scanner, which in-app is D2 only (D1's scanner gate is off — see HW-40). Run the ID-scan portion on D2; the manager-override and customer-display compliance attach can be exercised on D1.
- Steps: Add an age-restricted item → scan ID (D2) → manager override → confirm compliance record attached.
- Expected: Allowed/denied flows correct; override audited; compliance evidence stored. See
android-pos.md(age verification).
Catalog & products
HW-70 — Product image upload listing on POS · P2 · D1·D2·D3 · M:12327660784 · PRs #1723–#1726
- Steps: Upload a product image (portal) → confirm it displays on POS catalog/checkout + thumbnail.
- Expected: Image renders via CDN read path; no WAF block.
HW-71 — Cart/checkout shows all items · P1 · D1·D2 · GH #1764
- Steps: Build a large cart; verify every line item is visible/scrollable at checkout.
- Expected: All items shown (tracked bug — confirm fix or capture repro).
Notifications
HW-80 — Owner notification push fan-out + tap routing · P1 · D1·D2·D3 · M:12273262610 · PR #1784
- Steps: Register device token; trigger an owner-scoped event; receive push; tap to route.
- Expected: Push delivered to org-owner/store-admin scope; tap opens the correct screen.
(Email/SMS channels are modeled-not-sent → ST-05.) See
checklists/notifications.md.
Terminal admin
HW-90 — Terminal reset button behavior + PIN · P2 · D1·D2 · M:12273262608 · PR #1727
- Steps: Invoke the reset button; test wrong PIN then right PIN (or confirm removal if product = delete).
- Expected: Wrong PIN blocks; right PIN resets; or button removed/hidden per decision.
HW-91 — Terminal store reassignment (support portal) · P2 · portal + D1/D2 · M:12327660782 · PR #1731
- Steps: Reassign a terminal's store in the support portal; confirm the device reflects the new store and permissions hold.
- Expected: Reassignment consistent across portal + device.
Provisioning / mTLS
HW-95 — Terminal onboarding + mTLS enrollment + denial matrix · P0 · D1·D2 (needs mTLS proxy/creds)
- Steps: Onboard a fresh terminal; enroll mTLS cert; confirm terminal-api reachable; run the acceptance + denial matrix.
- Expected: Valid cert resolves org/store/terminal; each denial stops before mutation. Full
matrix in
android-pos.mdandterminal-mtls-local.md.
3.A Automated harness (bench-pass test infra)
A Bazel-driven harness automates the scriptable legs of HW-20 / HW-30 / HW-40 so a regression is
caught off-device and the on-device leg is reduced to a captured logcat assertion + a physical-output
human check. The harness only observes/asserts existing behavior — it changes no device
capability gate (hasBarcodeScanner, hasCashDrawer, the per-platform DevicePlatform overrides).
HW-10 (card-present) and HW-31 (external Zebra label) stay manual — see the table below.
All device-smoke targets are tagged manual + requires-android-device; the on-device runs are
external bench gates (they do not run in CI or on a host without a device). Pin the device with
ANDROID_SERIAL (see the §1.2 cheat sheet) so the right platform is exercised. Set
ANDROID_HOME/ANDROID_SDK_ROOT (and ANDROID_NDK_HOME if needed); if Gateway Maven returns
401/403, run source scripts/gateway-maven-credentials.sh first.
Per-case run commands
# HW-20 cash drawer kick — D1 (LANDI) ONLY (D2/D3 expose no cashDrawer())
ANDROID_SERIAL=2589CCM07231 bazel test --config=android_x86_64 \
//apps/android/app/src/androidTest:hw20_cash_drawer_device_smoke_test --test_output=all
# HW-30 receipt print + reprint — D1 (LANDI 58 mm)
ANDROID_SERIAL=2589CCM07231 bazel test --config=android_x86_64 \
//apps/android/app/src/androidTest:hw30_receipt_device_smoke_test --test_output=all
# HW-30 — D2 (Nexgo thermal); PEAK_POS_HW30_INSTALL=1 installs the local debug build on D2
ANDROID_SERIAL=N92 PEAK_POS_HW30_INSTALL=1 bazel test --config=android_x86_64 \
//apps/android/app/src/androidTest:hw30_receipt_device_smoke_test --test_output=all
# HW-40 barcode scan into cart — D2 (Nexgo) ONLY (D1/D3 are gated off the in-app scanner)
ANDROID_SERIAL=N92 bazel test --config=android_x86_64 \
//apps/android/app/src/androidTest:hw40_scan_device_smoke_test --test_output=all
The HW-30 format/barcode contract is also pinned off-device (no device needed) by a JVM unit target — run it in CI/locally:
bazel test //apps/android/app/src/test:hw30_receipt_format_harness_test
What each target proves vs. what stays human/external
| Case | Device | Target | Automated assertion | Stays human / external |
|---|---|---|---|---|
| HW-20 | D1 only | …/androidTest:hw20_cash_drawer_device_smoke_test | logcat: LandiCashDrawer/OmniDriver/openBox path exercised with no Failed to open cash drawer. (open() logs nothing on success.) | The physical drawer opening (vendor signal not in logcat). |
| HW-30 | D1 & D2 | …/androidTest:hw30_receipt_device_smoke_test | logcat: printReceipt called → Starting print... → Print completed successfully (i.e. ReceiptPrintService.printReceipt() ran end-to-end on the device printer). | Paper legibility + scannable printed barcode. |
| HW-30 | none | …/test:hw30_receipt_format_harness_test (JVM) | 32-col / 58 mm layout has no overflow; barcode + format threaded to printDocument(barcode=…); reprint replays identical body+barcode. | n/a (off-device contract test; runs in CI). |
| HW-40 | D2 only | …/androidTest:hw40_scan_device_smoke_test (runs Hw40BarcodeScanDeviceTest) | instrumentation: a UPC injected through the production KeyboardWedgeScanner path emits as a barcode (single, multi-scan, unknown-UPC passthrough); best-effort live cart logcat (barcode-length log / ItemAdded). | A live catalog scan landing as a priced cart line (needs seeded catalog + signed-in terminal). |
| HW-10 | — | MANUAL (§ HW-10) | none — card-present tap/chip/swipe + approve/decline/cancel can't be automated. | Entire case is human on a live reader. |
| HW-31 | — | MANUAL (§ HW-31) | none — external BT Zebra pairing + scannable-barcode print can't be automated. | Entire case is human on an external Zebra. |
Honesty notes: the HW-20/HW-30/HW-40 device-smoke targets prove the software invocation path on the device; physical tender, drawer hardware, paper output, and scannable printed barcodes remain human bench gates recorded in §9. The smokes accept
PEAK_POS_HW20_LOGCAT/PEAK_POS_HW30_LOGCATto assert against an already-captured logcat from a live session. No capability gate was changed by this harness — the D1 scanner gate (HW-40) and any future D2/D3 drawer path remain follow-up code items, not harness concerns.
4. Staging / backend-gated tests (Bucket B — not blocked by hardware)
Code is in; the gate is credentials/data, not a device. Record the exact missing env
(MANAGEMENT_TOKEN, MERCHANT_TOKEN, ORG_ID, STORE_ID, TERMINAL_ID, terminal mTLS
cert/key, receipt recipient) so the next run can go deeper without rediscovery.
| Case | Pri | Item(s) | What to prove | Gate |
|---|---|---|---|---|
| ST-01 | P0 | M:12273153384, M:12273284225, M:12273084265 · GH #1758 | SkanData/mKonnekt export end-to-end: V036, tx-bundler SKAN export, loyaltyId, nested-item totals, parity | staging creds + data |
| ST-02 | P1 | M:12292686128 | Email and SMS delivery of invoices and receipts; confirm invoice path is code-complete | email/SMS provider + recipient |
| ST-03 | P1 | M:12273162659 · PR #1784, GH #1763 | Live UPCitemdb lookup (PR uses stubs); confirm-before-save Add Product; Android scan path not yet wired | UPCitemdb access |
| ST-04 | P1 | M:12273161353 · PR #1784 | Household create/merge/split, loyalty rollups, campaign dedupe in retail + support portals | staging org data |
| ST-05 | P1 | M:12273262610 · PR #1784 | Owner-notification email/SMS channel dispatch (push = HW-80) | provider send |
| ST-06 | P1 | M:12273084266 | Account-scoped merchant migration staging/prod GRANTs + downstream smoke | staging/prod DDL access |
5. External / blocked / unbuilt (Bucket C — not provable on this bench)
Surface these as honest gaps; a green bench run is not full launch coverage.
| Case | Pri | Item(s) | Why off-bench |
|---|---|---|---|
| EX-01 | P0 | M:12273153385 | iOS POS / iPad Gateway activation — Mac/Xcode + card-present runtime |
| EX-02 | P0 | M:12273284348 | iOS TestFlight + App Store submission — Apple pipeline. See checklists/ios-pos.md |
| EX-03 | P1 | M:12273161264, M:12273284282 | Peak Mobile iOS inventory/push/AI (Android side is HW-80/HW-40). See checklists/peak-mobile.md, checklists/mobile-release-evidence.md |
| EX-04 | P1 | M:12273236728 | Blocked on real Puff n Huff merchant invoices for AI import |
| EX-05 | P3 | M:12273172123, M:12273180185 | Processor smart routing — not implemented; test after build |
| EX-06 | P3 | M:12273161726 | Firearms vertical — unbuilt; scope legal/compliance first |
| EX-07 | P3 | M:12273262899 | Automotive VIN/model vertical — unbuilt; define identity fields first |
| EX-08 | P3 | M:12327633040 | Intune MDM — scoping only, no customer driver |
6. Per-device end-to-end sale smoke (run last)
The integrated proof — run a full real sale on each device:
login → scan items (HW-40) → apply discount → card tender (HW-10) → receipt print (HW-30) → cash tender + drawer kick (HW-20) → customer display correct (HW-50/54) → transaction in backend.
- D1 C20Pro — full integrated sale.
- D2 N92 — full integrated sale.
- D3 Tab A9+ — full sale with external peripherals.
Only this proves the integrated stack; the launch + activation-cache smokes (PF-5) prove startup/persistence only.
7. Coverage gaps & honesty notes
- Debug vs release: the bench installs the local debug (
dev.keystore) build; the explicitly signed release/ProGuard APK is CI/keystore-gated and not buildable here. Functional cases are valid on the debug build, but ProGuard/R8-specific behavior is not signed off until the explicitly production-signed CI release APK is exercised — do not claim release sign-off here. - In-app capability gates (not just hardware): barcode scanning is in-app only on D2
(D1
hasBarcodeScanner()isfalse; D3 Generic exposes none) and the cash drawer is in-app only on D1 (#1770). HW-40/HW-60 ID-scan run on D2; HW-20 runs on D1; the open D1/D3 scanner gate is a follow-up code item, not a tester failure. - D2 labels are text-only:
NexgoLabelPrinterstrips ZPL, so scannable barcodes require an external Zebra (HW-31). - D1 cannot run the activation-cache smoke until the
.testAPK (samedev.keystore) is installed. - A launch smoke proves startup, not authenticated workflows, value movement, receipt delivery, peripherals, or staging bindings — keep ST-/EX- items open until their evidence exists.
- iOS / Mac / Xcode / XCFramework proof is external-only on this host.
8. Traceability matrix (Monday "needs testing" → case)
| Monday item | Title (abbrev) | Pri | Case |
|---|---|---|---|
| 12273180051 / 12273262760 | Android 30s background/relogin grace | P0 | HW-01 |
| 12327661562 | Catalog refresh without restart | P1 | HW-03 |
| 12273284367 / 12273262586 | Refunds & returns manager approval | P0 | HW-11 |
| 12273262559 / 12273284344 | Cash drawer hardware | P0 | HW-20 |
| 12273162897 / 12273180162 | Label printer hardware | P2 | HW-31 |
| 12273161616 | Restricted sale + ID scan + override | P0 | HW-60 |
| 12273284372 | Advertisements on customer display | P0 | HW-50 |
| 12273178045 / 12273288780 | Loyalty profile + customer-display entry | P1 | HW-51 |
| 12273172511 / 12273161853 | Store type + merchant logo idle display | P2 | HW-52 |
| 12273153564 | Customer review screen | P1 | HW-53 |
| 12273262610 | Owner notification fan-out (push) | P1 | HW-80 / ST-05 |
| 12327660784 | Product image upload listing | P2 | HW-70 |
| 12273262608 | Terminal reset button behavior | P2 | HW-90 |
| 12327660782 | Terminal store reassignment | P2 | HW-91 |
| 12327662836 | Retail portal edge cases / shift schedule | P2 | retail-portal.md |
| 12273153384 / 12273284225 / 12273084265 | SkanData/mKonnekt export | P0 | ST-01 |
| 12292686128 | Email + SMS invoices & receipts | P1 | ST-02 |
| 12273162659 | External SKU/UPC lookup | P1 | ST-03 |
| 12273161353 | Household / customer relationship | P1 | ST-04 |
| 12273084266 | Account-scoped merchant migration | P1 | ST-06 |
| 12273153385 | iOS POS Gateway activation | P0 | EX-01 |
| 12273284348 | iOS TestFlight + store submission | P0 | EX-02 |
| 12273161264 / 12273284282 | Peak Mobile inventory/push/AI | P1 | EX-03 |
| 12273236728 | Puff n Huff invoices (blocked) | P1 | EX-04 |
| 12273172123 / 12273180185 | Processor smart routing | P3 | EX-05 |
| 12273161726 | Firearms vertical | P3 | EX-06 |
| 12273262899 | Automotive VIN vertical | P3 | EX-07 |
| 12327633040 | Intune MDM | P3 | EX-08 |
Related GitHub issues: #1758 (SkanData), #1760 (cash drawer), #1763 (PO scan), #1764 (cart items), #1766 (EBT).
9. Execution log
Fill one row per case/device as run. Result is one of Pass / Fail / Blocked. Evidence = logcat path, screenshots, backend txn/receipt IDs, APK SHA.
| Date | Case | Device | Result | Tester | Evidence |
|---|---|---|---|---|---|
| 2026-06-21 | PF-2/3 build + install (debug) | D2, D3 | Pass | bench (adb) | android_app_local_staging.apk sha 15c07962…; install -r OK |
| 2026-06-21 | PF-5 launch smoke | D2 | Pass | bench (adb) | launch_smoke_test.sh; boots STAGING, foreground, no FATAL |
| 2026-06-21 | PF-5 launch smoke | D3 | Pass | bench (adb) | launch_smoke_test.sh; boots STAGING, no FATAL |
| 2026-06-21 | PF-5 activation-cache smoke | D2 | Pass | bench (adb) | gateway_sdk_activation_cache_device_smoke_test → cache_hit |
| 2026-06-21 | PF-2 R8/ProGuard verify (release) | build | Pass | bench | Historical standalone R8 check build passed; current gate builds //apps/android:android_app_prod_unsigned.apk and signs it explicitly |
| 2026-06-21 | PF-4 App Check debug token | D3 | Pass | bench (adb) | token 31ebc7e9… registered on peaksuite-staging via App Check API (ADC) |
| 2026-06-21 | HW-02 login → sale | D1 | Pass | bench (adb) | PIN unlock; live PEAK Beauty terminal; operator sale screen + catalog |
| 2026-06-21 | HW-71 cart shows items | D1 | Pass | bench (adb) | manual item → Cart(1), Subtotal/Total; dual pricing Cash $5.00 / Card $5.20 |
| 2026-06-21 | Checkout / tender flow | D1 | Pass | bench (adb) | tip→customer-display handoff; Gift Card; Key In Card; manager fee-waive |
| 2026-06-21 | HW-50 / HW-52 customer-display brand | D1 | Pass | bench (adb) | com.ms.msdisplay shows PEAK Beauty / Powered by PEAK POS / Welcome |
| 2026-06-21 | HW-54 customer-display tip handoff | D1 | Pass | bench (adb) | "Waiting for customer to choose tip on the customer display" |
| 2026-06-21 | History / transactions | D1 | Pass | bench (adb) | real sales/refunds/batches + txn detail; 0 FATAL across navigation |
| 2026-06-21 | Provisioning restore (staging) | D2, D3 | Blocked | bench (adb) | terminal-api mTLS ConnectionShutdownException; debug build lacks a valid terminal cert |
Bench session notes (2026-06-21)
- D1 (C20Pro, release
0.0.19) is a live, staging-provisioned terminal — the full software-observable POS path passed (login → cart → dual pricing → checkout → tip handoff → tender options; history/transactions; customer-display branding). Physical tender (card read / cash drawer) and completing a real transaction remain human-gated. - D2/D3 (local debug builds) pass the launch + activation-cache smokes but cannot
restore terminal provisioning against staging: the terminal-api mTLS handshake is
dropped (
ConnectionShutdownException) even though general internet and DNS forstaging-terminal.peakpos.coresolve. Registering the App Check debug token (done for D3) is necessary but not sufficient — the debug build needs re-onboarding to obtain a terminal client cert the staging mTLS LB accepts. Until then the POS screens / Hardware Test page / sale E2E are unreachable on D2/D3. - Cash-drawer / printer / scanner Hardware Test page is not in D1's
0.0.19build (it postdates #1770 + theHardwareTestSection); validating those on-device needs the CI explicitly production-signed release APK deployed to D1. - D1's displays are
FLAG_SECURE(both the operator screen and thecom.ms.msdisplaycustomer display), so screenshots are blocked — evidence is captured via uiautomator text dumps.