connect a database

Connect BigQuery

Provisioning read-only credentials for BigQuery — the same instructions Evalyst shows you when a credential check fails.

source of truth: app repo docs/readonly/bigquery.md

BigQuery bills by bytes scanned, so an unbounded SELECT * on a partitioned petabyte table is a four-figure mistake made in one keystroke. Read-only access is half the job here; the scan cap is the other half, and Evalyst enforces it on every query it submits.

1. Service account with read-only roles

PROJECT=<your-project>
gcloud iam service-accounts create evalyst-reader \
  --project "$PROJECT" --display-name "Evalyst (read-only)"

SA="evalyst-reader@${PROJECT}.iam.gserviceaccount.com"

# read table DATA and metadata — but not create, update, or delete anything
gcloud projects add-iam-policy-binding "$PROJECT" \
  --member "serviceAccount:${SA}" --role roles/bigquery.dataViewer
# submit query jobs (this is a job role, not a data role — it grants no read of any table)
gcloud projects add-iam-policy-binding "$PROJECT" \
  --member "serviceAccount:${SA}" --role roles/bigquery.jobUser

gcloud iam service-accounts keys create evalyst-reader.json \
  --iam-account "$SA" --project "$PROJECT"

Do not grant roles/bigquery.dataEditor, dataOwner, admin, or user — roles/bigquery.user includes bigquery.datasets.create, and Evalyst’s verify step will refuse the source because of it.

To scope tighter than the whole project, skip the project-level dataViewer and grant it per dataset instead:

bq add-iam-policy-binding --member "serviceAccount:${SA}" \
   --role roles/bigquery.dataViewer "${PROJECT}:<dataset>"

then list the datasets in the source config so discovery does not wander: "datasets": ["analytics", "billing"].

2. Source config

{
  "project": "<your-project>",
  "location": "US",
  "datasets": ["analytics"],
  "cost_caps": {
    "maximum_bytes_billed": 21474836480,
    "statement_timeout_ms": 60000
  }
}

maximum_bytes_billed defaults to 20 GB per query if you omit it. A query that would exceed the cap fails before scanning, and the error names the estimate — which is the useful outcome: the agent narrows the range and retries instead of you finding out on the invoice.

The credential is one vault field, GOOGLE_SERVICE_ACCOUNT_JSON — paste the whole evalyst-reader.json blob into the credentials modal. It never passes through chat.

3. Check it yourself

gcloud auth activate-service-account --key-file evalyst-reader.json
bq query --use_legacy_sql=false 'SELECT COUNT(*) FROM `<project>.<dataset>.<table>`'   # works
bq mk --table <project>:<dataset>.probe i:INTEGER                                      # must fail

Evalyst’s own check is cloudresourcemanager.testIamPermissions for bigquery.tables.updateData, tables.delete, tables.create and datasets.update — the authoritative answer about inherited bindings, which reading role names cannot give you. If that API is not reachable it falls back to a DML privilege oracle (UPDATE … WHERE FALSE against a table that does not exist) that scans nothing and writes nothing.

4. Extra guard rails worth setting

  • A custom quota on QueryUsagePerDay for the service account, in the project’s quota page — a ceiling Evalyst cannot raise even if its own cap is misconfigured.
  • A billing budget alert on the project.