---
module: devices.sw-deploy
title: Software Deployment
tab: Internal
order: 20
audience: internal
---

> **Internal · ops/support.** Written 2026-07-01 from source (`hiveops-devices/frontend` + `hiveops-fleet/backend`). The SW Deploy tab is a **thin view over Fleet tasks** — the data and all actions live in the **hiveops-fleet** service, not devices.

## What you're actually looking at

The **SW Deploy** tab (`DeviceSoftwareTab.svelte`, under a device's detail view in `devices.bcos.cloud`) renders two fleet task kinds for one device:

- **Step 1 — Software Download** = fleet task kind `SOFTWARE` (delivers the package to the ATM).
- **Step 2 — Software Install** = fleet task kind `SOFTWARE_INSTALL` (runs `softwareinstall.cmd` against the staged `software/` dir on the device).

It calls **only** the fleet backend: `GET /fleet/tasks/paginated?atmId={id}&taskKind=SOFTWARE&taskKind=SOFTWARE_INSTALL` to read, and `POST /fleet/tasks` to create an install. No devices-backend or incident call is involved for this tab's data.

## Roles required

| Action | Endpoint | Required authority |
|--------|----------|--------------------|
| View history / step cards | `GET /api/fleet/tasks/paginated` | `USER`,`CUSTOMER`,`ADMIN`,`MSP_ADMIN`,`BCOS_ADMIN` |
| Create the install task ("Schedule Install") | `POST /api/fleet/tasks` | `hasAnyRole('ADMIN','MSP_ADMIN','BCOS_ADMIN')` |
| **Approve/reject** the SOFTWARE / SOFTWARE_INSTALL task | `POST /api/fleet/tasks/{id}/approve` \| `/reject` | `hasAuthority('FLEET_APPROVER') or hasRole('BCOS_ADMIN')` |
| Cancel / retry | `POST /api/fleet/tasks/{id}/cancel` \| `/retry` | `hasAnyRole('ADMIN','MSP_ADMIN','BCOS_ADMIN')` |

**Gotcha:** creating the install is admin-gated, but **approving it is a separate gate** — `SOFTWARE` and `SOFTWARE_INSTALL` are born in status `PENDING_APPROVAL` (see below). A plain `MSP_ADMIN` who is not a `FLEET_APPROVER` can create the task but **cannot approve it**; a `BCOS_ADMIN` (or `FLEET_APPROVER`) must approve before the agent will ever see it.

## The dispatch lifecycle (why a task "isn't doing anything")

`SOFTWARE` / `SOFTWARE_INSTALL` follow the approval flow:

`PENDING_APPROVAL` → (approve) → `PENDING` → (agent polls) → `QUEUED` → `RUNNING` → `COMPLETED` / `FAILED`

- The agent only receives tasks in `PENDING` whose `scheduledAt` is null or in the past (`getNextPendingTaskOfKind`). So **PENDING_APPROVAL never dispatches** — it waits for an approver.
- On create, the target device must be **online** (heartbeat within **15 minutes**) and in the caller's **institution scope**, or the task is silently skipped (logged as a warning, no error to the user).
- `SOFTWARE_INSTALL` with no explicit schedule auto-defers to the device's **patch window** if one is assigned (`PatchWindowService.nextOccurrence`).

## First things to check when it misbehaves

1. **Nothing in the tab / expected history missing.** The tab reads the **live `fleet_tasks` table only**. Completed tasks are archived to `fleet_task_history` and deleted from `fleet_tasks` the instant the agent reports `COMPLETED` (`archiveAndDelete`), plus a 5-minute sweep (`archiveStaleTerminalTasks`) for `COMPLETED`/`CANCELLED`/`REJECTED`. So **completed downloads/installs disappear from this tab** — look in fleet task **history** (`GET /api/fleet/tasks/history?atmId={id}`) or `fleet_task_history` in DB. `FAILED` stays in the live table and is visible.
2. **"Schedule Install" button never appears.** Frontend `canInstall` requires the latest `SOFTWARE` download to be `status === 'COMPLETED'` *and still present in the live list*. Because completion archives+deletes the row, the button window is effectively zero — see uncertainties/architect notes. Workaround: create the `SOFTWARE_INSTALL` directly via `POST /fleet/tasks` with `taskKind:"SOFTWARE_INSTALL"`.
3. **Task created but stuck in PENDING_APPROVAL.** Needs an approver — `FLEET_APPROVER` authority or `BCOS_ADMIN`. Check `GET /api/fleet/tasks/pending-approval`.
4. **Task silently not created for a device.** Almost always **offline device** (no heartbeat in 15 min) or **out of institution scope**. Confirm via the device's `lastHeartbeat` in `device_summary` and the caller's institution scope. Check fleet backend logs for `offline — skipping` / `out of scope`.
5. **Download package rejected on create.** Artifact must be **enabled** — a disabled artifact returns `400 "Artifact '…' is disabled"`. `SOFTWARE_INSTALL` itself carries no artifact (it acts on the already-staged dir).
6. **Stuck in QUEUED/RUNNING.** A task idle in `QUEUED`/`RUNNING` past the stale threshold is auto-reset to `PENDING` (offset zeroed) on the next agent poll for re-dispatch.

## Where to look

- Frontend: `hiveops-devices/frontend/src/components/AtmDetail/DeviceSoftwareTab.svelte`
- Backend service: **hiveops-fleet** (port **8097**, DB **`hiveiq_fleet`**), `FleetTaskService` + `FleetApiController` + `InternalFleetController`.
- Logs: fleet backend (Loki `hiveops-fleet`), not devices.

See also [devices.sw-deploy] Overview (customer) and the Architect tab for the full data flow.
