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:
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
| Status | Meaning |
|---|---|
success | Approved by an admin and executed against your DB. |
rejected | Admin clicked Cancel on the SQL preview — nothing ran. |
failed | Approved 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?
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
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
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)
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:
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:
- Grant the sidecar's DB role
INSERTonask2do_auditbut notUPDATEorDELETE. - Copy rows to a write-once log (S3, append-only Postgres) on a schedule. A built-in option for this is on the roadmap.