Ask2DoAsk2Do

Audit log

Every write the assistant executes lands in an ask2do_audit table on your database. The table is owned by you, queryable from any tool you already use, and never touched by the cloud after creation.

Schema

Run this once on the same database the sidecar connects to. The sidecar will populate it on every approved write:

sql
CREATE TABLE IF NOT EXISTS ask2do_audit (
  id              BIGSERIAL PRIMARY KEY,
  tenant_id       BIGINT NOT NULL,
  conversation_id TEXT,
  panel_user_id   TEXT,
  panel_user_role TEXT,
  sql_text        TEXT NOT NULL,
  sql_summary     TEXT,
  status          TEXT NOT NULL,         -- 'success' | 'rejected' | 'failed'
  rows_affected   INTEGER,
  created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Common filters: by user, by status, recent activity.
CREATE INDEX IF NOT EXISTS ask2do_audit_user_idx     ON ask2do_audit(panel_user_id);
CREATE INDEX IF NOT EXISTS ask2do_audit_created_idx  ON ask2do_audit(created_at DESC);
CREATE INDEX IF NOT EXISTS ask2do_audit_status_idx   ON ask2do_audit(status) WHERE status != 'success';

If you skip this step, the sidecar tries to CREATE TABLE automatically when it starts. If the DB role lacks CREATE permission, it falls back to a warning in the logs and reads still work, but writes won't be auditable.

Status values

StatusMeaning
successApproved by an admin and executed against your DB.
rejectedAdmin clicked Cancel on the SQL preview — nothing ran.
failedApproved but the DB returned an error (constraint violation, lock, etc.). Check sql_text for the full SQL.

Sample queries

What did the assistant do today?

sql
SELECT panel_user_id, sql_summary, status, rows_affected, created_at
FROM ask2do_audit
WHERE created_at >= CURRENT_DATE
ORDER BY created_at DESC;

Per-user activity over the last 30 days

sql
SELECT panel_user_id,
       COUNT(*) FILTER (WHERE status = 'success') AS approved,
       COUNT(*) FILTER (WHERE status = 'rejected') AS cancelled,
       SUM(rows_affected) FILTER (WHERE status = 'success') AS rows_changed
FROM ask2do_audit
WHERE created_at >= NOW() - INTERVAL '30 days'
GROUP BY panel_user_id
ORDER BY approved DESC;

Find a specific UPDATE

sql
SELECT panel_user_id, sql_text, rows_affected, created_at
FROM ask2do_audit
WHERE sql_text ILIKE '%UPDATE users SET email%'
ORDER BY created_at DESC
LIMIT 10;

Failed writes (root-cause queue)

sql
SELECT created_at, panel_user_id, sql_summary, sql_text
FROM ask2do_audit
WHERE status = 'failed' AND created_at >= NOW() - INTERVAL '7 days'
ORDER BY created_at DESC;

Retention

We don't enforce retention — rows live as long as you keep them. For most customers we recommend keeping forever (it's the single most useful operational record you'll have). If your compliance regime requires deletion after N years, a one-line cron job:

sql
DELETE FROM ask2do_audit WHERE created_at < NOW() - INTERVAL '7 years';

Tamper-evidence

The table lives on your DB so there's no transport between the act and the record. Tampering would have to come from someone with write access to the audit table itself — usually a DBA. To protect against that: