Mobile App & Kiosk (MOB)
Module code MOB · Release R1 (ESS and MSS), R2 (kiosk, face verification, claims), R3 (field force) · Packaging Core; face verification is an add-on per enrolled employee · Requirements
FR-MOB-*,BR-MOB-*in the PRD and BRD · Architecture ADR-014 in architecture
1. Overview and scope
MOB is one app (Expo Router, iOS and Android) whose navigation is driven by the user's permissions and the tenant's entitlements. It is a client of the module APIs; it owns only device-side concerns: offline queues, secure storage, push tokens, kiosk enrolment, camera and location capture, and app-version policy. Face templates and kiosk devices are the only server-side MOB entities.
In scope: company-code resolution and login (password, OTP, SSO); ESS (punch, attendance calendar, requests, leave, payslips, profile, holidays, announcements, recognition); MSS (approvals inbox, team calendar, today's attendees, on-behalf requests); field (punch at client site, visits, tracking, travel; R3); claims capture with bill photos (R2); kiosk mode on shared tablets with face or PIN and liveness (R2); push notifications with deep links; offline queue for punches, visits and claim drafts; minimum supported version enforcement; accessibility and Indian-language readiness.
Out of scope: admin configuration screens (web only), a separate "Lite" app (Lite is a plan).
2. Learned from the reference product
Source: Spryple analysis, §15 and §21.1, §21.3, §21.6, §21.7.
- [Observed] Four apps exist (HCM, Lite, CRM, and claims inside the CRM app) with overlapping logins and duplicated leave screens; only the CRM app is documented on iOS (§15; defect D10).
- [Observed] HCM supports face self check-in, manager face enrolment (Verified or Not Verified) and a Receptionist front-office check-in for all or selected employees; no liveness cue was visible (SP-ATT-19; §21.3).
- [Observed] Lite is a separate SKU with its own employee and payroll concepts (payable salary = salary × presents) and has not been updated since 2024 (§15; §21.1).
- [Observed] The CRM app shows an in-app disclosure before the Android location prompt; store listings say "No data collected" yet "Shares location" (§21.1, §21.7).
- [Observed] The mobile attendance request needs a shift and half-day checkboxes; the leave request captures contact number and emergency mail (§21.3, §21.4).
3. Our design decisions
| ID | Decision [Proposed] | Reason |
|---|---|---|
| D-MOB-01 | One app; tabs and screens appear from /v1/iam/me/permissions and entitlements. | Removes four-app confusion; one release pipeline. |
| D-MOB-02 | Kiosk mode replaces the receptionist login: the tablet is enrolled with a location-scoped, punch-only token; employees identify by face (with liveness) or PIN plus photo. | Shared-device punching without a privileged human login. |
| D-MOB-03 | Offline punch queue: punches are signed on the device with a monotonic clock offset captured at last sync; the server flags punches whose device time drifts beyond tolerance. | Factories and field sites lose network; time tampering must be detectable. |
| D-MOB-04 | Face verification is a provider interface (enroll, verify, liveness), consent-gated, storing templates not raw images where possible. | DPDP biometric rules; vendor flexibility. |
| D-MOB-05 | Truthful store data-safety declarations and an in-app disclosure before every sensitive permission. | The reference listing is inconsistent. |
| D-MOB-06 | Minimum supported version enforced by the API (426 Upgrade Required) and OTA updates for JS-only fixes. | Old clients cannot corrupt new rules. |
4. Domain model
Punches created by the app or kiosk are stored in att_punch (see Attendance); MOB records only the sync envelope.
5. State machines
5.1 Offline queue item
5.2 Face profile
6. Key business logic
kioskPunch(kiosk, capture):
require kiosk token valid and kiosk.location active
if capture.method == FACE:
live = provider.liveness(capture.frames) ; if not live: reject "liveness failed"
match = provider.verify(capture.frame, candidates = profiles at kiosk.location)
if match.confidence < policy.face.threshold: offer PIN fallback with photo
if capture.method == PIN: verify PIN (rate-limited per kiosk) and store the photo
direction = policy.toggle ? nextDirection(employee, today) : capture.direction
ATT.recordPunch(employee, now, source = KIOSK, device = kiosk, face_match_result)
offlineSync(batch):
server_now - (device_now_at_sync - clock_offset) gives the drift
for each item: punched_at = device_time corrected by offset
if abs(drift) > policy.max_clock_drift_seconds: flag CLOCK_DRIFT
ATT.recordPunch(...) with dedupe_key so retries are idempotent
7. Configuration
| Setting | Type | Default | Description |
|---|---|---|---|
mob.min_supported_version | string | set per release | Older apps get 426 |
mob.offline_queue_max_hours | integer | 72 | Older queued punches are rejected |
mob.max_clock_drift_seconds | integer | 300 | Drift before a punch is flagged |
kiosk.identify_methods | array of string | [FACE, PIN] | Allowed methods |
kiosk.direction_mode | enum | AUTO_TOGGLE | AUTO_TOGGLE or ASK |
face.threshold | number | 0.90 | Match confidence needed (provider-specific) |
face.liveness_required | boolean | true | Always on in production |
push.enabled | boolean | true | Push notifications |
biometric_app_lock | boolean | false | Device biometrics to open the app |
8. Screens by persona
| Persona | Mobile screens |
|---|---|
| Employee | Home (punch button, today's shift, alerts), attendance calendar with legend, requests, leave apply and balances, payslips, holidays, announcements, recognition, profile, notifications |
| Line manager | Approvals inbox, team today (present, absent, late, on leave), team calendar, on-behalf request, device approvals, face enrolment for team (when enabled) |
| Field executive (R3) | Punch with photo, today's beat, visits with check-in, add account and activity, payment collection, travel summary |
| Claimant (R2) | New claim with bill photos, drafts offline, status tabs |
| Kiosk operator (R2) | Kiosk enrolment, kiosk punch screen (locked), offline indicator |
9. Events
| Event | Consumers |
|---|---|
mob.sync.batch_received | ATT (punch ingest), FFM (points and visits) |
mob.kiosk.enrolled | IAM (device token), audit |
mob.face.enrolled, mob.face.deleted | Audit, consent register |
mob.app.version_blocked | Ops metrics |
Consumed: iam.user.deactivated and iam.session.revoked (wipe tokens and queues), plt.entitlements.changed (navigation), org.geofence.changed (cached fences), ntf.task.created (badge counts via push).
10. API surface
MOB uses the module APIs. Its own endpoints:
| Resource | Main operations |
|---|---|
/public/tenants/resolve | Company-code lookup |
/v1/mob/installs | POST register, PUT push token |
/v1/mob/sync | POST batch of offline items |
/v1/mob/kiosks | POST enrol (with admin approval code), GET, DELETE |
/v1/mob/kiosks/punch | POST kiosk punch (kiosk token) |
/v1/mob/face-profiles | POST enrol, DELETE |
Detailed in API endpoints.
11. Permissions
| Permission | Scopes | Default roles |
|---|---|---|
mob.kiosk.enrol | LOCATION | hr_manager |
mob.kiosk.punch | LOCATION | kiosk device token only |
mob.face.enrol | TEAM, LOCATION | line_manager, hr_manager, kiosk_operator |
mob.face.delete | SELF, ORG | employee (withdraw consent), hr_admin |
12. Reports
| Report | Contents |
|---|---|
| App adoption | Installs, active users, version spread |
| Offline and drift | Queued punches, flagged drift, rejections |
| Kiosk health | Punches per kiosk, last seen, failure rate |
| Face verification | Enrolled vs eligible, match failures, fallbacks to PIN |
13. Release and priority
| Capability | Release | MoSCoW |
|---|---|---|
| Single app, company code, login, ESS and MSS, push, offline punch | R1 | Must |
| Version enforcement and OTA | R1 | Must |
| Claims capture | R2 | Should |
| Kiosk mode | R2 | Should |
| Face verification with liveness | R2 | Should |
| Field force screens | R3 | Must (for FFM) |
| Indian-language UI (Hindi, Telugu, Tamil) | R3 | Could |
14. Open questions
- On-device face matching (privacy, offline) versus a cloud provider in ap-south-1 (accuracy, cost)? This needs a spike with pilot hardware.
- Do we support low-end Android devices (2 GB RAM) for kiosk mode? This affects the camera and liveness approach.
- Should the kiosk work fully offline for a shift, with templates cached on the tablet? This raises biometric storage risk.