# 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` | Local DuckDB telemetry database path          |
| `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.

## 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/repair-handoff.md) for project setup and default scopes.

## Model Runtime

The model runtime stores local model weights under `PROVON_MODELS_DIR`. Runtime process state
(active downloads, running services, engine installs, and the catalog cache) is kept in memory
only and is reset when the Node process restarts. Installed models are rediscovered from
`<model-dir>/manifest.json` files, and partial downloads can resume from `.part` files.

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.
