# Device connectivity and operations

> Historical design requirements: consult [chapter 30](30-current-architecture.md) for the current implementation and outstanding work.

Current decision: the deployed transport is the local MQTTnet mutual-TLS gateway using the `uic/tms/v1/devices/{deviceId}` contract. AWS IoT is not deployed. Android UIC Home and Linux xTMSAgent implement this contract; see chapters 10 and 33. The comparison below is retained as decision history.

## Transport recommendation

Start the compatibility pilot with an adapter around the existing MQTT broker, then choose the production broker using measured fleet, availability, certificate and hosting requirements. Design one canonical application protocol so AWS IoT Core can be added without changing portal business rules. Do not run two active transports issuing independent commands to the same device.

| Dimension | Adapt existing MQTT | AWS IoT Core |
| --- | --- | --- |
| Legacy terminal fit | Existing protocol and field experience | Requires credential/topic and agent compatibility validation |
| Operations | Team owns patching, persistence, failover and capacity | Managed broker with AWS-specific policy/operations |
| Identity | Add per-device identity, revocation and tenant mapping | Per-device certificates and tightly scoped IoT policies |
| Device state | TMS maintains desired/reported state | Shadows optional; SQL business state remains authoritative |
| Hosting | Bank-hosted, private cloud or cloud | AWS region and connectivity constraints |
| Cost | Infrastructure plus operational support | Model connections, messages, shadows, rules and data transfer |
| Decision gate | Isolation, recovery and load tests | Agent proof, policy tests, region and cost review |

AWS IoT Core supports MQTT QoS 0 and 1; QoS 1 can produce duplicate delivery and does not prove command execution. Device Shadows can represent desired/reported state, but should not be used as a queue of one-time reboot commands. [AWS MQTT documentation](https://docs.aws.amazon.com/iot/latest/developerguide/mqtt.html), [Device Shadows](https://docs.aws.amazon.com/iot/latest/developerguide/iot-device-shadows.html).

## Canonical topic and message design

Proposed new-agent namespace: `uic/tms/v1/devices/{deviceId}/telemetry`, `/inventory`, `/commands`, `/results`, `/config/request`, `/config/response`. Device identity is globally unique; broker policy binds credentials to that exact device. Customer and AssignmentEpoch are resolved from trusted enrollment and compared with signed message context. Tenant identifiers in payloads cannot override that mapping.

Device may publish only its report/result/request topics and subscribe only to its own command/response topics. Deny wildcard subscriptions, arbitrary topic publishing and anonymous access. Backend transport identities have separate permissions. Use TLS; prefer unique device certificates where supported. Credential provisioning, rotation, expiry and revocation must be operational workflows.

Envelope fields: protocolVersion, messageId, deviceId, assignmentEpoch, sentAtUtc, sequence, bootId, correlationId and typed payload. Server also records receivedAtUtc. Use bootId + sequence to distinguish device restart from stale data; device clock is not authoritative for receipt or command expiry. Bound payload size, schema depth, collection lengths and per-device rate.

Never retain one-time commands. Keep pending work in the durable operation store and deliver only if unexpired, authorized and still assigned at the current epoch. QoS 0 is acceptable for replaceable heartbeats; important results/config acknowledgements require QoS 1 plus application-level retry/deduplication. Ingress acknowledges application-level durability after durable queue or DataService acceptance, not merely receipt into an in-memory handler.

## Existing service reuse

| Observed source | Reuse candidate | Required adaptation |
| --- | --- | --- |
| MQTT/TMSMQTT.csproj | .NET worker, MQTTnet, Dapper, service hosting | Runtime alignment; isolate broker and business persistence |
| MQTT/Services/MqttAuthorizationService.cs | Own-topic checks | Remove global customer broadcast; fail closed without authenticated identity; customer/epoch mapping |
| MQTT/Models/MqttTopics.cs | Existing notify/cmd/status and config handshake topics | Versioned translation adapter for existing agents |
| MQTT/Data/TmsRepository.cs | Existing state/task semantics | Replace legacy table access with typed DataService calls |
| MQTT/Program.cs | Broker, notification listener and task workers | Outbox leasing and idempotent result handling |
| TMSRemoteControlServer | WebSocket relay, H.264 frame forwarding, keyframe cache | Scoped tickets, origin checks, WSS, session routing and revocation |

Both inspected projects target net9.0. This is source-level reuse assessment only; no compatibility build or device test has been performed. Preserve legacy wire formats through a translation boundary until representative Android and Linux terminals pass contract tests. Terminal firmware capabilities are not yet verified.

The existing relay viewer uses a static query-string API key, and its documented terminal token is derived from a shared secret and terminal ID. Those authentication patterns are not the new authorization model. Keep the video forwarding mechanism; replace admission and session ownership. Audit and health endpoints must not expose other customers' sessions.

## Durable operation state machine

```text
Draft → [PendingApproval] → Queued → Dispatched → Accepted → Running → Succeeded
                            │           │          │          └→ Failed
                            │           └──────────┴───────────→ TimedOut
                            ├→ Cancelled
                            └→ Expired
```

Dispatched means the gateway handed the command to transport. Accepted means the agent acknowledged it. Succeeded requires device-reported completion evidence appropriate to the operation. Reboot may require a new bootId/uptime report; install requires observed package/version; parameter apply requires matching revision/hash. An elapsed timeout records uncertainty; late results are attached as reconciliation events without silently rewriting the audit trail. Terminal results are monotonic and sequence-checked.

Create operation + target snapshot + audit + outbox in one transaction. Dispatcher leases work, rechecks ownership/permission/expiry, publishes the same CommandId on retries and records attempts. Agent persists deduplication state and returns the previous result for repeated CommandId. If a legacy agent cannot guarantee deduplication, disable automatic retry for unsafe actions and expose an UnknownOutcome result for manual reconciliation.

Cancellation prevents queued work from starting; it cannot recall an already executed reboot. A dispatched command is cancellation-requested only if the agent supports cancellation. Serialize conflicting app/config/block operations per device and application. A new desired state supersedes stale work explicitly, with audit.

## Operation policies

| Operation | Required behavior |
| --- | --- |
| Reboot | Capability check, reason, expiry, maintenance window; verify subsequent boot |
| Send message | Bounded text; escape display; define delivery versus user-read acknowledgement |
| Block/unblock | Separate desired and reported state; authorized reason; unblock cannot be inferred from timeout |
| Remote control | Online and capable device; short-lived scoped session; view/control permission; visible session indicator |
| Application push | Approved artifact, compatible platform/model/agent, disk/battery constraints, install result |
| Parameter push | Published compatible schema/config revision, integrity check, validate before atomic apply |

Block semantics must be defined per terminal agent: restrict payment application or device workflow while preserving the management channel so an authorized unblock remains deliverable. Blocking is not an acquirer-side payment-account suspension. Offline devices show PendingBlock until acknowledged.

## Applications: APK and Linux ZIP

Lifecycle: Uploading → Quarantined → Validating → Approved → Released → Withdrawn. Upload uses an expiring customer/application-scoped grant, size limits and server-generated storage key. Completion verifies checksum and scans content. Do not trust extension, client MIME type or uploaded metadata.

APK validation extracts package identifier, version code/name, signing identity, platform requirements and compatible device constraints. Enforce signing continuity and downgrade rules. ZIP validation requires a defined manifest, target model/architecture, installation type, payload hashes and signed package policy. Reject path traversal, absolute paths, dangerous symlinks and decompression bombs. Uploaded archives are never executed on the server.

Release produces an immutable artifact and approval record. Deployment freezes target devices, software version, schedule and assignment epochs. Use canary batches, bounded concurrency, pause thresholds and per-device outcomes. A signed HTTPS grant enables download; terminal verifies manifest/hash/signature before staging, installation and reporting. Renew an expired download grant only after reauthorization. Rollback is a new deployment and is offered only when the package/agent supports it.

## Telemetry and location

Collect typed device properties, OS/firmware versions, battery/storage/network health, app inventory and optional location with source, accuracy and timestamp. Separate reported time from received time and display freshness. Proposed offline threshold is three missed heartbeat intervals plus grace; tune by network and terminal capabilities.

Location collection is customer policy controlled with role-limited map access and retention. A last-known coordinate must not be presented as live tracking. Keep raw location out of broad audit exports. Retention proposals: current state continuously replaced, raw telemetry 30 days, operational results 180 days, audit 365 days; each requires bank/residency review before production.

## Failure and recovery cases

Test broker restart, duplicate/out-of-order reports, replay after transfer, queue outage, SQL outage, expired certificates, terminal clock skew, storage download failure and partial bulk deployment. Queue outage must not accept an important report and silently lose it. Portal uses last-known state with a stale indicator when live ingest is unavailable. Payment traffic stays independent from this management path.
