# Audit log: sessions and file transfers

> How QuickDesk audit rows are structured, how sessions are reconstructed from open and close events, and what the console's query windows and row caps mean.

The **Sessions & files** screen is a read-only view over the QuickDesk
audit trail. Every remote-control connection and every file transfer that
passes through a ClutchCall QuickDesk device writes a row to the
`quickdesk_audit` table in ClickHouse. The console renders two
projections of that table:

| Card             | Backing procedure              | Shows                                          |
| ---------------- | ------------------------------ | ---------------------------------------------- |
| **Sessions**     | `adminQuickdesk.sessions`      | Connection lifecycle, grouped by `session_id`. |
| **Files shared** | `adminQuickdesk.files`         | File transfers, with per-file names and sizes. |

Nothing on this screen mutates state. It is an audit surface: if a row
is missing or a session looks wrong, the cause is upstream in what the
device reported, not in the view.

## Two event families: connection lifecycle and file transfer

The audit table mixes two kinds of events, and the two cards separate
them.

**Connection lifecycle events** mark the start and the end of a
remote-control session. Both carry the same `session_id`. The `sessions`
procedure groups on that id and pairs the opening event with the closing
one, so each console row is a *session*, not a raw event. A session that
has an opening event but no closing event is still returned — see
[Sessions that never close](#sessions-that-never-close).

**File transfer events** are standalone. Each row is one transfer
operation, tagged with a direction (`send` or `recv`) and carrying the
list of files it moved plus the filesystem path involved. There is no
open/close pairing here: one event is one complete record. The console
tags `send` transfers with a warning tone so that outbound movement off a
device is visually distinct from inbound.

Both families are scoped to a device (`device_id`) and, where the remote
end identified itself, to a peer (`peer_id` for transfers, `ip` for
sessions).

## Field reference for an audit row

### Session rows

`adminQuickdesk.sessions` returns one object per reconstructed session:

| Field         | Meaning                                                                 |
| ------------- | ----------------------------------------------------------------------- |
| `device_id`   | The QuickDesk device the session ran against.                           |
| `session_id`  | The identifier shared by the session's open and close events.           |
| `ip`          | Peer IP recorded at connection time. May be empty; the console renders `—`. |
| `opened`      | Timestamp of the opening event.                                         |
| `live`        | `true` when no closing event has been seen for this `session_id`.        |
| `durationSec` | Seconds between open and close. `null` while the session is `live`.      |

`device_id` and `session_id` together identify a row uniquely — the
console keys its table on the pair, so the same `session_id` observed
against two devices produces two rows.

The console formats `durationSec` as `12s`, `4m 07s`, or `1h 23m`
depending on magnitude, and shows a **live** tag instead of a duration
whenever `live` is set.

### File transfer rows

`adminQuickdesk.files` returns one object per transfer event:

| Field       | Meaning                                                                       |
| ----------- | ----------------------------------------------------------------------------- |
| `ts`        | Timestamp of the transfer event.                                              |
| `action`    | `send` or `recv`. May be empty, in which case the console renders `—`.         |
| `device_id` | The device the transfer ran against.                                          |
| `peer_id`   | The remote end of the transfer. May be empty.                                 |
| `path`      | Filesystem path the transfer targeted. May be empty.                          |
| `numFiles`  | The number of files the event reported.                                       |
| `files`     | An array of `[name, size]` pairs.                                             |

`numFiles` and `files` are independent fields, and they can disagree.
`numFiles` is the count the device reported; `files` is the per-file
detail, which may be empty even when the count is non-zero. The console
treats them as separate: it always prints the count, and prints the
comma-joined name list only when `files` is non-empty.

The aggregate byte size shown next to the count is **computed in the
console**, not stored: it is the sum of the second element of every pair
in `files`. Because of that, the byte total reflects only the files that
appear in `files`. If per-file detail is missing, the size is suppressed
rather than shown as zero. Do not treat that total as an authoritative
transfer volume — treat `files` as the authority, and read the total as a
convenience sum of it.

## How a session is reconstructed from open and close events

A session row in the console is derived, not stored:

1. The device emits a connection event when a remote-control session
   starts. It carries `session_id`, `device_id`, and the peer `ip`.
2. The device emits a matching close event when the session ends,
   carrying the same `session_id`.
3. `adminQuickdesk.sessions` groups the events by `session_id` and emits
   one row: `opened` from the first event, `durationSec` from the
   difference between the two.
4. If step 2 never happened, the row comes back with `live: true` and
   `durationSec: null`.

This has a consequence worth internalising: **the `live` flag means "no
close event has been recorded", not "a connection is currently
established"**. It is an inference from absence. The next section covers
why the two are not the same thing.

## Sessions that never close

A session can sit in the `live` state indefinitely. The common causes:

- **The close event was never emitted.** A device that loses power, is
  hard-killed, or drops its network link mid-session has no opportunity
  to report the close. The open event is already durable in the audit
  table; the pair never completes.
- **The close event was emitted but not delivered.** If the audit write
  fails or is dropped in transit, the same asymmetry results.
- **The session genuinely is still open.** A long-running unattended
  session looks identical to the two cases above.

Because these are indistinguishable from the audit table alone, the
console does not try to distinguish them — it reports exactly what the
trail says. To tell a stale `live` row from a real one, cross-check the
device's current connection state on the device screen rather than
inferring it from this table.

A stale `live` row never resolves on its own. The close event is not
back-filled, so the row stays `live` for as long as it remains inside the
query window, and then falls out of the view.

## Query windows, row caps, and retention

The console calls both procedures with fixed arguments:

| Card         | Window (`days`) | Cap (`limit`) |
| ------------ | --------------- | ------------- |
| Sessions     | 7               | 200           |
| Files shared | 30              | 200           |

Three things follow from this.

**The windows differ by card on purpose.** Sessions are an operational
signal — you look at them to understand what happened recently — so the
window is short. File transfers are a compliance signal, where "what
left this machine last month" is the question, so the window is longer.
A transfer that happened three weeks ago appears in **Files shared** but
its parent session will already have aged out of **Sessions**.

**The row cap truncates silently.** Each table returns at most 200 rows.
The screen has no pagination and no "showing N of M" indicator, so a busy
fleet will fill both tables to the cap and you will be looking at a
prefix of the trail, not the whole of it. When a table is exactly full,
assume there is more.

**Neither number is a retention policy.** `days` bounds the *query*, not
the data. Rows older than the window still exist in `quickdesk_audit`;
they are simply not requested. How long they actually survive is a
property of the `quickdesk_audit` table in your ClickHouse deployment —
its TTL and partition-dropping configuration — and not something this
screen sets or reports. If you need a guaranteed retention period for an
audit or compliance requirement, verify it against the table definition
in your deployment, not against what this screen displays.

## Exporting the trail outside the console

Two routes out, depending on what you need.

**Call the procedures directly.** Both `adminQuickdesk.sessions` and
`adminQuickdesk.files` accept `limit` and `days` as inputs. The console
hardcodes 200/7 and 200/30, but a caller is free to pass different
values:

```ts
const sessions = await trpc.adminQuickdesk.sessions.query({
  limit: 200,
  days: 30,
});

const transfers = await trpc.adminQuickdesk.files.query({
  limit: 200,
  days: 90,
});
```

You get the same reconstructed shapes documented above — pre-paired
sessions, pre-grouped transfers — which is usually what you want for
reporting, since you do not have to re-implement the open/close pairing.
Note that there is no cursor or offset parameter, so `limit` is a hard
ceiling on a single call rather than a page size.

**Query ClickHouse directly.** The underlying rows live in
`quickdesk_audit`. Reading that table yourself is the route for anything
the procedures do not express: arbitrary time ranges beyond a single
call's `limit`, joins against your own inventory data, unpaired-event
analysis, or scheduled extracts into a warehouse. You are then working
with raw events rather than reconstructed sessions, so you own the
grouping on `session_id`.

## Related

- [Telemetry](/platform/telemetry) — the other operational data streams,
  including the ClickHouse CDR pipeline this trail sits alongside.
- [Authentication](/concepts/authentication) — the API key scopes that
  gate the `adminQuickdesk` procedures.
