# 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_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_WORKBENCH_ORIGINS=https://provon.example.com
PROVON_AUTH_TRUST_HOST=true
```

For split Cloudflare origins:

```bash
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. Never enable `PROVON_AUTH_SKIP_CSRF_CHECK` in production.

## Node Storage

| Variable                    | Default                                | Purpose                                    |
| --------------------------- | -------------------------------------- | ------------------------------------------ |
| `PROVON_META_DB_URL`        | `libsql:.provon/data/meta.db`          | Metadata database URL                      |
| `PROVON_META_DB_AUTH_TOKEN` | None                                   | Remote libSQL authentication               |
| `PROVON_TELEMETRY_DB_URL`   | `duckdb:.provon/data/telemetry.duckdb` | `duckdb:<path>` or `motherduck:<database>` |
| `PROVON_LOCAL_BLOB_DIR`     | `.provon/blobs`                        | Blobs, staged payloads, and runtime data   |
| `PROVON_SELF_HOSTED_DIR`    | Sibling `self-hosted` directory        | Local model artifacts and process state    |

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

### 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_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_WORKFLOW_TRIGGER_INTERVAL_MS`                  | `30000`  | Scheduled workflow trigger interval   |
| `PROVON_WORKFLOW_RUNS_PER_TICK`                        | `100`    | Workflow execution cap per tick       |
| `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 |

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` | `AUTH_GITHUB_ALLOW_ACCOUNT_LINKING` |
| Google   | `AUTH_GOOGLE_CLIENT_ID`, `AUTH_GOOGLE_CLIENT_SECRET` | `AUTH_GOOGLE_ALLOW_ACCOUNT_LINKING` |
| 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
```

Account linking is disabled by default. Enable it only after confirming the provider guarantees
ownership of the returned email address.

### 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
```

### Enterprise SAML

The configuration surface accepts:

- `AUTH_SAML_IDP_METADATA` or `AUTH_SAML_IDP_METADATA_URL`;
- `AUTH_SAML_ACS_URL`, normally `https://<api-origin>/api/auth/saml/acs`;
- optionally `AUTH_SAML_ENTITY_ID`;
- signing key and certificate variables when AuthnRequests must be signed.

Assertion signatures are required by default. The checked-in Node and Cloudflare entrypoints do not
currently wire a production `samlify` schema validator, so SAML authentication fails closed in
production. Use enterprise OIDC for production SSO until a real validator is supplied by the
deployable runtime. `AUTH_SAML_SKIP_SCHEMA_VALIDATION=true` is a local-development bypass and must
not be used in production.

## 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](./connectors.md) for project setup and default scopes.

## Local Model Runtime

Node stores local model state under `PROVON_SELF_HOSTED_DIR`. Advanced engine overrides include:

- `PROVON_LLAMA_SERVER_PATH`
- `PROVON_STABLE_DIFFUSION_CPP_SERVER_PATH`
- `PROVON_DIFFUSERS_SERVER_PATH`
- `PROVON_DIFFUSERS_PYTHON_PATH`
- `PROVON_SENTENCE_TRANSFORMERS_SERVER_PATH`
- `PROVON_SENTENCE_TRANSFORMERS_PYTHON_PATH`
- `PROVON_SGLANG_PYTHON_PATH`
- `PROVON_SGLANG_DIFFUSION_SERVER_PATH`
- `PROVON_SGLANG_DIFFUSION_PYTHON_PATH`

Use overrides only when the runtime cannot manage the default engine path. See
[Model providers](./model-providers.md#local-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.
