The database connector lets the patch agent run read-only SELECTs against your own Postgres while it reasons about BUG tickets. The data shows up as a markdown block in the QA notes the CTO handler reads downstream — nothing else. There is no write path, no DDL path, and no execution outside a READ ONLY transaction.
It's off by default. Two tenant-level gates have to clear before a single row reaches the agent: the per-integration Allow AI database context toggle, and a per-shape approval (or the per-integration auto-diagnostics override). Skip either and the agent stays blind.

When to enable
Turn it on when:
- Bug-ticket triage repeatedly stalls on "the AI can't see what state the row is in" and an engineer ends up pasting a SELECT into the chat.
- You have a Postgres replica (or a role on the primary) you'd hand to a junior dev for diagnostics anyway.
- You're willing to curate an allowlist of schemas/tables the AI may read.
Don't turn it on if you can't supply a genuinely SELECT-only role, or if the data sensitivity means a column-name redactor isn't enough on its own.
Only Postgres is supported today.
Gates
| Gate | Where | Default |
|---|---|---|
| Allow AI database context | Per-integration toggle in Settings | off |
| Per-shape approval | Pending approvals queue, reviewed by an admin | none |
The per-integration toggle is the master switch for that database. The per-shape approval is the surgical layer: every structurally distinct query the AI proposes is paused for human review the first time it appears.
An optional Auto read-only diagnostics toggle bypasses the per-shape approval queue for that integration. See below.
Setup
- Provision a Postgres role with
SELECT-only privileges on the data you intend to expose. A read replica is the cleanest source. The connector is also fine on a primary as long as the role really cannot write. - Settings → Integrations → Data sources → Database → Add database. Either the host/port/db/user/password fields or a libpq connection string — when both are filled the connection string wins.
- Pick an SSL mode.
requireis the UI default.disableis allowed but only sensible against a local sandbox. - Populate the schema allowlist before flipping anything on. Empty allowlist is default-deny — every concrete table reference fails the guard.
- Optionally narrow with the table allowlist. Empty means "any table inside the allowed schemas"; otherwise every referenced table must appear here.
- Click Test connection. FixControl probes the credentials and then verifies the role really cannot write by attempting a write inside a READ ONLY transaction. The integration is only marked Read-only verified if that write probe is rejected by the engine. If the probe instead succeeds, you have a role that can write — fix the grants before exposing it.
- Toggle Allow AI database context when satisfied. The integration shows as connected, but the agent stays blind until this is on.
Credentials are stored encrypted with a per-tenant key. Host/port/database/user remain visible in the UI without decrypting the secret.
You can have one active integration per (host, database). Re-adding after delete works.
Allowlist semantics
Default-deny on schemas. Concretely:
- Empty schema allowlist → every query fails with "no schemas are configured for this integration".
- Non-empty schema allowlist → schema-qualified references are checked against it. Unqualified references are treated as
public. - The table allowlist is layered on top. Empty means "no extra table-level filter"; non-empty means every referenced table must appear on it (matched as either bare
tableor fully qualifiedschema.table).
References in subqueries are walked the same way as top-level ones. Even if a reference somehow slips past the schema check, the READ ONLY transaction plus the role grants still block any write.
Approval flow

The agent normalises every proposed SELECT to a shape — the SQL with literal values blanked out — so one approval covers every later run of that same query against different keys. Revoking and re-approving a shape is supported without manual cleanup.
Lifecycle:
- Agent proposes a query for a BUG ticket. FixControl normalises it and looks up an active approval.
- No approval → the shape lands in the pending-approvals queue. The agent skips this query for this run; the markdown block records
(awaiting approval). - Admin reviews the queue at Settings → Integrations → Database → Pending approvals, clicks Approve.
- Subsequent runs of the same shape sail through.
- Revoke stops the shape from running; a future re-approval works fine.
Approve a shape — not a literal query. Approving SELECT id, status FROM orders WHERE id = $1 lets every future run with any id value through. That's the point: bug-ticket reasoning often hits the same query against different keys.
The approval queue flags shapes whose touched tables look sensitive by name (users, customers, payments, tokens, secrets, …) as a UI hint. The runtime redactor catches the actual columns regardless.
Auto-diagnostics — the tradeoff
The Auto read-only diagnostics toggle bypasses the per-shape approval queue for that integration. Every proposed query that passes the allowlist runs immediately and is recorded in the audit as auto-diagnostics. Use it when:
- The allowlist is genuinely tight (a handful of low-sensitivity tables on a replica).
- You want the agent to iterate without a human in the loop on every novel shape.
Don't use it when:
- The allowlist contains tables full of PII the column redactor doesn't know about.
- You want a paper trail of who approved what before any data left the database.
The allowlist guard, the READ ONLY transaction, the row/byte caps, and the redactor still apply. Auto-diagnostics removes only the human-in-the-loop step.
Audit & redaction
Every execution is recorded with the clipped query text, tables touched, row count, bytes returned, truncation flag, redacted column names, duration, error, actor (AI vs human), approval source, and the BUG ticket it ran for. The records are append-only and tenant-scoped.
Result caps:
- 50 rows max per query — anything beyond is truncated.
- 64 KB max stringified payload — first row that crosses the bound triggers truncation.
- 5-second statement timeout and a short lock timeout, both enforced at the engine.
Column-name redaction runs before rows reach the agent. Any column whose name looks like email, phone, password, secret, token, api_key, access_token, refresh_token, ssn, social_security, credit_card, card_number, cvv, iban, or bank_account (with common snake/dash variants) is rewritten to [redacted], and the redacted column names are noted on the audit row. Redaction is column-name based, not value-based — a free-text notes column with an email in it is not caught. If that's a problem for your data, keep that column off the allowlist.
Troubleshooting
"Read-only not verified" badge stays after a successful test. The credentials work, but a write probe inside the READ ONLY transaction was not rejected by the engine. The role still has write or DDL grants. Re-grant as SELECT-only and re-test.
"schema X is not on the allowlist" or "no schemas are configured". Default-deny behaviour. Add the schema to the integration's schema allowlist and retry. If the error mentions a schema you didn't expect (e.g. pg_catalog), the AI proposed a system-table query — leave the allowlist alone, that query should fail.
"forbidden keyword: X" or "writable CTE rejected". A non-SELECT keyword (INSERT, UPDATE, DELETE, MERGE, DROP, COPY, CALL, …) or a writable CTE was rejected by design. The agent rephrases, or you run the query manually. The audit log records the offending keyword.
Query times out. The 5-second cap fired. Either the query is genuinely expensive (add an index on the replica, or trim the query) or the replica is lagging. Diagnostic queries must never block production.
Truncated results on every run. You're hitting the 50-row / 64 KB cap. Tighten the WHERE clause; results are not paginated.
The engine rejected a query as a read-only violation after the integration was working. An allowlist miss reached the engine and Postgres did its job. The integration is automatically marked failed so it surfaces on the dashboard. Inspect the audit entry and tighten the allowlist before re-enabling.