# Approval workflow

> Roles, connection grants, and the lifecycle of a query request from submission through approval to masked, audited results.

- Documentation: Guides
- Updated: 2026-10-10
- Source: https://queryproxy.com/docs/approval-workflow/
- Language: en-US
- Author: Muhammet Şafak

---
Everything in QueryProxy revolves around one object: the **query request**. This
guide walks through who can do what, and what happens to a request at each step.

## Roles

| Role | Can do |
| :-- | :-- |
| **Admin** | Everything, system-wide: teams, users, all settings. Set per user (`is_admin`). |
| **DBA** | Manage connections and masking rules, grant access, approve/reject requests; can also submit queries on any of the team's connections. |
| **Developer** | Submit queries through granted connections, view own (masked) results. |
| **Auditor** | Read-only access to the audit log (and its CSV export) and to the team's Requests list and request detail (SQL, status, requester, reviewer, approval trace, DDL flag); cannot view or download result rows, and has no access to connections, approvals, masking or ChatOps settings. |

Admin is global; DBA, Developer and Auditor are **per team**. A user can belong
to several teams with different roles. Connections, requests, masking rules and
audit logs never cross team boundaries.

The audit log is reserved to the Auditor and the Admin; a DBA has no access to it.

## Connections and grants

DBAs register target databases under **Connections**. Credentials (host, port,
database, username, password) are encrypted at rest with AES-256. The password
is never shown again after saving — the edit form leaves it blank, and leaving
it blank keeps the stored one — and credentials never appear in logs or error
messages either.

A developer sees a connection only after a DBA **grants** it to them
(Connections → Grants). Ungranted connections are invisible in Query Studio.

## The request lifecycle

```
pending ──▶ approved ──▶ queued ──▶ running ──▶ completed
   │                                              │
   ├──▶ rejected  (reason required)               └──▶ failed
   └──▶ cancelled (by the requester or an admin)
```

- **Submit.** The developer writes SQL in Query Studio. The
  [SQL guards](/docs/sql-guards/) validate it before submission — requests
  that violate a guard never enter the queue.
- **Pending.** The request waits indefinitely until someone decides; there is
  no auto-expiry. Team DBAs are notified in the portal and, if configured, in
  [Slack](/docs/slack-integration/) / [Teams](/docs/teams-integration/).
- **Approve / Reject.** Any team DBA can decide — except on their **own**
  requests: self-approval is blocked. Admins may override that block, and the
  override is flagged in the audit log. Rejection requires a written reason.
  Deciding from chat follows the same rules, and each Approve / Reject control
  there carries a **single-use token** bound to that one request and valid for
  24 hours, so a decision taken in chat can only answer a message QueryProxy
  actually posted, and only once. The resolved approver must also be a
  **member of the integration's team**.
- **Execute.** Approved requests are queued and picked up by the worker. Reads
  stream through a database cursor into a result file, with
  [masking](/docs/data-masking/) applied to every row before it is written.
  Writes report affected row counts; multi-statement requests run atomically
  and roll back entirely if any statement fails.
- **Results.** The requester (and the team's DBAs) can browse results
  page-by-page or download them as CSV. Auditors see request metadata, history
  and approval traces — not the result data itself. Result files are pruned
  automatically after the configured retention period.

## Notifications

Requesters are notified when their request is decided and when execution
finishes; DBAs are notified of new submissions. Notifications appear in the
portal's bell menu. New submissions and decisions are also announced in your chat
channel when it is configured; the execution-finished notification goes to the
requester in the portal only, not to Slack or Teams.

## The audit trail

Every step above writes an audit entry: submission, decision (with channel and
override flag), execution start/finish (with duration and affected rows),
result downloads, plus logins, grants and configuration changes. Audit rows are
immutable — the model layer refuses updates and deletes. Auditors can filter
the trail and export it as CSV.

Entries record the client IP as QueryProxy sees it. Behind a reverse proxy that
is the proxy's own address unless you list it in `TRUSTED_PROXIES` — see
[Configuration](/docs/configuration/).
