# Configuration reference

> Every scalar accepts the listed `QOD_*` / `PROXY_*` environment-variable override. Sensitive defaults (passwords, secrets, keys) are flagged; rotate them before any non-local deployment.

## `quack-flightsql`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-flightsql.host` | `PROXY_HOST` | `0.0.0.0` |  | FlightSQL edge bind address. |
| `quack-flightsql.port` | `PROXY_PORT` | `31338` |  | FlightSQL edge port. |
| `quack-flightsql.tlsEnabled` | `PROXY_TLS_ENABLED` | `true` |  | Enable TLS on the FlightSQL edge. |
| `quack-flightsql.tlsCertChain` | `PROXY_TLS_CERT_CHAIN` | `certs/server-cert.pem` |  | Path to the TLS certificate chain PEM (auto-generated if missing). |
| `quack-flightsql.tlsPrivateKey` | `PROXY_TLS_PRIVATE_KEY` | `certs/server-key.pem` |  | Path to the TLS private key PEM (auto-generated if missing). |
| `quack-flightsql.sessionTtlSec` | `QOD_SESSION_TTL_SEC` | `3600` |  | Edge session TTL in seconds before a fresh handshake is forced. |
| `quack-flightsql.resumeHoldTimeoutSec` | `PROXY_RESUME_HOLD_TIMEOUT_SEC` | `60` |  | Max seconds the edge holds a statement while a suspended pool cold-starts. |

## `quack-flightsql.acl`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-flightsql.acl.enabled` | `QOD_ACL_ENABLED` | `false` |  | Enable table-level RBAC (per-statement EffectiveSet check). |
| `quack-flightsql.acl.dialect` | `QOD_ACL_DIALECT` | `duckdb` |  | Statement parser dialect for ACL extraction. |
| `quack-flightsql.acl.filteredMetadata` | `QOD_ACL_FILTERED_METADATA` | `true` |  | Implicitly admit reads of the session catalog's information_schema (schemata/tables/columns/views) and filter the result rows to the principal's granted objects. false = the pre-0.6.7 grant-required posture. |

## `quack-flightsql.auth`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-flightsql.auth.roleClaim` | `QOD_AUTH_ROLE_CLAIM` | `role` |  | JWT claim that carries the user's role. |
| `quack-flightsql.auth.database.enabled` | `QOD_AUTH_DB_ENABLED` | `true` |  | Enable database (bcrypt) authentication for the FlightSQL edge. |
| `quack-flightsql.auth.database.jdbcUrl` | `QOD_AUTH_DB_JDBC_URL` | `jdbc:postgresql://localhost:5432/qod` |  | JDBC URL for the auth-lookup database. |
| `quack-flightsql.auth.database.username` | `QOD_AUTH_DB_USER` | `postgres` |  | Username for the auth-lookup JDBC connection. |
| `quack-flightsql.auth.database.password` | `QOD_AUTH_DB_PASSWORD` | `***` | yes | Password for the auth-lookup JDBC connection. |
| `quack-flightsql.auth.database.systemQuery` | `QOD_AUTH_DB_SYSTEM_QUERY` | `SELECT password_hash, kind, enabled, must_change_password FROM qodstate_user WHERE tenant IS NULL AND username = ? LIMIT 1` |  | SQL template for AuthScope.System (empty tenant / superuser=true). MUST return the four mandatory columns (password_hash, kind, enabled, must_change_password) and accepts one ? placeholder for username; matches WHERE tenant IS NULL. |
| `quack-flightsql.auth.database.tenantQuery` | `QOD_AUTH_DB_TENANT_QUERY` | `SELECT password_hash, kind, enabled, must_change_password FROM qodstate_user WHERE tenant = ? AND username = ? LIMIT 1` |  | SQL template for AuthScope.Tenant. MUST return the four mandatory columns (password_hash, kind, enabled, must_change_password) and accepts two ? placeholders in order: tenant, username. |
| `quack-flightsql.auth.keycloak.enabled` | `QOD_AUTH_KEYCLOAK_ENABLED` | `false` |  | Enable the Keycloak OIDC bearer provider. |
| `quack-flightsql.auth.keycloak.baseUrl` | `QOD_AUTH_KEYCLOAK_BASE_URL` | `https://keycloak.example.com` |  | Keycloak base URL (e.g. https://keycloak.example.com). |
| `quack-flightsql.auth.keycloak.realm` | `QOD_AUTH_KEYCLOAK_REALM` | `quack` |  | Keycloak realm name. |
| `quack-flightsql.auth.keycloak.clientId` | `QOD_AUTH_KEYCLOAK_CLIENT_ID` | _(unset)_ |  | Keycloak client ID for ROPC. |
| `quack-flightsql.auth.keycloak.clientSecret` | `QOD_AUTH_KEYCLOAK_CLIENT_SECRET` | `***` | yes | Keycloak client secret. |
| `quack-flightsql.auth.keycloak.issuer` | `QOD_AUTH_KEYCLOAK_ISSUER` | _(unset)_ |  | Override for the expected token issuer (the `iss` claim the bearer validator requires). Leave empty to derive it from baseUrl + realm. Set it when Keycloak's browser-facing issuer (KC_HOSTNAME_URL) differs from the in-cluster baseUrl used to fetch JWKS: tokens minted via an ingress carry iss=https://host/auth/realms/&lt;realm> while the manager fetches JWKS in-cluster. JWKS is always derived from baseUrl, not this value. |
| `quack-flightsql.auth.google.enabled` | `QOD_AUTH_GOOGLE_ENABLED` | `false` |  | Enable the Google OIDC bearer provider. |
| `quack-flightsql.auth.google.clientId` | `QOD_AUTH_GOOGLE_CLIENT_ID` | _(unset)_ |  | Google OAuth client ID. |
| `quack-flightsql.auth.google.clientSecret` | `QOD_AUTH_GOOGLE_CLIENT_SECRET` | `***` | yes | Google OAuth client secret. |
| `quack-flightsql.auth.google.groupsLookup` | `QOD_AUTH_GOOGLE_GROUPS_LOOKUP` | `false` |  | Resolve Google Workspace groups membership server-side. |
| `quack-flightsql.auth.google.serviceAccountKeyPath` | `QOD_AUTH_GOOGLE_SVC_ACCT_KEY_PATH` | _(unset)_ |  | Path to a Google service-account JSON used for groups lookup. |
| `quack-flightsql.auth.google.groupsCacheTtlSeconds` | `QOD_AUTH_GOOGLE_GROUPS_CACHE_TTL_SEC` | `300` |  | Cache TTL for Google groups lookups in seconds. |
| `quack-flightsql.auth.azure.enabled` | `QOD_AUTH_AZURE_ENABLED` | `false` |  | Enable the Azure AD bearer provider. |
| `quack-flightsql.auth.azure.tenantId` | `QOD_AUTH_AZURE_TENANT_ID` | _(unset)_ |  | Azure AD tenant ID. |
| `quack-flightsql.auth.azure.clientId` | `QOD_AUTH_AZURE_CLIENT_ID` | _(unset)_ |  | Azure AD application (client) ID. |
| `quack-flightsql.auth.azure.clientSecret` | `QOD_AUTH_AZURE_CLIENT_SECRET` | `***` | yes | Azure AD application client secret. |
| `quack-flightsql.auth.aws.enabled` | `QOD_AUTH_AWS_ENABLED` | `false` |  | Enable the AWS Cognito bearer provider. |
| `quack-flightsql.auth.aws.region` | `QOD_AUTH_AWS_REGION` | `us-east-1` |  | AWS region hosting the Cognito user pool. |
| `quack-flightsql.auth.aws.userPoolId` | `QOD_AUTH_AWS_USER_POOL_ID` | _(unset)_ |  | Cognito user pool ID. |
| `quack-flightsql.auth.aws.clientId` | `QOD_AUTH_AWS_CLIENT_ID` | _(unset)_ |  | Cognito app client ID. |
| `quack-flightsql.auth.jwt.secretKey` | `JWT_SECRET_KEY` | `***` | yes | HMAC secret for HS256/HS512 external JWT verification. |
| `quack-flightsql.auth.jwt.publicKeyPath` | `JWT_PUBLIC_KEY_PATH` | _(unset)_ |  | Path to the RSA/ECDSA PEM public key for external JWT verification. |
| `quack-flightsql.auth.jwt.issuer` | `JWT_ISSUER` | _(unset)_ |  | Expected 'iss' claim value (empty = not checked). |
| `quack-flightsql.auth.jwt.audience` | `JWT_AUDIENCE` | _(unset)_ |  | Expected 'aud' claim value (empty = not checked). |
| `quack-flightsql.auth.oauthScopes` | `QOD_AUTH_OAUTH_SCOPES` | `openid profile email` |  | OAuth scopes requested at authorization time for the browser SQL-token flow. |

## `quack-flightsql.opa`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-flightsql.opa.defaultMode` | `QOD_ACL_MODE` | `qod` |  | Default data-access authorization mode for tenants without their own: qod (QoD RBAC grants) or opa (the tenant's Open Policy Agent is authoritative). |
| `quack-flightsql.opa.url` | `QOD_OPA_URL` | _(unset)_ |  | Manager-wide OPA base URL used by opa-mode tenants that set none. |
| `quack-flightsql.opa.timeoutMs` | `QOD_OPA_TIMEOUT_MS` | `2000` |  | Per-request OPA timeout; on timeout the request is denied as unavailable. |
| `quack-flightsql.opa.cacheTtlSec` | `QOD_OPA_CACHE_TTL_SEC` | `5` |  | Seconds an OPA allow/deny decision is cached (0 disables the cache). |

## `quack-flightsql.validation`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-flightsql.validation.enabled` | `QOD_VALIDATION_ENABLED` | `true` |  | Enable per-statement SQL validation. |
| `quack-flightsql.validation.allowByDefault` | `QOD_VALIDATION_ALLOW_BY_DEFAULT` | `true` |  | When true, statements pass when no explicit rule matches. |
| `quack-flightsql.validation.bypassUsers` | `QOD_VALIDATION_BYPASS_USERS` | `admin@localhost.local,admin` |  | Comma-separated usernames that skip SQL validation entirely. |

## `quack-native`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-native.enabled` | `QOD_QUACK_ENABLED` | `true` |  | Serve the native Quack protocol front door. |
| `quack-native.host` | `QOD_QUACK_HOST` | `0.0.0.0` |  | Quack front door bind address. |
| `quack-native.port` | `QOD_QUACK_PORT` | `9494` |  | Quack front door port (9494 is the protocol's default, so `quack:host` needs no port). |
| `quack-native.tlsEnabled` | `QOD_QUACK_TLS_ENABLED` | `false` |  | Enable TLS on the Quack front door. Off by default: the DuckDB client only speaks plain HTTP to loopback hosts. |
| `quack-native.tlsCertChain` | `QOD_QUACK_TLS_CERT_CHAIN` | `certs/server-cert.pem` |  | Path to the TLS certificate chain PEM (shared with the FlightSQL edge by default). |
| `quack-native.tlsPrivateKey` | `QOD_QUACK_TLS_PRIVATE_KEY` | `certs/server-key.pem` |  | Path to the TLS private key PEM (PKCS8). |
| `quack-native.maxHeartbeatTimeoutSec` | `QOD_QUACK_MAX_HEARTBEAT_SEC` | `3600` |  | Cap on the heartbeat lease a client may request, in seconds. |
| `quack-native.maxBodyBytes` | `QOD_QUACK_MAX_BODY_BYTES` | `268435456` |  | Largest request body accepted on /quack, in bytes (appends and streamed inserts). |

## `quack-on-demand`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.host` | `QOD_ON_DEMAND_HOST` | `0.0.0.0` |  | Manager REST bind address (0.0.0.0 to listen on all interfaces). |
| `quack-on-demand.port` | `QOD_ON_DEMAND_PORT` | `20900` |  | Manager REST + admin UI port. |
| `quack-on-demand.apiKey` | `QOD_API_KEY` | `***` | yes | Static admin API key sent as X-API-Key. Unset or empty: outside HA a random key is generated at boot and printed to the console; under HA the static-key arm stays disabled and /api accepts only session and PAT credentials (never open). |
| `quack-on-demand.runtimeType` | `QOD_RUNTIME_TYPE` | `local` |  | Quack node runtime backend: 'local' (child processes), 'kubernetes' (pods) or 'fleet' (one node per server joined through qod fleet join). |
| `quack-on-demand.minPort` | `QOD_MIN_PORT` | `21900` |  | Lower bound of the port range LocalQuackBackend allocates child nodes from. |
| `quack-on-demand.maxPort` | `QOD_MAX_PORT` | `22500` |  | Upper bound of the port range LocalQuackBackend allocates child nodes from. |
| `quack-on-demand.maxNodesTotal` | `QOD_MAX_NODES_TOTAL` | `50` |  | Hard cap on concurrent child nodes across all pools. |
| `quack-on-demand.nativeClient` | `QOD_NATIVE_CLIENT` | `true` |  | Use the JNI-backed native Quack wire client. False falls back to the embedded path. |
| `quack-on-demand.stampWrites` | `QOD_STAMP_WRITES` | `true` |  | Stamp DuckLake snapshots created by FlightSQL DML/DDL with author and commit message (native wire bracket; fail-open). Off = writes are never bracketed. |
| `quack-on-demand.nodeDisableSsl` | `QOD_NODE_DISABLE_SSL` | `true` |  | Disable TLS on the embedded path's quack_query() call. Ignored on the native path. |
| `quack-on-demand.spawnScript` | `QOD_SPAWN_SCRIPT` | `./scripts/spawn-quack-node.sh` |  | Path to spawn-quack-node.sh invoked by LocalQuackBackend on Unix. |
| `quack-on-demand.spawnScriptWindows` | `QOD_SPAWN_SCRIPT_WINDOWS` | `./scripts/spawn-quack-node.ps1` |  | Path to the PowerShell spawn script (spawn-quack-node.ps1) invoked by LocalQuackBackend on Windows. |
| `quack-on-demand.drainTimeoutSec` | `QOD_DRAIN_TIMEOUT_SEC` | `60` |  | Seconds to wait for in-flight statements during graceful pool shutdown. |
| `quack-on-demand.healthCheckIntervalSec` | `QOD_HEALTH_CHECK_INTERVAL_SEC` | `5` |  | Seconds between supervisor health checks against child nodes. |
| `quack-on-demand.reconcileIntervalSec` | `QOD_RECONCILE_INTERVAL_SEC` | `30` |  | Seconds between supervisor reconcile passes that respawn dead nodes. 0 disables the periodic loop (reconcile still runs once at boot). |
| `quack-on-demand.publicBaseUrl` | `QOD_PUBLIC_BASE_URL` | _(unset)_ |  | Externally visible base URL (e.g. https://qod.example.com) used to build password-reset links mailed to users. When empty the link is host-relative (/ui/reset-password?...) and Main logs a boot warning. |
| `quack-on-demand.requireEncryption` | `QOD_REQUIRE_ENCRYPTION` | `false` |  | Refuse `database/create` unless the caller asks for encryption at rest. Gates creates only: existing databases are untouched, so turning this on never bricks a running deployment. |
| `quack-on-demand.sessionIdleTtlSec` | `QOD_SESSION_IDLE_TTL_SEC` | `28800` |  | UI session idle TTL in seconds. A session unused for this long is dropped on the next access; each successful access slides the window. Manager restart still invalidates everything (sessions are heap-only). |

## `quack-on-demand.admin`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.admin.username` | `QOD_ADMIN_USERNAME` | `admin@localhost.local,admin` |  | Comma-separated admin usernames seeded into qodstate_user. |
| `quack-on-demand.admin.password` | `QOD_ADMIN_PASSWORD` | `***` | yes | Bootstrap admin password, used only when the admin row does not exist yet; empty (the default) seeds nothing. |
| `quack-on-demand.admin.kind` | `QOD_ADMIN_KIND` | `admin` |  | Account kind (qodstate_user.kind) assigned to the bootstrap admin user. |

## `quack-on-demand.auth`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.auth.management.identitySource` | `QOD_MGMT_IDENTITY_SOURCE` | `db` |  | System-scope (bare /ui/) admin-UI login mode: 'db' (password form) or 'oidc' (SSO). Per-tenant login mode is read from the tenant's authProvider, not this key. |
| `quack-on-demand.auth.management.sessionJwtSecret` | `QOD_SESSION_JWT_SECRET` | `***` | yes | HS256 secret used to sign UI session JWTs. Pin a stable value (>= 32 chars) to make sessions survive manager restart and to share session state across replicas. Empty = autogenerate a fresh 32-byte secret at boot, printed to the console (sessions die on restart; HA refuses to boot). |
| `quack-on-demand.auth.management.sessionCookieSecure` | `QOD_SESSION_COOKIE_SECURE` | `auto` |  | Whether the qod_session cookie carries the `Secure` flag. Accepts 'auto' (default, derives from the request's X-Forwarded-Proto -- https=Secure, http or absent=not Secure), 'true' (force Secure regardless of request scheme; use behind a TLS ingress that strips X-Forwarded-Proto), or 'false' (force not Secure). |
| `quack-on-demand.auth.management.sessionCookiePath` | `QOD_SESSION_COOKIE_PATH` | `/api` |  | Path attribute on the qod_session cookie. Default '/api'. Override when the manager sits behind a path-rewriting reverse proxy: the value must match the BROWSER-visible URL prefix, not the backend's. E.g. proxy at https://platform/quack/api/* -> QOD_SESSION_COOKIE_PATH=/quack/api. |
| `quack-on-demand.auth.management.publicBaseUrl` | `QOD_MGMT_PUBLIC_BASE_URL` | _(unset)_ |  | Externally visible manager base URL (e.g. https://qod.example.com). Used to build OIDC redirect_uri and post_logout_redirect_uri for admin-UI SSO. When empty, derived from X-Forwarded-Proto / X-Forwarded-Host / Host. |
| `quack-on-demand.auth.management.oidc.issuerUrl` | `QOD_MGMT_OIDC_ISSUER_URL` | _(unset)_ |  | OIDC issuer URL for admin-UI SSO (system scope), e.g. https://accounts.google.com or http://keycloak:8080/auth/realms/qod. Discovery reads $&#123;issuerUrl&#125;/.well-known/openid-configuration. Empty disables system-scope SSO. |
| `quack-on-demand.auth.management.oidc.clientId` | `QOD_MGMT_OIDC_CLIENT_ID` | _(unset)_ |  | OIDC client id for admin-UI SSO (system scope). |
| `quack-on-demand.auth.management.oidc.clientSecret` | `QOD_MGMT_OIDC_CLIENT_SECRET` | `***` | yes | OIDC client secret for admin-UI SSO (system scope). |
| `quack-on-demand.auth.management.oidc.scopes` | `QOD_MGMT_OIDC_SCOPES` | `openid email profile` |  | OIDC scopes requested for admin-UI SSO. Default 'openid email profile'. |
| `quack-on-demand.auth.management.slEnabled` | `SL_ENABLED` | `false` |  | Enable the Starlake SSO integration (menu link, ticket endpoints, logout callback) |
| `quack-on-demand.auth.management.slUrl` | `SL_URL` | _(unset)_ |  | Starlake base URL for the SSO handoff and logout callback, e.g. https://starlake.example.com |
| `quack-on-demand.auth.lockout.enabled` | `QOD_AUTH_LOCKOUT_ENABLED` | `false` |  | Lock a database-backed user out after maxFailures consecutive bad passwords. Requires SMTP to be configured (quack-on-demand.smtp.host) so a locked-out user has a self-service reset path; Main refuses to boot otherwise. |
| `quack-on-demand.auth.lockout.maxFailures` | `QOD_AUTH_LOCKOUT_MAX_FAILURES` | `10` |  | Consecutive failed logins before a database-backed user is locked out. |

## `quack-on-demand.autoscale`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.autoscale.enabled` | `QOD_AUTOSCALE_ENABLED` | `true` |  | Global kill switch for the demand scale-out sweep. |
| `quack-on-demand.autoscale.sweepSeconds` | `QOD_AUTOSCALE_SWEEP_SEC` | `60` |  | Sweep interval in seconds; clamped to a 30s floor. |
| `quack-on-demand.autoscale.windowMinutes` | `QOD_AUTOSCALE_WINDOW_MINUTES` | `5` |  | Load window W for the Little's-law concurrency estimate. |
| `quack-on-demand.autoscale.highWatermark` | `QOD_AUTOSCALE_HIGH_WATERMARK` | `0.8` |  | Scale-out utilization threshold. |
| `quack-on-demand.autoscale.lowWatermark` | `QOD_AUTOSCALE_LOW_WATERMARK` | `0.3` |  | Scale-in utilization threshold; must be &lt; highWatermark. |
| `quack-on-demand.autoscale.outStreak` | `QOD_AUTOSCALE_OUT_STREAK` | `2` |  | Consecutive sweeps above highWatermark before adding a reader. |
| `quack-on-demand.autoscale.inStreak` | `QOD_AUTOSCALE_IN_STREAK` | `10` |  | Consecutive sweeps below lowWatermark before removing a reader. |
| `quack-on-demand.autoscale.scaleOutCooldownSec` | `QOD_AUTOSCALE_OUT_COOLDOWN_SEC` | `180` |  | Per-pool cooldown after any scale action or resume before scaling out. |
| `quack-on-demand.autoscale.scaleInCooldownSec` | `QOD_AUTOSCALE_IN_COOLDOWN_SEC` | `600` |  | Per-pool cooldown after any scale action before scaling in. |
| `quack-on-demand.autoscale.assumedConcurrencyPerNode` | `QOD_AUTOSCALE_ASSUMED_CONCURRENCY` | `4` |  | Capacity contribution of a node with maxConcurrent = 0 (unlimited). |
| `quack-on-demand.autoscale.hardCap` | `QOD_AUTOSCALE_HARD_CAP` | `16` |  | Upper bound on maxNodes at validation time; a typo guard, not a quota. |
| `quack-on-demand.autoscale.failureBackoffSweeps` | `QOD_AUTOSCALE_FAILURE_BACKOFF_SWEEPS` | `5` |  | Sweeps to skip a pool after 3 consecutive scale failures. |

## `quack-on-demand.branching`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.branching.enabled` | `QOD_BRANCH_ENABLED` | `true` |  | Global kill switch for branching (endpoints, MCP tools, expiry sweep). |
| `quack-on-demand.branching.defaultTtlHours` | `QOD_BRANCH_DEFAULT_TTL_HOURS` | `168` |  | Default time-to-live of a branch in hours when the creator sets none; an expired branch is discarded by the leader's sweep. 0 = branches never expire by default. |
| `quack-on-demand.branching.sweepSec` | `QOD_BRANCH_SWEEP_SEC` | `300` |  | Expiry sweep interval in seconds; clamped to a 60s floor. |
| `quack-on-demand.branching.maxPerDatabase` | `QOD_BRANCH_MAX_PER_DATABASE` | `20` |  | Maximum live (open or proposed) branches per tenant-db. |
| `quack-on-demand.branching.mergeTimeoutSec` | `QOD_BRANCH_MERGE_TIMEOUT_SEC` | `600` |  | Bounded wait for the merge transaction on the ephemeral merge node before the request fails; the commit is probed afterwards so a late commit is still recorded. |
| `quack-on-demand.branching.nodeReadyTimeoutSec` | `QOD_BRANCH_NODE_READY_TIMEOUT_SEC` | `120` |  | How long to wait for the ephemeral merge node to accept connections. |

## `quack-on-demand.catalog`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.catalog.auditCatalogReads` | `QOD_AUDIT_CATALOG_READS` | `false` |  | Audit catalog browser reads: one catalog.read event per gated GET. Off by default (reads are chatty; mutations are always audited). |
| `quack-on-demand.catalog.previewMaxRows` | `QOD_CATALOG_PREVIEW_MAX_ROWS` | `1000` |  | Hard cap on rows returned by the catalog data-preview endpoint. |
| `quack-on-demand.catalog.previewTimeoutSec` | `QOD_CATALOG_PREVIEW_TIMEOUT_SEC` | `30` |  | Seconds before a catalog data-preview query is cancelled. |
| `quack-on-demand.catalog.icebergDiffMaxFiles` | `QOD_CATALOG_ICEBERG_DIFF_MAX_FILES` | `200` |  | Refuse an Iceberg data diff when either snapshot has more data files than this: the diff is a full EXCEPT scan of both versions. |
| `quack-on-demand.catalog.undropTimeoutSec` | `QOD_CATALOG_UNDROP_TIMEOUT_SEC` | `300` |  | Seconds before an undrop recovery CTAS is abandoned. Larger than the preview timeout because it is a mutation over potentially large tables; on timeout the handler probes whether the table was created anyway and reports accordingly. |
| `quack-on-demand.catalog.restoreTimeoutSec` | `QOD_CATALOG_RESTORE_TIMEOUT_SEC` | `300` |  | Seconds before a restore CREATE OR REPLACE is abandoned. On timeout the handler probes whether the replace committed anyway and reports accordingly. |

## `quack-on-demand.defaultMetastore`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.defaultMetastore.pgHost` | `QOD_PG_HOST` | `localhost` |  | Postgres host for control plane + DuckLake catalog. |
| `quack-on-demand.defaultMetastore.pgPort` | `QOD_PG_PORT` | `5432` |  | Postgres port. |
| `quack-on-demand.defaultMetastore.pgUser` | `QOD_PG_USER` | `postgres` |  | Postgres username used by the manager + Quack nodes. |
| `quack-on-demand.defaultMetastore.pgPassword` | `QOD_PG_PASSWORD` | `***` | yes | Postgres password. |
| `quack-on-demand.defaultMetastore.dbName` | `QOD_PG_DBNAME` | `qod` |  | Control-plane database name (default 'qod'). |
| `quack-on-demand.defaultMetastore.schemaName` | `QOD_PG_SCHEMA` | `main` |  | Postgres schema for control-plane tables. |
| `quack-on-demand.defaultMetastore.dataPath` | `QOD_DUCKLAKE_DATA_PATH` | `./ducklake/tpch` |  | Root path for DuckLake parquet data files. |

## `quack-on-demand.embeddedPostgres`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.embeddedPostgres.enabled` | `QOD_PG_EMBEDDED` | `false` |  | Run the control plane on a bundled embedded Postgres rooted at dataDir instead of an external server. Single-node evaluation / small-team mode; refused under HA. |
| `quack-on-demand.embeddedPostgres.port` | `QOD_PG_EMBEDDED_PORT` | `25432` |  | Fixed TCP port for the embedded Postgres. Fixed rather than OS-assigned so the coordinates stay stable across restarts and psql works for support. |
| `quack-on-demand.embeddedPostgres.dataDir` | `QOD_PG_EMBEDDED_DATA_DIR` | _(unset)_ |  | Directory holding the embedded Postgres data directory. Empty means the platform user-data dir (&lt;user-data-dir>/pg). |

## `quack-on-demand.federation`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.federation.secretStore` | `QOD_FEDERATION_SECRET_STORE` | `dispatch` |  | Federation secret resolver: postgres \| env \| aws-sm \| gcp-sm \| azure-kv \| vault. |

## `quack-on-demand.fleet`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.fleet.joinToken` | `QOD_FLEET_JOIN_TOKEN` | `***` | yes | Shared secret every fleet heartbeat carries. Required when runtimeType=fleet. |
| `quack-on-demand.fleet.heartbeatSec` | `QOD_FLEET_HEARTBEAT_SEC` | `5` |  | Heartbeat interval in seconds; returned to every qod fleet join process in each reply. |
| `quack-on-demand.fleet.heartbeatTimeoutSec` | `QOD_FLEET_HEARTBEAT_TIMEOUT_SEC` | `30` |  | Silence beyond this many seconds marks a server unreachable. |
| `quack-on-demand.fleet.reassignAfterSec` | `QOD_FLEET_REASSIGN_AFTER_SEC` | `600` |  | Unreachable beyond this many seconds moves the server's node slot to a free server. 0 = at once, -1 = never. |
| `quack-on-demand.fleet.startupTimeoutSec` | `QOD_FLEET_STARTUP_TIMEOUT_SEC` | `120` |  | How long a claim waits for the server to report the node running. |
| `quack-on-demand.fleet.stopTimeoutSec` | `QOD_FLEET_STOP_TIMEOUT_SEC` | `60` |  | How long a release waits for the server to report the node stopped. |
| `quack-on-demand.fleet.ephemeral` | `QOD_FLEET_EPHEMERAL` | `fleet` |  | Where maintenance and branch-merge nodes run: 'fleet' claims a server like any node, 'local' runs them on the manager host through the local backend. |
| `quack-on-demand.fleet.autoApprove` | `QOD_FLEET_AUTO_APPROVE` | `0.0.0.0/0,::/0` |  | Comma-separated CIDRs: a server whose heartbeat comes from one of them is approved on join; any other waits for `qod fleet approve`. Empty = approve none automatically. The default admits every address. |
| `quack-on-demand.fleet.trustedProxies` | `QOD_FLEET_TRUSTED_PROXIES` | _(unset)_ |  | Comma-separated CIDRs of proxies / load balancers whose X-Forwarded-For is believed when resolving a heartbeat's client address. Empty = believe none. |
| `quack-on-demand.fleet.nodePgHost` | `QOD_FLEET_NODE_PG_HOST` | _(unset)_ |  | Address fleet servers dial for the managed Postgres, when it differs from the manager's own (e.g. the manager reaches it by a container-only name). Replaces pgHost in fleet assignments of databases on the default metastore only. Empty = unchanged. |
| `quack-on-demand.fleet.nodePgPort` | `QOD_FLEET_NODE_PG_PORT` | _(unset)_ |  | Port fleet servers dial for the managed Postgres; replaces pgPort only where it equals the default metastore's. Empty = unchanged. |

## `quack-on-demand.ha`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.ha.enabled` | `QOD_HA_ENABLED` | `false` |  | Enable active-active multi-replica manager mode (kubernetes or fleet runtime). |
| `quack-on-demand.ha.leaderRetrySec` | `QOD_LEADER_RETRY_SEC` | `3` |  | Seconds between leader-lock acquisition attempts and LISTEN polls. |
| `quack-on-demand.ha.topologyRefreshSec` | `QOD_TOPOLOGY_REFRESH_SEC` | `30` |  | Seconds between snapshot-refresh fallback passes in HA mode. |

## `quack-on-demand.hibernation`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.hibernation.enabled` | `QOD_HIBERNATE_ENABLED` | `true` |  | Global kill switch for the idle-pool hibernation sweep. |
| `quack-on-demand.hibernation.sweepSeconds` | `QOD_HIBERNATE_SWEEP_SEC` | `300` |  | Sweep interval in seconds; clamped to a 60s floor. |
| `quack-on-demand.hibernation.defaultIdleMinutes` | `QOD_HIBERNATE_IDLE_MIN` | `0` |  | Manager-wide default idle minutes before a running pool is suspended. 0 (the default) means hibernation is per-pool opt-in via idleTimeoutSec. Clamped to a 5-minute floor: activity timestamps lag by up to one sweep interval. |

## `quack-on-demand.k8s`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.k8s.namespace` | `QOD_K8S_NAMESPACE` | `default` |  | Kubernetes namespace KubernetesQuackBackend operates in. |
| `quack-on-demand.k8s.image` | `QOD_K8S_IMAGE` | `starlakeai/quack-on-demand-node:latest-snapshot` |  | Docker image used for spawned Quack-node pods. |
| `quack-on-demand.k8s.serviceAccount` | `QOD_K8S_SERVICE_ACCOUNT` | _(unset)_ |  | ServiceAccount applied to spawned node pods (unset = default). |
| `quack-on-demand.k8s.serviceType` | `QOD_K8S_SERVICE_TYPE` | `ClusterIP` |  | Kubernetes Service type fronting node pods. |
| `quack-on-demand.k8s.quackPort` | `QOD_K8S_QUACK_PORT` | `8080` |  | Container port exposing each node's /quack endpoint. |
| `quack-on-demand.k8s.startupTimeoutSec` | `QOD_K8S_STARTUP_TIMEOUT_SEC` | `120` |  | Seconds to wait for a spawned node pod to become ready. |
| `quack-on-demand.k8s.stopTimeoutSec` | `QOD_K8S_STOP_TIMEOUT_SEC` | `60` |  | Seconds stop() waits for a deleted node pod to actually disappear before proceeding. |
| `quack-on-demand.k8s.podLabel` | `QOD_K8S_POD_LABEL` | `managed-by=quack-on-demand` |  | Label selector that identifies manager-owned node pods. |
| `quack-on-demand.k8s.podTemplateEnabled` | `QOD_POD_TEMPLATE_ENABLED` | `false` |  | Allow superusers to supply a full Pod-manifest YAML template for a pool's node pods. Off by default; raw manifests are cluster-level power. |
| `quack-on-demand.k8s.runAsUser` | `QOD_K8S_RUN_AS_USER` | `1000` |  | Pod-level runAsUser/fsGroup applied to spawned node pods. A pod template's own securityContext.runAsUser (if set) wins over this default. |

## `quack-on-demand.maintenance`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.maintenance.enabled` | `QOD_MAINT_ENABLED` | `true` |  | Enable the maintenance scheduler + drain-loop fibers. |
| `quack-on-demand.maintenance.tickSec` | `QOD_MAINT_TICK_SEC` | `60` |  | Seconds between maintenance scheduler ticks (cadence + threshold checks). |
| `quack-on-demand.maintenance.maxConcurrent` | `QOD_MAINT_MAX_CONCURRENT` | `2` |  | Max maintenance runs executing concurrently across the manager. |
| `quack-on-demand.maintenance.minIntervalMin` | `QOD_MAINT_MIN_INTERVAL_MIN` | `30` |  | Minimum minutes between non-manual maintenance runs of the same tenant-db. |
| `quack-on-demand.maintenance.runTimeoutMin` | `QOD_MAINT_RUN_TIMEOUT_MIN` | `60` |  | Minutes without a heartbeat before a running maintenance run is swept as failed. |
| `quack-on-demand.maintenance.nodeReadyTimeoutSec` | `QOD_MAINT_NODE_READY_TIMEOUT_SEC` | `180` |  | Seconds to wait for the ephemeral maintenance node to accept connections after spawn before the run is failed as 'node spawn failed'. Covers cold-start extension installs. |

## `quack-on-demand.managedObjectStore`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.managedObjectStore.enabled` | `QOD_MANAGED_STORE_ENABLED` | `false` |  | Global kill switch for managed object storage. Off = managed creates 400. |
| `quack-on-demand.managedObjectStore.endpoint` | `QOD_MANAGED_STORE_ENDPOINT` | _(unset)_ |  | S3-compatible endpoint URL for the operator root bucket. |
| `quack-on-demand.managedObjectStore.region` | `QOD_MANAGED_STORE_REGION` | `us-east-1` |  | Region passed to the S3-compatible client. |
| `quack-on-demand.managedObjectStore.bucket` | `QOD_MANAGED_STORE_BUCKET` | `qod-managed` |  | Operator root bucket; each tenant-db incarnation gets its own prefix. |
| `quack-on-demand.managedObjectStore.accessKeyId` | `QOD_MANAGED_STORE_ACCESS_KEY_ID` | _(unset)_ |  | Access key id for the operator root bucket. |
| `quack-on-demand.managedObjectStore.secretAccessKey` | `QOD_MANAGED_STORE_SECRET_ACCESS_KEY` | `***` | yes | Secret access key for the operator root bucket. |
| `quack-on-demand.managedObjectStore.urlStyle` | `QOD_MANAGED_STORE_URL_STYLE` | `path` |  | Bucket addressing style presented to clients: 'path' or 'vhost'. |
| `quack-on-demand.managedObjectStore.retainDays` | `QOD_MANAGED_STORE_RETAIN_DAYS` | `7` |  | Days a deleted tenant-db prefix is retained before the purge worker removes it. |
| `quack-on-demand.managedObjectStore.purgeSweepSec` | `QOD_MANAGED_STORE_PURGE_SWEEP_SEC` | `300` |  | Purge worker sweep interval in seconds; clamped to a 60s floor. |

## `quack-on-demand.mcp`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.mcp.enabled` | `QOD_MCP_ENABLED` | `true` |  | Serve the MCP endpoint at POST /mcp. |
| `quack-on-demand.mcp.maxRows` | `QOD_MCP_MAX_ROWS` | `500` |  | Hard cap on rows returned by the run_sql MCP tool. |

## `quack-on-demand.metrics`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.metrics.sink` | `QOD_METRICS_SINK` | `prometheus` |  | Active metrics sink: prometheus \| aws \| azure \| gcp \| none. |
| `quack-on-demand.metrics.aws.namespace` | `QOD_METRICS_AWS_NAMESPACE` | `quack-on-demand` |  | CloudWatch namespace when metrics.sink=aws. |
| `quack-on-demand.metrics.aws.stepSeconds` | `QOD_METRICS_AWS_STEP_SEC` | `60` |  | CloudWatch publish step in seconds. |
| `quack-on-demand.metrics.azure.instrumentationKey` | `QOD_METRICS_AZURE_KEY` | `***` | yes | Azure Monitor instrumentation key. |
| `quack-on-demand.metrics.azure.stepSeconds` | `QOD_METRICS_AZURE_STEP_SEC` | `60` |  | Azure Monitor publish step in seconds. |
| `quack-on-demand.metrics.gcp.projectId` | `QOD_METRICS_GCP_PROJECT_ID` | _(unset)_ |  | GCP project ID when metrics.sink=gcp. |
| `quack-on-demand.metrics.gcp.stepSeconds` | `QOD_METRICS_GCP_STEP_SEC` | `60` |  | GCP Cloud Monitoring publish step in seconds. |

## `quack-on-demand.pat`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.pat.maxDepth` | `QOD_PAT_MAX_DEPTH` | `8` |  | Max depth of a PAT delegation chain (root = 0). Operational backstop on the revocation cascade and row growth, not a security boundary -- narrow() already bounds privilege. |

## `quack-on-demand.routing`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.routing.cacheAware` | `QOD_ROUTING_CACHE_AWARE` | `true` |  | Cache-aware placement on object-store pools. false instantly reverts routing decisions to pure least-loaded; locality metrics and their memoized statement parse keep running. |
| `quack-on-demand.routing.loadCapFactor` | `QOD_ROUTING_LOAD_CAP_FACTOR` | `2.0` |  | Load cap c: a table's home node is bypassed when its inFlight exceeds c x pool average. |
| `quack-on-demand.routing.directoryMaxTables` | `QOD_ROUTING_DIRECTORY_MAX_TABLES` | `4096` |  | Per-pool bound on placement-directory entries (LRU-evicted). Safety bound. |

## `quack-on-demand.smtp`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.smtp.host` | `QOD_SMTP_HOST` | _(unset)_ |  | SMTP relay host. Unset keeps the manager on the log-only mail sender. |
| `quack-on-demand.smtp.port` | `QOD_SMTP_PORT` | `587` |  | SMTP relay port. |
| `quack-on-demand.smtp.user` | `QOD_SMTP_USER` | _(unset)_ |  | SMTP auth username. Unset disables SMTP auth. |
| `quack-on-demand.smtp.password` | `QOD_SMTP_PASSWORD` | `***` | yes | SMTP auth password. |
| `quack-on-demand.smtp.from` | `QOD_SMTP_FROM` | `no-reply@quack-on-demand.local` |  | From address stamped on outgoing mail. |
| `quack-on-demand.smtp.starttls` | `QOD_SMTP_STARTTLS` | `true` |  | Use STARTTLS when connecting to the SMTP relay. |

## `quack-on-demand.telemetry`

| Key | Env var | Default | Sensitive | Description |
| --- | --- | --- | --- | --- |
| `quack-on-demand.telemetry.store` | `QOD_TELEMETRY_STORE` | `postgres` |  | Telemetry store backing audit log (and, later, history/usage): postgres \| none (record nothing). |
| `quack-on-demand.telemetry.auditRetentionDays` | `QOD_AUDIT_RETENTION_DAYS` | `90` |  | Days to keep audit events before the hourly purge deletes them. |
| `quack-on-demand.telemetry.journalCapacity` | `QOD_TELEMETRY_JOURNAL_CAPACITY` | `8192` |  | Bounded in-process telemetry journal capacity; overflow drops events (counted). |
| `quack-on-demand.telemetry.stmtHistoryRetentionDays` | `QOD_STMT_HISTORY_RETENTION_DAYS` | `7` |  | Days to keep statement-history rows before the periodic purge removes them. |
| `quack-on-demand.telemetry.hourlyRollupRetentionDays` | `QOD_HOURLY_ROLLUP_RETENTION_DAYS` | `90` |  | Days to keep hourly rollup buckets before the periodic purge removes them. |
| `quack-on-demand.telemetry.rollupIntervalSec` | `QOD_ROLLUP_INTERVAL_SEC` | `300` |  | Seconds between rollup computation passes that aggregate raw statement history into rollup buckets. |
| `quack-on-demand.telemetry.usageRetentionDays` | `QOD_USAGE_RETENTION_DAYS` | `400` |  | Days to keep daily rollup buckets (the usage-accounting ledger) before the periodic purge removes them. 400 covers a full billing year. |
