# Self-hosting Configuration

Configure Provon through environment variables and runtime bindings. Keep deployment coordinates in
version control, keep secret values in a secret manager, and use the same semantic configuration
across staging and production.

## Critical Secret

`AUTH_SECRET` is required in production. `PROVON_AUTH_SECRET` is an equivalent fallback, but use one
name consistently.

This value:

- signs authentication state and sessions;
- derives application-level encryption for stored OAuth tokens, provider credentials, and Connector
  secrets;
- must be identical across runtime surfaces that read the same metadata store.

Generate at least 32 random bytes:

```bash
openssl rand -base64 32
```

Store and back it up as encryption key material. Replacing it invalidates sessions and can make
existing encrypted credentials unreadable. Do not rotate it without an explicit credential
re-encryption and user reauthorization plan.

## Public Origin And HTTP

| Variable                    | Runtime | Purpose                                                       |
| --------------------------- | ------- | ------------------------------------------------------------- |
| `PROVON_HTTP_HOST`          | Node    | Bind address; defaults to `127.0.0.1`                         |
| `PROVON_HTTP_PORT`          | Node    | Listen port; defaults to `3000`                               |
| `PROVON_SERVE_WORKBENCH`    | Node    | Set to `0` only for an intentional API-only host              |
| `PROVON_AUTH_ORIGIN`        | Both    | Exact public origin used for OAuth callbacks                  |
| `PROVON_WORKBENCH_ORIGINS`  | Both    | Comma-separated exact browser origins allowed to use the API  |
| `PROVON_AUTH_COOKIE_DOMAIN` | Both    | Shared parent cookie domain for split subdomains              |
| `PROVON_AUTH_TRUST_HOST`    | Both    | Trust forwarded public host when set to `true` behind a proxy |
| `PROVON_VERSION`            | Both    | Version reported by `/healthz`                                |

For a same-origin Node deployment:

```bash
PROVON_AUTH_ORIGIN=https://provon.example.com
PROVON_WORKBENCH_ORIGINS=https://provon.example.com
PROVON_AUTH_TRUST_HOST=true
```

For split Cloudflare origins:

```bash
PROVON_AUTH_ORIGIN=https://api.provon.example
PROVON_WORKBENCH_ORIGINS=https://app.provon.example
PROVON_AUTH_COOKIE_DOMAIN=.provon.example
PROVON_AUTH_TRUST_HOST=true
```

Only trust forwarded hosts when the ingress removes client-supplied forwarding headers and writes
its own values. Forwarded origins that do not exactly match `PROVON_AUTH_ORIGIN` are ignored.

## Node Storage

| Variable                        | Default                                | Purpose                                       |
| ------------------------------- | -------------------------------------- | --------------------------------------------- |
| `PROVON_META_DB_URL`            | `libsql:.provon/data/meta.db`          | Metadata database URL                         |
| `PROVON_META_DB_AUTH_TOKEN`     | None                                   | Remote metadata libSQL authentication         |
| `PROVON_METERING_DB_URL`        | `libsql:.provon/data/metering.db`      | Billable usage event database URL             |
| `PROVON_METERING_DB_AUTH_TOKEN` | None                                   | Remote metering libSQL authentication         |
| `PROVON_TELEMETRY_DB_URL`       | `duckdb:.provon/data/telemetry.duckdb` | `duckdb:<path>` or `motherduck:<database>`    |
| `PROVON_BLOBS_DIR`              | `.provon/blobs`                        | Blobs, staged payloads, and runtime data root |
| `PROVON_MODELS_DIR`             | Sibling `models` directory             | Model weights and artifacts                   |

Metadata, metering, and telemetry must use distinct database URLs.

The directory containing `PROVON_BLOBS_DIR` also holds persistent Node runtime state. Mount or
back up the parent data directory, not only the visible blob files.

## Model Services

The Node runtime can optionally control standalone Python services for local inference and
fine-tuning. These services run outside the Node process and communicate over HTTP.

| Variable                            | Default                 | Purpose                                     |
| ----------------------------------- | ----------------------- | ------------------------------------------- |
| `PROVON_PYTHON_INFERENCE_URL`       | none                    | Base URL of `services/python-inference`     |
| `PROVON_PYTHON_INFERENCE_API_KEY`   | none                    | Shared bearer token for inference service   |
| `PROVON_PYTHON_FINE_TUNING_URL`     | `http://127.0.0.1:8001` | Base URL of `services/python-fine-tuning`   |
| `PROVON_PYTHON_FINE_TUNING_API_KEY` | none                    | Shared bearer token for fine-tuning service |

When the inference URL is unset, the runtime still operates but cannot serve `self/` targets. When
the fine-tuning URL is unset, fine-tuning API routes return `503`.

The Python services themselves accept:

- `PROVON_HOST` / `PROVON_PORT` for binding;
- `PROVON_API_KEY` for authentication;
- `PROVON_MODELS_DIR` for the inference model cache;
- `PROVON_LLAMA_CPP_PATH` for GGUF export in the fine-tuning service.

### MotherDuck

| Variable                                       | Purpose                                                  |
| ---------------------------------------------- | -------------------------------------------------------- |
| `MOTHERDUCK_TOKEN`                             | Shared telemetry database access                         |
| `MOTHERDUCK_ADMIN_TOKEN`                       | Organization-isolated database and service-account setup |
| `MOTHERDUCK_HOST`                              | Postgres endpoint override                               |
| `MOTHERDUCK_PORT`                              | Postgres endpoint port                                   |
| `MOTHERDUCK_USER`                              | Postgres endpoint user                                   |
| `MOTHERDUCK_SESSION_HINT`                      | Optional session routing hint                            |
| `PROVON_TELEMETRY_TENANT_ISOLATION`            | `organization` or `shared`; defaults to `organization`   |
| `MOTHERDUCK_SERVICE_ACCOUNT_TOKEN_TTL_SECONDS` | Generated organization token lifetime                    |
| `MOTHERDUCK_DUCKLING_SIZE`                     | Optional organization compute size                       |
| `MOTHERDUCK_READ_SCALING_POOL_SIZE`            | Optional read scaling pool size                          |

Use:

```bash
PROVON_TELEMETRY_DB_URL=motherduck:provon-telemetry
PROVON_TELEMETRY_TENANT_ISOLATION=shared
MOTHERDUCK_TOKEN=...
```

for one shared MotherDuck database. Use the default `organization` isolation with
`MOTHERDUCK_ADMIN_TOKEN` when each organization should receive isolated MotherDuck resources.

## Cloudflare Telemetry

The Cloudflare runtime requires:

| Variable or binding                           | Purpose                                                                    |
| --------------------------------------------- | -------------------------------------------------------------------------- |
| `PROVON_TELEMETRY_BACKEND`                    | Must be `cloudflare-r2-sql`                                                |
| `PROVON_R2_SQL_ACCOUNT_ID`                    | Cloudflare account containing telemetry                                    |
| `PROVON_R2_SQL_BUCKET`                        | Data Catalog-enabled telemetry bucket                                      |
| `PROVON_R2_SQL_NAMESPACE`                     | Base R2 SQL namespace                                                      |
| `PROVON_R2_SQL_TOKEN`                         | R2 SQL API token secret                                                    |
| `PROVON_R2_CATALOG_URI`                       | Iceberg REST catalog URI for the warehouse runtime                         |
| `PROVON_R2_CATALOG_WAREHOUSE`                 | Data Catalog warehouse name                                                |
| `PROVON_R2_DATA_CATALOG_TOKEN`                | Read-write catalog token for telemetry mutations                           |
| `PROVON_TELEMETRY_WAREHOUSE_CACHE_MAX_TABLES` | Maximum per-container table runtimes retained in memory; defaults to `256` |
| `PROVON_BLOB_BUCKET`                          | R2 binding for staging and blobs                                           |
| `PROVON_TELEMETRY_WAREHOUSE_EXECUTOR`         | Internal Service Binding for telemetry mutations                           |

The default read model is:

```toml
PROVON_TELEMETRY_QUERY_SOURCE = "summaries"
PROVON_TELEMETRY_PAYLOAD_RETENTION = "all"
```

`summaries` requires the `TELEMETRY_SUMMARY_QUEUE` binding on telemetry writer runtimes.
`projected-only` drops raw payload fields at the Cloudflare telemetry write boundary. Choose it only
when projected evidence is sufficient for investigation and diagnosis.

Use the checked-in `wrangler.*.example.toml` files as the binding contract. Do not rename a binding
without changing the corresponding runtime code.

## OTLP And Background Work

Defaults are suitable for development and moderate workloads. Tune only from measured body sizes,
queue lag, write latency, and dependency limits.

| Variable                            | Default    | Purpose                                      |
| ----------------------------------- | ---------- | -------------------------------------------- |
| `PROVON_OTLP_MAX_BYTES`             | `10485760` | Decoded HTTP payload ceiling                 |
| `PROVON_OTLP_MAX_BATCH_BYTES`       | `16777216` | Soft bytes claimed per ingest tick           |
| `PROVON_OTLP_MAX_JOB_BYTES`         | `16777216` | Hard ceiling for one staged job              |
| `PROVON_OTLP_WORKER_INTERVAL_MS`    | `250`      | Node ingest worker interval; `0` disables it |
| `PROVON_OTLP_WORKER_BATCH_SIZE`     | `50`       | Jobs claimed per Node tick                   |
| `PROVON_OTLP_WORKER_LOCK_MS`        | `60000`    | Ingest claim lease                           |
| `PROVON_OTLP_FLUSH_MAX_ROWS`        | `2000`     | Soft rows per write group                    |
| `PROVON_OTLP_FLUSH_MAX_BYTES`       | `8388608`  | Soft bytes per write group                   |
| `PROVON_OTLP_MAX_PENDING_JOBS`      | `10000`    | Admission ceiling for queued ingest          |
| `PROVON_OTLP_CLEANUP_INTERVAL_MS`   | `3600000`  | Completed-job cleanup interval               |
| `PROVON_OTLP_CLEANUP_OLDER_THAN_MS` | `86400000` | Completed-job age before cleanup             |

Node also exposes:

| Variable                                               | Default  | Purpose                               |
| ------------------------------------------------------ | -------- | ------------------------------------- |
| `PROVON_TELEMETRY_SUMMARY_MATERIALIZATION_INTERVAL_MS` | `5000`   | Summary queue drain interval          |
| `PROVON_DIAGNOSTIC_RULE_INTERVAL_MS`                   | `60000`  | Ready diagnostic Run polling interval |
| `PROVON_DIAGNOSTIC_RULE_BATCH_SIZE`                    | `100`    | Diagnostic Runs claimed per tick      |
| `PROVON_REPAIR_STATUS_SYNC_INTERVAL_MS`                | `300000` | GitHub repair-status polling interval |

Gateway reservation cleanup exposes additional controls:

| Variable                                          | Runtime          | Default   | Purpose                                     |
| ------------------------------------------------- | ---------------- | --------- | ------------------------------------------- |
| `PROVON_GATEWAY_PTB_RECONCILIATION_ENABLED`       | Node, Cloudflare | Enabled   | Disable stale managed-credit reconciliation |
| `PROVON_GATEWAY_PTB_RECONCILIATION_INTERVAL_MS`   | Node             | `60000`   | Managed-credit cleanup loop interval        |
| `PROVON_GATEWAY_PTB_STALE_RESERVATION_MS`         | Node             | `600000`  | Managed-credit reservation age threshold    |
| `PROVON_GATEWAY_PTB_STALE_RESERVATION_MS`         | Cloudflare Jobs  | `1800000` | Managed-credit reservation age threshold    |
| `PROVON_GATEWAY_PTB_RECONCILIATION_BATCH_SIZE`    | Node, Cloudflare | `100`     | Managed-credit reservations released/tick   |
| `PROVON_GATEWAY_USAGE_RECONCILIATION_ENABLED`     | Node             | Enabled   | Disable stale usage-policy reconciliation   |
| `PROVON_GATEWAY_USAGE_RECONCILIATION_INTERVAL_MS` | Node             | `60000`   | Usage-policy cleanup loop interval          |
| `PROVON_GATEWAY_USAGE_STALE_RESERVATION_MS`       | Node             | `600000`  | Usage-policy reservation age threshold      |
| `PROVON_GATEWAY_USAGE_RECONCILIATION_BATCH_SIZE`  | Node             | `100`     | Usage-policy reservations released/tick     |

Setting an interval to `0` disables the corresponding loop only where the implementation accepts a
non-negative interval. Disabling a loop is an architecture change: assign its responsibility to
another runtime before doing so.

## Data Retention

Project retention policy selects what should expire. Runtime variables control the maintenance job:

| Variable                                  | Default    | Purpose                                     |
| ----------------------------------------- | ---------- | ------------------------------------------- |
| `PROVON_DATA_RETENTION_INTERVAL_MS`       | `21600000` | Node retention interval                     |
| `PROVON_DATA_RETENTION_BATCH_SIZE`        | `10`       | Projects or organizations per run           |
| `PROVON_DATA_RETENTION_DELETE_TIMEOUT_MS` | `60000`    | Delete timeout                              |
| `PROVON_DATA_RETENTION_MAX_RETRIES`       | `3`        | Retry count                                 |
| `PROVON_DATA_RETENTION_DELETE_MAX_FILES`  | `128`      | Iceberg files deleted per bounded operation |

Cloudflare scheduled retention runs through the Jobs Worker and telemetry warehouse. R2 lifecycle,
snapshot expiration, and compaction are separate platform maintenance controls.

## Sign-in Providers

Email and password remain available when no social provider is configured. A social provider is
enabled only when both its client ID and client secret are present.

| Provider | Required variables                                   | Optional                           |
| -------- | ---------------------------------------------------- | ---------------------------------- |
| GitHub   | `AUTH_GITHUB_CLIENT_ID`, `AUTH_GITHUB_CLIENT_SECRET` | None                               |
| Google   | `AUTH_GOOGLE_CLIENT_ID`, `AUTH_GOOGLE_CLIENT_SECRET` | None                               |
| Apple    | `AUTH_APPLE_CLIENT_ID`, `AUTH_APPLE_CLIENT_SECRET`   | `AUTH_APPLE_ALLOW_ACCOUNT_LINKING` |

Register:

```text
https://<api-origin>/api/auth/callback/github
https://<api-origin>/api/auth/callback/google
https://<api-origin>/api/auth/callback/apple
```

Google and GitHub identities require verified provider email claims. To link another identity,
sign in first and use **Account settings → Sign-in methods**. Anonymous email-based account linking
is not supported.

### Enterprise OIDC

Required:

- `AUTH_OIDC_CLIENT_ID`
- `AUTH_OIDC_CLIENT_SECRET`
- `AUTH_OIDC_ISSUER` or `AUTH_OIDC_WELL_KNOWN`

Optional controls include `AUTH_OIDC_NAME`, `AUTH_OIDC_SCOPE`,
`AUTH_OIDC_TOKEN_ENDPOINT_AUTH_METHOD`, `AUTH_OIDC_ALLOW_ACCOUNT_LINKING`, and
`AUTH_OIDC_EMAIL_TRUST_POLICY`.

The default email trust policy requires `email_verified=true`. Use `trusted_provider` only when the
IdP contract guarantees email ownership. The callback provider ID is `enterprise-oidc`:

```text
https://<api-origin>/api/auth/callback/enterprise-oidc
```

## Connector OAuth

Login OAuth and Connector OAuth are separate applications. Connector credentials use the
`*_INTEGRATION_*` prefix.

| Connector | Required variables                                                 | Optional scope override     |
| --------- | ------------------------------------------------------------------ | --------------------------- |
| GitHub    | `GITHUB_INTEGRATION_CLIENT_ID`, `GITHUB_INTEGRATION_CLIENT_SECRET` | `GITHUB_INTEGRATION_SCOPES` |
| Slack     | `SLACK_INTEGRATION_CLIENT_ID`, `SLACK_INTEGRATION_CLIENT_SECRET`   | `SLACK_INTEGRATION_SCOPES`  |
| Notion    | `NOTION_INTEGRATION_CLIENT_ID`, `NOTION_INTEGRATION_CLIENT_SECRET` | None                        |
| Jira      | `JIRA_INTEGRATION_CLIENT_ID`, `JIRA_INTEGRATION_CLIENT_SECRET`     | `JIRA_INTEGRATION_SCOPES`   |
| Linear    | `LINEAR_INTEGRATION_CLIENT_ID`, `LINEAR_INTEGRATION_CLIENT_SECRET` | `LINEAR_INTEGRATION_SCOPES` |

Register:

```text
https://<api-origin>/v1/integrations/github/callback
https://<api-origin>/v1/integrations/slack/callback
https://<api-origin>/v1/integrations/notion/callback
https://<api-origin>/v1/integrations/jira/callback
https://<api-origin>/v1/integrations/linear/callback
```

See [Connectors](../findings/connectors.md) for project setup and default scopes.

## Model Runtime

The model runtime stores local model weights under `PROVON_MODELS_DIR` and keeps its
process state in a fixed file at `<data-dir>/data/model-runtime.json`. The data directory is resolved
from the parent of `PROVON_BLOBS_DIR`, or from `XDG_DATA_HOME` when no blob directory is configured.

Node controls standalone Python inference and fine-tuning services over HTTP:

- `PROVON_PYTHON_INFERENCE_URL`
- `PROVON_PYTHON_INFERENCE_API_KEY`
- `PROVON_PYTHON_FINE_TUNING_URL`
- `PROVON_PYTHON_FINE_TUNING_API_KEY`
- `PROVON_MODEL_FINE_TUNING_WORK_DIR`
- `PROVON_TRANSFORMERS_DEVICE`
- `PROVON_TRANSFORMERS_DTYPE`
- `PROVON_TRANSFORMERS_TRUST_REMOTE_CODE=1`

Node stages immutable training data under `PROVON_MODEL_FINE_TUNING_WORK_DIR`. The fine-tuning
service writes its checkpoint under the same job directory, then Node atomically imports the
standalone model into `PROVON_MODELS_DIR`. When the services run in separate containers,
mount the training work directory at the same absolute path in both containers. See
[Model providers](../ai-gateway/providers.md#runtime-and-private-models).

## Configuration Review

Before production:

- compare staging and production variable names without comparing secret values;
- confirm every runtime reading one metadata store has the same `AUTH_SECRET`;
- verify exact Workbench origins and public callback URLs;
- verify storage paths or bindings point to durable production resources;
- keep body, queue, and retention limits consistent across producer and consumer surfaces;
- restart or redeploy all affected surfaces after a shared secret or binding change;
- record the configuration version with the application release.
