connect a database
Connect BigQuery
Provisioning read-only credentials for BigQuery — the same instructions Evalyst shows you when a credential check fails.
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
QueryUsagePerDayfor 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.