# General Configuration

BaSyx Go components use a shared configuration model from `internal/common`. Configuration can be provided through a YAML file and overridden with environment variables.

## Configuration Source Precedence

The shared configuration loader applies the following precedence:

1. Environment variables
2. YAML configuration file
3. Built-in defaults

Environment variables override YAML values. Nested keys use underscore notation, for example `server.port` becomes `SERVER_PORT`.

## Common Configuration Sections

These sections are part of the shared configuration model. Components ignore settings that are not relevant to their feature set.

### `logging`

| Key | Default | Purpose |
| --- | --- | --- |
| `format` | `text` | Log output format: `text` or `json`. |
| `level` | `info` | Minimum log severity: `debug`, `info`, `warn`, or `error`. |

All diagnostic logs are written to standard error. See
[Observability](observability) for request logging, correlation IDs,
OpenTelemetry tracing and metrics, and log collection. OpenTelemetry settings
are standard `OTEL_*` environment variables rather than keys in this YAML
configuration model.

### `server`

| Key | Default | Purpose |
| --- | --- | --- |
| `host` | `0.0.0.0` | Host used for the HTTP server and generated Swagger server URL. |
| `port` | `5004` | HTTP server port. |
| `contextPath` | `""` | Base path for API, Swagger, and health endpoints. |
| `cacheEnabled` | `false` | Enables descriptor persistence caches where supported. |
| `strictVerification` | `permissive` | Semantic verification mode: `off`, `permissive`, or `strict`. |
| `verificationEndpointAvailable` | `true` | Enables the `/verify` endpoint and Swagger entry where supported. |
| `readHeaderTimeoutSeconds` | `15` | Maximum time in seconds to read HTTP request headers. |
| `readTimeoutSeconds` | `300` | Maximum time in seconds to read an entire HTTP request, including its body. |
| `writeTimeoutSeconds` | `300` | Maximum time in seconds to write an HTTP response. |
| `idleTimeoutSeconds` | `60` | Maximum time in seconds to wait for the next request on an idle keep-alive connection. |
| `shutdownTimeoutSeconds` | `10` | Maximum time in seconds to wait for in-flight requests during graceful shutdown. |
| `pagination.defaultLimit` | `100` | Page size used when a request omits the `limit` query parameter. |
| `pagination.maxLimit` | `1000` | Largest accepted `limit`. A larger value is rejected with HTTP `400`. |

All HTTP timeout values must be greater than `0`. When a service receives an interrupt or termination signal, it stops accepting new connections and allows in-flight requests to finish for up to `shutdownTimeoutSeconds`.

The pagination limits apply to every paginated endpoint of every BaSyx Go service, including the REST Event Feed (`GET /events`). A request without `limit` returns at most `pagination.defaultLimit` items; the response carries a cursor when more items exist. A `limit` above `pagination.maxLimit` is rejected, not clamped, so clients never receive a shorter page than they asked for. Both values must be greater than `0`, `maxLimit` must not be smaller than `defaultLimit`, and `maxLimit` must not exceed `2147483646`. Lower `maxLimit` to bound the memory a single request can use on large datasets.

### `postgres`

```{note}
The pool defaults, `connMaxIdleTimeMinutes`, zero-value handling, validation rules, and automatic service-name fallback described below require BaSyx Go 1.0.5 or later.
```

| Key | Default | Purpose |
| --- | --- | --- |
| `dsn` | `""` | Complete PostgreSQL connection string. When non-empty, it replaces the individual connection fields listed below. |
| `host` | `db` | PostgreSQL host. |
| `port` | `5432` | PostgreSQL port. |
| `user` | `admin` | Database user. |
| `password` | `admin123` | Database password. |
| `dbname` | `basyxTestDB` | Database name. |
| `sslmode` | `disable` | PostgreSQL TLS verification mode: `disable`, `allow`, `prefer`, `require`, `verify-ca`, or `verify-full`. |
| `sslcert` | `""` | Path to the client certificate used for certificate-based PostgreSQL authentication. |
| `sslkey` | `""` | Path to the private key belonging to `sslcert`. |
| `sslrootcert` | `""` | Path to the root CA certificate used to verify the PostgreSQL server certificate. |
| `connectTimeoutSeconds` | `0` | Connection timeout in seconds. Must not be negative; `0` leaves the timeout unspecified. |
| `applicationName` | `""` | PostgreSQL `application_name` reported for the connection. When empty, the service name is used. |
| `fallbackApplicationName` | `""` | Fallback application name used when no primary application name is supplied. |
| `searchPath` | `""` | PostgreSQL schema search path for new sessions. |
| `options` | `""` | Additional PostgreSQL server startup options. |
| `timezone` | `""` | PostgreSQL session time zone. |
| `maxOpenConnections` | `50` | Maximum number of open DB connections per service process or pod. `0` uses the default. |
| `maxIdleConnections` | `25` | Maximum number of idle DB connections per service process or pod. `0` uses the default, capped at a smaller explicit open limit. |
| `connMaxLifetimeMinutes` | `5` | Maximum DB connection lifetime in minutes. `0` uses the default. |
| `connMaxIdleTimeMinutes` | `0` | Maximum time an idle connection is retained, in minutes. `0` disables idle-time recycling. |

When `postgres.dsn` is non-empty, do not explicitly configure `host`, `port`, `user`, `password`, `dbname`, TLS, timeout, application-name, search-path, options, or time-zone fields in YAML or environment variables. The service rejects this ambiguous combination. Connection-pool settings can still be used with a DSN.

If no primary `application_name` is supplied through `applicationName` or the DSN, the component sets it to its service name. An explicitly configured value is preserved. This identifies each BaSyx service in PostgreSQL views such as `pg_stat_activity`.

#### Optional PostgreSQL reader

```{note}
The `postgres.reader` configuration requires BaSyx Go 1.0.7 or later.
```

PostgreSQL-backed BaSyx HTTP services can use a separate reader connection for
eligible, eventually consistent reads. Configure it as a nested
`postgres.reader` block:

```yaml
postgres:
  host: postgres-primary
  port: 5432
  user: basyx
  password: change-me
  dbname: basyx
  sslmode: verify-full
  sslrootcert: /run/secrets/postgres/ca.crt
  maxOpenConnections: 30
  maxIdleConnections: 15
  connMaxLifetimeMinutes: 5
  connMaxIdleTimeMinutes: 1

  reader:
    host: postgres-reader
    port: 5432
    user: basyx_reader
    password: change-me
    dbname: basyx
    sslmode: verify-full
    sslrootcert: /run/secrets/postgres/ca.crt
    maxOpenConnections: 60
    maxIdleConnections: 30
    connMaxLifetimeMinutes: 5
    connMaxIdleTimeMinutes: 1
```

The reader supports the same connection and pool keys as the writer:

| YAML key below `postgres.reader` | Environment variable | Default or requirement |
| --- | --- | --- |
| `dsn` | `POSTGRES_READER_DSN` | Optional complete reader connection string. |
| `host` | `POSTGRES_READER_HOST` | Required when `dsn` is not set. |
| `port` | `POSTGRES_READER_PORT` | `5432` |
| `user` | `POSTGRES_READER_USER` | No writer-value inheritance. |
| `password` | `POSTGRES_READER_PASSWORD` | No writer-value inheritance. |
| `dbname` | `POSTGRES_READER_DBNAME` | Required when `dsn` is not set. |
| `sslmode` | `POSTGRES_READER_SSLMODE` | Empty uses the PostgreSQL driver behavior. |
| `sslcert` | `POSTGRES_READER_SSLCERT` | `""` |
| `sslkey` | `POSTGRES_READER_SSLKEY` | `""` |
| `sslrootcert` | `POSTGRES_READER_SSLROOTCERT` | `""` |
| `connectTimeoutSeconds` | `POSTGRES_READER_CONNECTTIMEOUTSECONDS` | `0` leaves the timeout unspecified. |
| `applicationName` | `POSTGRES_READER_APPLICATIONNAME` | Empty uses `<service-name>-reader`. |
| `fallbackApplicationName` | `POSTGRES_READER_FALLBACKAPPLICATIONNAME` | `""` |
| `searchPath` | `POSTGRES_READER_SEARCHPATH` | `""` |
| `options` | `POSTGRES_READER_OPTIONS` | `""` |
| `timezone` | `POSTGRES_READER_TIMEZONE` | `""` |
| `maxOpenConnections` | `POSTGRES_READER_MAXOPENCONNECTIONS` | `50` when set to `0`. |
| `maxIdleConnections` | `POSTGRES_READER_MAXIDLECONNECTIONS` | `25` when set to `0`, capped at a smaller open limit. |
| `connMaxLifetimeMinutes` | `POSTGRES_READER_CONNMAXLIFETIMEMINUTES` | `5` when set to `0`. |
| `connMaxIdleTimeMinutes` | `POSTGRES_READER_CONNMAXIDLETIMEMINUTES` | `0` disables idle-time recycling. |

Use either `postgres.reader.dsn` or the individual reader connection fields,
not both. Reader settings do not inherit connection fields or pool limits from
the writer. Omitting the complete reader configuration reuses the writer pool
and preserves the behavior of earlier releases. Pointing both configurations
to the same PostgreSQL endpoint is also supported, but does not add database
read capacity and both pools then consume the same server connection budget.
A physical standby is a separate PostgreSQL instance that replicates the same
database, not an independent database.

Reader routing is supported by:

- AAS Registry
- Submodel Registry
- Discovery Service
- Digital Twin Registry
- AAS Repository
- Submodel Repository
- Concept Description Repository
- AAS Environment, including the repository, registry, and discovery APIs it
  hosts
- AASX File Server
- Company Lookup Service
- DPP API

Eligible retrieval requests use the reader. This decision is based on the
operation's consistency requirements, not on the number of SQL queries needed
to complete a request. Mutations, mutation guards and prechecks, transactions,
read-after-write paths, schema validation, asynchronous jobs, operation
invocation, authorization policy storage, and upload or binary staging remain
on the writer. The Configuration Service and History Evidence Verifier are
writer-only.

Configuring a reader opts the service into eventual consistency. PostgreSQL
replication lag can make a successful write appear later through a
reader-routed request. The same applies to deletions and
authorization-relevant attribute changes: the current authorization middleware
and filters are still applied, but a reader can briefly return the preceding
resource state after access should have been revoked. Deployments that require
immediate revocation or read-after-write consistency must omit the reader for
the affected service or use replication guarantees that meet their required
consistency window.

Compound responses are assembled in one read-only repeatable-read transaction.
This keeps all SQL queries for that response on one reader connection and one
database snapshot, so a resource is not combined from different replica
positions. Separate requests can still observe different replay positions.

Both configured pools must be reachable during service startup. A reader
outage after startup is not silently routed to the writer. In production:

- use distinct writer and reader endpoints provided by a PostgreSQL operator
  or managed service;
- use TLS with server verification and Secret-backed credentials;
- grant the reader role only the permissions needed by reader-routed
  operations where feasible. For endpoints that are not inherently read-only,
  consider setting `postgres.reader.options` to
  `-c default_transaction_read_only=on` as an additional guard;
- monitor replica health and replay lag;
- test failover and recovery behavior for the selected PostgreSQL platform.

The pool settings apply independently to every process or Kubernetes pod. Calculate the connection budget separately for each PostgreSQL primary, including only clients that connect to that primary:

```text
required application connections =
  sum(maxOpenConnections per BaSyx service × maximum concurrent replica count)
  + non-BaSyx application pools
```

Non-BaSyx clients include identity services such as Keycloak when they share the database. The maximum concurrent replica count must include temporary old and new pods during rolling updates. Keep the total below that primary's usable connection budget and reserve connections for administration, monitoring, migrations, and failover. The maximum is a limit rather than a guarantee that every pool always opens that many connections, but PostgreSQL must be able to handle the configured worst case. Idle connections are still open PostgreSQL sessions.

When a separate reader endpoint is configured, calculate its budget
independently:

```text
required reader connections =
  sum(reader.maxOpenConnections per BaSyx service × maximum concurrent replica count)
  + non-BaSyx reader clients
```

Keep this total below the reader endpoint's usable connection budget and
reserve capacity for monitoring, administration, and failover. More pools or
connections do not add CPU, storage I/O, or replication capacity.

All four pool values must be non-negative. An explicitly configured `maxIdleConnections` greater than the effective `maxOpenConnections` is rejected during startup. If `maxIdleConnections` is `0` and an explicit open limit is smaller than the default idle limit of `25`, the effective idle limit is capped at that smaller open limit.

#### Optional CloudNativePG read-write connection pooler

```{note}
The `database.pooler` values require BaSyx Helm chart `3.7.0` or newer. They
configure a CloudNativePG `Pooler` resource and are not BaSyx application YAML
settings.
```

For a database managed by the chart, an optional read-write PgBouncer layer can
decouple the number of client connections opened by horizontally scaled BaSyx
pods from the number of PostgreSQL server processes:

```yaml
database:
  pooler:
    enabled: true
    instances: 2
    poolMode: transaction
    maxClientConn: 1000
    defaultPoolSize: 25
    preparedStatements:
      enabled: true
      maxPreparedStatements: 100
    parameters:
      reserve_pool_size: "5"
    resources:
      requests:
        cpu: 250m
        memory: 256Mi
    nodeSelector: {}
    affinity: {}
    tolerations: []
    topologySpreadConstraints: []
```

The complete value set and its defaults are:

| Key | Default | Purpose |
| --- | --- | --- |
| `database.pooler.enabled` | `false` | Renders the managed read-write Pooler and routes eligible runtime services through it. |
| `database.pooler.instances` | `2` | Number of PgBouncer pods. |
| `database.pooler.poolMode` | `transaction` | PgBouncer pooling mode: `transaction` or `session`. |
| `database.pooler.maxClientConn` | `1000` | Maximum client connections accepted by each PgBouncer pod. |
| `database.pooler.defaultPoolSize` | `25` | Default PostgreSQL server connections per PgBouncer pod and user/database pair. |
| `database.pooler.preparedStatements.enabled` | `true` | Enables PgBouncer protocol-level prepared-statement tracking. |
| `database.pooler.preparedStatements.maxPreparedStatements` | `100` | Maximum tracked prepared statements when tracking is enabled. |
| `database.pooler.parameters` | `{}` | Additional PgBouncer parameters as lowercase keys with string values. |
| `database.pooler.resources` | `{}` | Requests and limits for the PgBouncer container. |
| `database.pooler.nodeSelector` | `{}` | Node labels used to place Pooler pods. |
| `database.pooler.affinity` | `{}` | Node, pod, and pod anti-affinity for Pooler pods. |
| `database.pooler.tolerations` | `[]` | Tolerations for Pooler pods. |
| `database.pooler.topologySpreadConstraints` | `[]` | Topology-spread constraints for Pooler pods. |

When enabled, eligible services connect to
`<database.clusterName>-rw-pooler`. The chart replaces only `POSTGRES_HOST`;
the port, database name, user, password, and other settings still come from the
managed cluster's application Secret. For long cluster names, the chart
truncates the cluster-name portion of the Pooler service name to keep the
generated name within 63 characters and distinct from the CloudNativePG
Cluster name.

The routing rules are intentionally narrower than a global host replacement:

- BaSyx runtime services that use the global managed database connect through
  the Pooler.
- The Configuration Service wait and migration containers connect directly to
  the CloudNativePG read-write service. Migrations use session-level advisory
  locks and must not run through transaction pooling.
- Keycloak keeps its direct JDBC connection to the read-write service.
- A service with its own `database` override, whether inline or through
  `existingSecret`, keeps the host from that override and is not redirected.
- `database.type=external` never renders or uses this Pooler. If an external
  database needs PgBouncer, operate it separately and put its endpoint in the
  external database Secret.

Transaction pooling requires prepared-statement tracking for clients that use
protocol-level prepared statements. The chart therefore renders
`max_prepared_statements=100` by default. Setting
`preparedStatements.enabled=false` explicitly renders
`max_prepared_statements=0` and should only be done after verifying that the
complete workload does not need prepared statements. The first-class client
limit, server pool size, and prepared-statement values take precedence if the
same keys are also present under `parameters`. Additional parameters must be
supported by the PgBouncer version bundled with the installed CloudNativePG
operator.

Size the client side and server side separately. The aggregate application
pool limit should fit within the Pooler client capacity with headroom for
uneven load, rolling updates, and a failed Pooler pod:

```text
sum(maxOpenConnections per BaSyx pod × maximum concurrent replica count)
< surviving Pooler instances × maxClientConn
```

For one user/database pair and no additional server-pool parameters, a starting
estimate for PostgreSQL connections is:

```text
Pooler instances × (defaultPoolSize + reserve_pool_size)
+ direct Keycloak connections
+ migrations, monitoring, administration, and failover reserve
< PostgreSQL max_connections
```

Adjust the estimate for every active user/database pair and for additional
PgBouncer limits. A Pooler queues excess client work; it does not add write
capacity to the PostgreSQL primary. If latency remains high, compare the BaSyx
[PostgreSQL pool metrics](observability)
with `cnpg_pgbouncer_*` metrics and PostgreSQL query, lock, storage, and CPU
data before increasing the server pool.

Before enabling transaction pooling in production, render the chart against
the installed CloudNativePG CRDs and test the deployed BaSyx version through
the Pooler. Include bulk endpoints, large-object operations, connection
saturation, rolling updates, and a PostgreSQL switchover.

#### Helm reader configuration and read-only Pooler

```{note}
The `database.reader` values require BaSyx Helm chart `3.9.0` and BaSyx Go
`1.0.7` or newer. Both the reader and its read-only Pooler are disabled by
default.
```

For a database managed by the chart, enabling the global reader routes
reader-eligible requests to the CloudNativePG read-only service
`<database.clusterName>-ro`. Enabling its Pooler additionally creates a
CloudNativePG `Pooler` of type `ro`, changes the reader endpoint to
`<database.clusterName>-ro-pooler`, and leaves writer routing unchanged. For
long cluster names, the chart truncates the cluster-name portion of the Pooler
service name to keep it within 63 characters and distinct from the
CloudNativePG Cluster name:

```yaml
database:
  instances: 3
  reader:
    enabled: true
    maxOpenConnections: 50
    maxIdleConnections: 25
    pooler:
      enabled: true
      instances: 2
      monitoring:
        enabled: true
        labels:
          release: kube-prometheus-stack
```

The global reader connection values are:

| Key | Default | Purpose |
| --- | --- | --- |
| `database.reader.enabled` | `false` | Injects the optional reader connection into supported BaSyx Go services. |
| `database.reader.existingSecret` | `""` | Complete external reader Secret. |
| `database.reader.host` | `""` | External reader host override. |
| `database.reader.port` | `""` | External reader port override. |
| `database.reader.dbname` | `""` | External reader database-name override. |
| `database.reader.user` | `""` | External reader user override. |
| `database.reader.password` | `""` | External reader password override. |
| `database.reader.sslmode` | `""` | Reader TLS mode override. |
| `database.reader.sslcert` | `""` | Reader client-certificate path override. |
| `database.reader.sslkey` | `""` | Reader client-key path override. |
| `database.reader.sslrootcert` | `""` | Reader root-CA path override. |
| `database.reader.connectTimeoutSeconds` | `0` | Reader connection-timeout override. |
| `database.reader.applicationName` | `""` | Reader application-name override. |
| `database.reader.fallbackApplicationName` | `""` | Reader fallback application-name override. |
| `database.reader.searchPath` | `""` | Reader search-path override. |
| `database.reader.options` | `""` | Reader PostgreSQL startup-options override. |
| `database.reader.timezone` | `""` | Reader session-time-zone override. |
| `database.reader.maxOpenConnections` | `50` | Reader pool's maximum open connections per BaSyx pod. |
| `database.reader.maxIdleConnections` | `25` | Reader pool's maximum idle connections per BaSyx pod. |
| `database.reader.connMaxLifetimeMinutes` | `5` | Reader connection lifetime. |
| `database.reader.connMaxIdleTimeMinutes` | `0` | Reader idle lifetime; `0` disables idle-time recycling. |

For an external database, `database.reader.existingSecret` selects a complete
reader Secret. It must contain `host`, `port`, `dbname`, `user`, and `password`
and can contain `sslmode`, `sslcert`, `sslkey`, `sslrootcert`,
`connectTimeoutSeconds`, `applicationName`, `fallbackApplicationName`,
`searchPath`, `options`, and `timezone`. Do not combine `existingSecret` with
inline reader connection fields. Inline reader connection values are stored in
a generated Secret. Any omitted inline field is deliberately sourced from the
effective writer Secret. This makes a host-only override possible without
duplicating credentials:

```yaml
database:
  type: external
  existingSecret: basyx-postgres-writer
  reader:
    enabled: true
    host: postgres-reader.example.internal
    sslmode: verify-full
```

Setting only `database.reader.enabled: true` for an external database points
both pools to the same endpoint. This is valid for migration and compatibility
but does not add database capacity. Prefer a complete existing reader Secret,
TLS with server verification, and a least-privilege read-only role where
feasible in production.

Each charted BaSyx Go service supports a local override at
`<component>.database.reader`:

| Component value | Service |
| --- | --- |
| `aasDiscovery` | Discovery Service |
| `aasRegistry` | AAS Registry |
| `aasRepository` | AAS Repository |
| `aasEnvironment` | AAS Environment |
| `dppApi` | DPP API |
| `submodelRegistry` | Submodel Registry |
| `submodelRepository` | Submodel Repository |
| `cdRepository` | Concept Description Repository |
| `companyLookup` | Company Lookup Service |
| `digitalTwinRegistry` | Digital Twin Registry |

A service-local reader object replaces the global reader configuration. A
reader-only local override continues to use the global writer. A service-local
writer override without a local reader disables inherited global reader
routing so that the service cannot accidentally read from a different
database. The Configuration Service and Keycloak never receive the reader
environment. The Go AASX File Server supports reader routing, but this chart
does not currently deploy it.

The read-only Pooler accepts the same capacity, prepared-statement, placement,
and monitoring values as the read-write Pooler:

- `database.reader.pooler.enabled`
- `database.reader.pooler.instances`
- `database.reader.pooler.poolMode`
- `database.reader.pooler.maxClientConn`
- `database.reader.pooler.defaultPoolSize`
- `database.reader.pooler.preparedStatements.enabled`
- `database.reader.pooler.preparedStatements.maxPreparedStatements`
- `database.reader.pooler.parameters`
- `database.reader.pooler.resources`
- `database.reader.pooler.nodeSelector`
- `database.reader.pooler.affinity`
- `database.reader.pooler.tolerations`
- `database.reader.pooler.topologySpreadConstraints`
- `database.reader.pooler.monitoring.*`

The schema requires `database.instances` to be at least `2` when a managed
reader implicitly targets the chart's native `<database.clusterName>-ro`
service, because one instance provides no standby. A managed writer with one
instance can still use an explicit reader `host` or `existingSecret`. The
schema also rejects a read-only Pooler when the reader is disabled or when
`database.type=external`. Its `PodMonitor` renders only when both the Pooler
and its monitoring are enabled. It uses the `cnpg.io/poolerName` selector and
supports the same labels, annotations, interval, timeout, namespace selector,
relabelings, and metric relabelings as the read-write Pooler monitor.
Prometheus Operator and its `monitoring.coreos.com/v1` CRDs must already be
installed.

Budget Pooler clients and PostgreSQL server connections separately. The sum of
the reader pool limits across all BaSyx pods must fit within the surviving
read-only Pooler instances' client capacity. The Pooler server-pool sizes,
including every active user/database pair, must fit within the destination
standby's `max_connections` with operational headroom. Monitor BaSyx
`db.client.connections.*` metrics, `cnpg_pgbouncer_*` metrics, and PostgreSQL
replication lag before increasing either limit.

The DSN and database credentials are sensitive and should normally be supplied through a secret rather than committed to YAML.

### `cors`

| Key | Default | Purpose |
| --- | --- | --- |
| `allowedOrigins` | `[]` | Allowed CORS origins. |
| `allowedMethods` | `[GET, POST, PUT, PATCH, DELETE, OPTIONS]` | Allowed HTTP methods. |
| `allowedHeaders` | `[]` | Allowed request headers. |
| `allowCredentials` | `false` | Enables credentialed CORS requests. |

### `oidc` and `abac`

| Key | Default | Purpose |
| --- | --- | --- |
| `oidc.trustlistPath` | `config/trustlist.json` | JSON trustlist of accepted OIDC providers. |
| `abac.enabled` | `false` | Enables OIDC authentication and ABAC authorization middleware. |
| `abac.modelPath` | `config/access_rules/access-rules.json` | ABAC access-rules model. |
| `abac.policyFileImport` | `""` | Controls startup import of `modelPath`: `always`, `if_missing`, or `never`. An empty value lets the service choose its default behavior. |
| `abac.policyScope` | `""` | Optional database namespace for stored ABAC policies. An empty value uses the service's built-in scope. |
| `abac.managementApi.enabled` | `false` | Enables the protected API for managing the active ABAC policy at runtime. |

If `abac.enabled` is `false`, the shared security setup is skipped. If it is `true`, the trustlist is required. `policyFileImport` determines whether the policy file is loaded on every start, only if the database has no active policy, or never. `policyScope` controls the database-backed ABAC policy namespace; use different scopes to isolate deployments that share a database, and share a scope only when services should intentionally use the same active policy.

When set, `policyScope` is trimmed, must not exceed 255 characters, and may contain ASCII letters, digits, `_`, `-`, `.`, and `:`.

### `rebac`

Relationship-based access control (ReBAC) is experimental. It lets users share their own resources with other users or groups without editing the ABAC policy. ReBAC extends ABAC as a strict union: a request is allowed when ABAC or ReBAC allows it. See [Relationship-Based Access Control](rebac) for setup and operation.

| Key | Default | Purpose |
| --- | --- | --- |
| `rebac.enabled` | `false` | Enables ReBAC. Requires `abac.enabled=true` and a readable `oidc.trustlistPath`. |
| `rebac.subjectClaim` | `sub` | Top-level access token claim with a stable user identifier. Use `oid` for Microsoft Entra ID. |
| `rebac.groupClaim` | `groups` | Top-level access token claim with group names. Use `basyx.<target>` for a claim mapped in the trustlist. |
| `rebac.administrators` | `[]` | Bootstrap and recovery administrators as `issuer\|subject` or `issuer\|group:<name>`. |

With `rebac.enabled=false`, no ReBAC code is wired and services behave exactly as before. When ReBAC is enabled, `subjectClaim` and `groupClaim` must not be empty, and every `administrators` entry must contain an issuer and a subject or a non-empty group name separated by `|`. A service with ReBAC enabled refuses to start without ABAC (`REBAC-SETUP-ABACREQUIRED`) or OIDC (`REBAC-SETUP-OIDCREQUIRED`).

Relationships are stored in the BaSyx PostgreSQL database, and all services sharing a database share them. Enable ReBAC consistently on all services that share the database. Company Lookup and Digital Twin Registry do not support ReBAC and always run with ReBAC disabled.

#### OIDC trustlist provider fields

The source also defines the following provider fields for entries read from the JSON file at `oidc.trustlistPath`. They are not additional keys in the main YAML `oidc` section.

| Field | Purpose |
| --- | --- |
| `issuer` | OIDC issuer URL whose tokens are accepted. |
| `audience` | Optional required token audience. Audience validation is skipped when this is empty. |
| `scopes` | Scopes that an accepted token must provide. |
| `discoveryUrl` | Optional non-standard OpenID Provider discovery URL. |
| `scopeClaims` | Optional JSON pointers identifying claims from which OAuth scopes are read. |
| `claimMappings` | Optional rules that map provider-specific claims into canonical BaSyx claims. |

Each `claimMappings` entry contains:

| Field | Purpose |
| --- | --- |
| `target` | Target claim name without the `basyx.` prefix. The mapped claim is exposed as `basyx.<target>`. |
| `mode` | Mapping mode: `list` collects values as a list, while `scalar` produces a single value. |
| `sources` | Provider claim paths used as mapping inputs. |

### `general`

| Key | Default | Purpose |
| --- | --- | --- |
| `enableImplicitCasts` | `true` | Allows implicit casts in ABAC/query expression evaluation. |
| `enableDescriptorDebug` | `false` | Enables descriptor query debug output. |
| `discoveryIntegration` | `false` | Enables discovery-specific descriptor behavior; some services set this internally. |
| `enableCustomMiddlewareHeaderInjection` | `false` | Enables custom claim/header middleware where supported. |
| `supportsSingularSupplementalSemanticId` | `false` | Accepts singular `supplementalSemanticId` compatibility format. |
| `aasRegistryIntegration` | `false` | Enables AAS repository to AAS registry synchronization. |
| `submodelRegistryIntegration` | `false` | Enables Submodel repository to Submodel registry synchronization. |
| `externalUrl` | `""` | Public base URL used to generate synchronized registry endpoint descriptors. Multiple URLs can be comma-separated. |
| `trustProxyHeaders` | `false` | Allows trusted reverse proxies to supply the public request scheme, host, and client information through `Forwarded` or `X-Forwarded-*` headers. |
| `trustedProxyCIDRs` | `[]` | CIDR allowlist of proxy source addresses whose forwarded headers may be trusted. |
| `uploadMaxSizeBytes` | `134217728` | Maximum compressed HTTP request size, including multipart overhead, for binary upload endpoints. |
| `delegatedOperationResponseMaxSizeBytes` | `1048576` | Maximum JSON response size in bytes from a delegated Submodel Operation. Applies to synchronous and asynchronous invocation in the Submodel Repository and AAS Environment. |
| `aasxMaxPartCount` | `10000` | Maximum number of non-directory entries in an AASX package. |
| `aasxMaxOPCMetadataSizeBytes` | `16777216` | Maximum combined expanded size of AASX OPC metadata. |
| `aasxMaxPartExpandedSizeBytes` | `134217728` | Maximum expanded size of one AASX payload part. |
| `aasxMaxTotalExpandedSizeBytes` | `134217728` | Maximum combined expanded size of all AASX payload parts. |
| `aasxMaxThumbnailSizeBytes` | `16777216` | Maximum expanded size of an AASX thumbnail. |
| `aasPreconfigPaths` | `[]` | AAS Environment startup import sources. Supports files or folders with `.aasx`, `.json`, or `.xml` files. |
| `bulkBatchLimit` | `1000` | Maximum row count per generated bulk SQL statement. Must be greater than `0`. |

`uploadMaxSizeBytes` limits the compressed HTTP request, including multipart overhead. The AASX settings independently limit expanded package content to protect against packages whose contents are much larger than the uploaded file. All six values must be greater than `0`. In addition, `aasxMaxTotalExpandedSizeBytes` must be greater than or equal to `aasxMaxPartExpandedSizeBytes`, which must be greater than or equal to `aasxMaxThumbnailSizeBytes`.

`delegatedOperationResponseMaxSizeBytes` defaults to 1 MiB. Set a larger positive byte count when a delegated Operation returns more data, such as records. The response is buffered in memory, so size the limit for the available memory. File element attachment downloads are unaffected.

When registry synchronization is enabled, `general.externalUrl` must be set to at least one absolute URL with scheme and host.

### `jws` and `swagger`

| Key | Default | Purpose |
| --- | --- | --- |
| `jws.privateKeyPath` | `""` | RSA private key used by Submodel Repository and AAS Environment signing use cases. |
| `jws.certificateChainPath` | `""` | PEM-encoded X.509 certificate chain included as the JWS `x5c` certificate chain where signing supports it. |
| `swagger.enabled` | `true` | Enables Swagger UI and OpenAPI specification endpoints. |
| `swagger.contactName` | `Eclipse BaSyx` | Contact name injected into OpenAPI/Swagger docs. |
| `swagger.contactEmail` | `basyx-dev@eclipse.org` | Contact email injected into OpenAPI/Swagger docs. |
| `swagger.contactUrl` | `https://basyx.org` | Contact URL injected into OpenAPI/Swagger docs. |

### `history`

History settings control API history, audit metadata, and optional external evidence storage. A component must implement history handling for these settings to have a runtime effect.

| Key | Default | Purpose |
| --- | --- | --- |
| `mode` | `off` | History mode: `off`, `api`, or `audit`. |
| `retentionDays` | `0` | Requested database-history retention. Only `0` (keep forever) is currently accepted; automatic deletion is not implemented. |
| `fullSnapshotInterval` | `1` | Number of history rows between full snapshots. Must be at least `1`; `1` stores every row as a full snapshot. |
| `immutability` | `none` | Immutability mode: `none`, `postgres_guarded`, or the reserved `external_anchor`. |
| `auditIdentityMode` | `none` | Identity detail stored with audit entries: `none`, `minimal`, or `extended`. |
| `integrityAnchor.provider` | `none` | External integrity-anchor backend. Only `none` is currently implemented. |

`external_anchor` cannot currently be used: it requires a non-`none` integrity-anchor provider, while no such provider is implemented yet.

#### `history.evidence`

Evidence storage writes WORM-compatible history artifacts to object storage.

| Key | Default | Purpose |
| --- | --- | --- |
| `enabled` | `false` | Enables external evidence artifact writing. Requires `history.mode` to be `api` or `audit`. |
| `provider` | `none` | Evidence backend. Accepted values are `none` and `s3`; enabled evidence requires `s3`. |
| `bucket` | `""` | S3 bucket that receives evidence artifacts. Required when evidence is enabled. |
| `prefix` | `basyx-history-evidence` | Object-key prefix used inside the bucket. |
| `region` | `us-east-1` | S3 region. Required when evidence is enabled. |
| `endpoint` | `""` | Optional custom S3-compatible endpoint, for example a MinIO endpoint. |
| `accessKeyId` | `""` | Optional explicit S3 access-key ID. Configure it together with `secretAccessKey`. |
| `secretAccessKey` | `""` | Optional explicit S3 secret access key. Configure it together with `accessKeyId`. |
| `pathStyle` | `false` | Uses path-style S3 addressing instead of virtual-hosted bucket addressing. Often needed for local S3-compatible services. |
| `retentionMode` | `""` | S3 Object Lock mode: `governance` or `compliance`. Required when evidence is enabled. |
| `retentionDays` | `0` | Object-lock retention period. Must be at least `1` when evidence is enabled. |
| `writeTimeoutSeconds` | `10` | Timeout for writing an evidence artifact. Must be at least `1`. |
| `signing.privateKeyPath` | `""` | Private key used to sign evidence manifests. If empty, `jws.privateKeyPath` is used as the fallback signing key. |
| `signing.publicKeyPath` | `""` | Public key used for evidence-signature verification. |
| `signing.required` | `false` | Requires signing material. At least a signing private key, the JWS fallback key, or a public key must be configured. |

Credentials are sensitive. Prefer secret-backed environment variables or mounted secret files over committing them to YAML.

### `eventing`

Eventing is experimental. It publishes AAS, asset, Submodel, and PCN changes as CloudEvents through MQTT 5, Kafka, and AMQP 1.0 brokers, or makes them available through the REST Event Feed. The AAS Repository, Submodel Repository, and AAS Environment support eventing.

| Key | Default | Purpose |
| --- | --- | --- |
| `enabled` | `false` | Enables event publishing to broker transports. |
| `format` | `cloudevents` | Event serialization format. Only `cloudevents` is supported. |
| `sinks` | `[]` | Broker transports: `mqtt`, `kafka`, and `amqp`. Several transports can be enabled together. |
| `outboxEnabled` | `false` | Enables the transactional outbox that stores pending events in PostgreSQL. Required by every transport. |
| `topicPrefix` | `basyx` | Prefix of the generated MQTT topics. It must not contain `+` or `#` or end with `/`. |
| `sourceBaseUrl` | `""` | Optional event source base URL. Defaults to the first `general.externalUrl`, then to the local server URL and context path. |
| `schemaBaseUrl` | `""` | Optional base URL of the event payload schemas. Defaults to the source base URL followed by `/.well-known/event-feed/schemas`. |

A non-empty `sinks` list requires both `enabled` and `outboxEnabled`. An enabled outbox requires at least one sink. Sink names must be unique, and every transport must use a distinct `sinkId`. `sourceBaseUrl` and `schemaBaseUrl` must be absolute HTTP(S) URLs without credentials, query, or fragment.

Event delivery is at least once. Consumers should deduplicate by CloudEvents ID. Pending events are retried indefinitely and do not expire, so allow database capacity for broker outages. Drain pending deliveries before changing the broker, sink ID, or destination of a transport.

Credential and TLS settings are available for every transport. Supply either a credential value or its file, not both. Files are read at startup, and trailing line endings are removed. Client certificate and key files must be supplied together. TLS requires TLS 1.2 or later and verifies the server certificate.

#### `eventing.mqtt`

| Key | Default | Purpose |
| --- | --- | --- |
| `broker` | `""` | Required. Broker URL `mqtt://host:port` or `tls://host:port` without credentials or path. |
| `clientId` | `""` | Required. MQTT client ID. It must be unique for each process connecting to the broker, including replicas. |
| `sinkId` | `mqtt` | Stable identifier of the delivery queue. Replicas sharing a database use the same value. |
| `qos` | `1` | MQTT QoS: `0`, `1`, or `2`. QoS `0` weakens the at-least-once guarantee. |
| `retained` | `false` | Publishes retained messages. |
| `username`, `password` | `""` | Optional broker credentials. |
| `usernameFile`, `passwordFile` | `""` | Files containing the broker credentials. |
| `caFile` | `""` | Optional PEM CA bundle. Requires a `tls://` broker. |
| `certificateFile`, `keyFile` | `""` | Optional client certificate and key for mutual TLS. Requires a `tls://` broker. |

#### `eventing.kafka`

| Key | Default | Purpose |
| --- | --- | --- |
| `brokers` | `[]` | Required bootstrap brokers as `host:port` without scheme or credentials. |
| `topic` | `basyx.events` | Topic for all event families. Create it before enabling the sink. |
| `sinkId` | `kafka` | Stable identifier of the delivery queue. |
| `clientId` | `basyx` | Client name reported to Kafka. |
| `producerBatchMaxBytes` | `0` | Maximum uncompressed batch size. `0` uses the client default of 1,000,012 bytes; otherwise `512` to `1073741824`. Align broker and topic message limits with this value. |
| `tlsEnabled` | `false` | Enables TLS. |
| `caFile` | `""` | Optional PEM CA bundle. Requires `tlsEnabled`. |
| `certificateFile`, `keyFile` | `""` | Optional client certificate and key for mutual TLS. Requires `tlsEnabled`. |
| `saslMechanism` | `""` | Empty disables SASL. Supported values are `PLAIN`, `SCRAM-SHA-256`, and `SCRAM-SHA-512`. |
| `username`, `password` | `""` | SASL credentials. Required when `saslMechanism` is set and rejected otherwise. |
| `usernameFile`, `passwordFile` | `""` | Files containing the SASL credentials. |

#### `eventing.amqp`

| Key | Default | Purpose |
| --- | --- | --- |
| `broker` | `""` | Required. Broker URL `amqp://host[:port]` or `amqps://host[:port]` without credentials, path, query, or fragment. Default ports are 5672 and 5671. |
| `address` | `""` | Required target address, for example `/queues/basyx.events` or `/exchanges/<exchange>/<routing-key>` for RabbitMQ 4. BaSyx does not create queues, exchanges, or bindings. |
| `sinkId` | `amqp` | Stable identifier of the delivery queue. |
| `hostName` | `""` | Optional AMQP connection hostname. RabbitMQ selects a virtual host with `vhost:<name>`. |
| `username`, `password` | `""` | Optional SASL PLAIN credentials. Supply both or neither. |
| `usernameFile`, `passwordFile` | `""` | Files containing the SASL PLAIN credentials. |
| `caFile` | `""` | Optional PEM CA bundle. Requires `amqps`. |
| `certificateFile`, `keyFile` | `""` | Optional client certificate and key for mutual TLS. Requires `amqps`. |

#### `eventing.feed`

The REST Event Feed is independent of `eventing.enabled` and the broker transports. When enabled, it adds `GET /events`, `GET /.well-known/event-feed.json`, and the schema endpoints to the service.

| Key | Default | Purpose |
| --- | --- | --- |
| `enabled` | `false` | Enables the REST Event Feed. |
| `maxAgeDays` | `30` | Visible retention window in days, measured from the mutation time. |
| `hardDeleteGraceDays` | `10` | Additional days before expired events are physically deleted. `0` disables the delay. |
| `sourceBaseUrl` | `""` | Compatible alias of `eventing.sourceBaseUrl`. If both are set, they must match. |
| `schemaBaseUrl` | `""` | Compatible alias of `eventing.schemaBaseUrl`. If both are set, they must match. |
| `cleanupIntervalHours` | `24` | Interval of the physical cleanup. Cleanup also runs at startup. |
| `publishIntervalMillis` | `250` | Interval before checking for committed events to publish to the feed. |

`maxAgeDays`, `hardDeleteGraceDays`, and `publishIntervalMillis` must not be negative. The page size of `GET /events` follows `server.pagination.defaultLimit` and `server.pagination.maxLimit`; the former `eventing.feed.maxPageSize` setting no longer exists and is ignored if present. Running the feed requires database schema v1.2.0 or later, and broker transports require v1.2.1 or later. Run the Configuration Service before starting the updated services.

## Example YAML

```yaml
logging:
  format: text
  level: info

server:
  host: 0.0.0.0
  port: 5004
  contextPath: ""
  cacheEnabled: false
  strictVerification: permissive
  verificationEndpointAvailable: true
  readHeaderTimeoutSeconds: 15
  readTimeoutSeconds: 300
  writeTimeoutSeconds: 300
  idleTimeoutSeconds: 60
  shutdownTimeoutSeconds: 10
  pagination:
    defaultLimit: 100
    maxLimit: 1000

postgres:
  dsn: ""
  host: db
  port: 5432
  dbname: basyxTestDB
  user: admin
  password: admin123
  sslmode: disable
  sslcert: ""
  sslkey: ""
  sslrootcert: ""
  connectTimeoutSeconds: 0
  applicationName: ""
  fallbackApplicationName: ""
  searchPath: ""
  options: ""
  timezone: ""
  maxOpenConnections: 50
  maxIdleConnections: 25
  connMaxLifetimeMinutes: 5
  connMaxIdleTimeMinutes: 0

cors:
  allowedOrigins: []
  allowedMethods: [GET, POST, PUT, PATCH, DELETE, OPTIONS]
  allowedHeaders: []
  allowCredentials: false

oidc:
  trustlistPath: "config/trustlist.json"

abac:
  enabled: false
  modelPath: "config/access_rules/access-rules.json"
  policyFileImport: ""
  policyScope: ""
  managementApi:
    enabled: false

rebac:
  enabled: false
  subjectClaim: sub
  groupClaim: groups
  administrators: []

general:
  enableImplicitCasts: true
  enableDescriptorDebug: false
  discoveryIntegration: false
  enableCustomMiddlewareHeaderInjection: false
  supportsSingularSupplementalSemanticId: false
  aasRegistryIntegration: false
  submodelRegistryIntegration: false
  externalUrl: ""
  trustProxyHeaders: false
  trustedProxyCIDRs: []
  uploadMaxSizeBytes: 134217728
  delegatedOperationResponseMaxSizeBytes: 1048576
  aasxMaxPartCount: 10000
  aasxMaxOPCMetadataSizeBytes: 16777216
  aasxMaxPartExpandedSizeBytes: 134217728
  aasxMaxTotalExpandedSizeBytes: 134217728
  aasxMaxThumbnailSizeBytes: 16777216
  aasPreconfigPaths: []
  bulkBatchLimit: 1000

jws:
  privateKeyPath: ""
  certificateChainPath: ""

swagger:
  enabled: true
  contactName: "Eclipse BaSyx"
  contactEmail: "basyx-dev@eclipse.org"
  contactUrl: "https://basyx.org"

history:
  mode: off
  retentionDays: 0
  fullSnapshotInterval: 1
  immutability: none
  auditIdentityMode: none
  evidence:
    enabled: false
    provider: none
    bucket: ""
    prefix: basyx-history-evidence
    region: us-east-1
    endpoint: ""
    accessKeyId: ""
    secretAccessKey: ""
    pathStyle: false
    retentionMode: ""
    retentionDays: 0
    writeTimeoutSeconds: 10
    signing:
      privateKeyPath: ""
      publicKeyPath: ""
      required: false
  integrityAnchor:
    provider: none

eventing:
  enabled: false
  format: cloudevents
  sinks: []
  outboxEnabled: false
  topicPrefix: basyx
  sourceBaseUrl: ""
  schemaBaseUrl: ""
  mqtt:
    broker: ""
    clientId: ""
    sinkId: mqtt
    qos: 1
    retained: false
    username: ""
    password: ""
    usernameFile: ""
    passwordFile: ""
    caFile: ""
    certificateFile: ""
    keyFile: ""
  kafka:
    brokers: []
    topic: basyx.events
    sinkId: kafka
    clientId: basyx
    producerBatchMaxBytes: 0
    tlsEnabled: false
    caFile: ""
    certificateFile: ""
    keyFile: ""
    saslMechanism: ""
    username: ""
    password: ""
    usernameFile: ""
    passwordFile: ""
  amqp:
    broker: ""
    address: ""
    sinkId: amqp
    hostName: ""
    username: ""
    password: ""
    usernameFile: ""
    passwordFile: ""
    caFile: ""
    certificateFile: ""
    keyFile: ""
  feed:
    enabled: false
    maxAgeDays: 30
    hardDeleteGraceDays: 10
    sourceBaseUrl: ""
    schemaBaseUrl: ""
    cleanupIntervalHours: 24
    publishIntervalMillis: 250
```

## Environment Variables

For regular settings, use the uppercase YAML path with dots replaced by underscores:

```bash
LOGGING_FORMAT=text
LOGGING_LEVEL=info
SERVER_PORT=5004
SERVER_CONTEXTPATH=/api
SERVER_STRICTVERIFICATION=permissive
SERVER_READ_HEADER_TIMEOUT_SECONDS=15
SERVER_READ_TIMEOUT_SECONDS=300
SERVER_WRITE_TIMEOUT_SECONDS=300
SERVER_IDLE_TIMEOUT_SECONDS=60
SERVER_SHUTDOWN_TIMEOUT_SECONDS=10
SERVER_PAGINATION_DEFAULT_LIMIT=100
SERVER_PAGINATION_MAX_LIMIT=1000
POSTGRES_HOST=db
POSTGRES_PORT=5432
POSTGRES_USER=admin
POSTGRES_PASSWORD=admin123
POSTGRES_DBNAME=basyxTestDB
POSTGRES_SSLMODE=disable
POSTGRES_CONNECTTIMEOUTSECONDS=10
POSTGRES_MAXOPENCONNECTIONS=50
POSTGRES_MAXIDLECONNECTIONS=25
POSTGRES_CONNMAXLIFETIMEMINUTES=5
POSTGRES_CONNMAXIDLETIMEMINUTES=0
ABAC_ENABLED=false
ABAC_POLICYFILEIMPORT=if_missing
ABAC_POLICY_SCOPE=aasregistryservice
ABAC_MANAGEMENTAPI_ENABLED=false
REBAC_ENABLED=false
OIDC_TRUSTLISTPATH=config/trustlist.json
GENERAL_EXTERNALURL=https://example.org/aas
GENERAL_TRUSTPROXYHEADERS=false
GENERAL_UPLOADMAXSIZEBYTES=134217728
GENERAL_DELEGATEDOPERATIONRESPONSEMAXSIZEBYTES=1048576
GENERAL_AASXMAXPARTCOUNT=10000
GENERAL_AASXMAXOPCMETADATASIZEBYTES=16777216
GENERAL_AASXMAXPARTEXPANDEDSIZEBYTES=134217728
GENERAL_AASXMAXTOTALEXPANDEDSIZEBYTES=134217728
GENERAL_AASXMAXTHUMBNAILSIZEBYTES=16777216
GENERAL_BULK_BATCH_LIMIT=1000
SWAGGER_ENABLED=true
```

`GENERAL_AAS_PRECONFIG_PATHS` is parsed as a comma-separated list and overrides `general.aasPreconfigPaths`:

```bash
GENERAL_AAS_PRECONFIG_PATHS=file:/data/example.aasx,/data/preconfigured-aas
```

The following explicit aliases are also supported:

| YAML setting | Environment variable |
| --- | --- |
| `logging.format` | `LOGGING_FORMAT` |
| `logging.level` | `LOGGING_LEVEL` |
| `server.readHeaderTimeoutSeconds` | `SERVER_READ_HEADER_TIMEOUT_SECONDS` or `BASYX_SERVER_READ_HEADER_TIMEOUT_SECONDS` |
| `server.readTimeoutSeconds` | `SERVER_READ_TIMEOUT_SECONDS` or `BASYX_SERVER_READ_TIMEOUT_SECONDS` |
| `server.writeTimeoutSeconds` | `SERVER_WRITE_TIMEOUT_SECONDS` or `BASYX_SERVER_WRITE_TIMEOUT_SECONDS` |
| `server.idleTimeoutSeconds` | `SERVER_IDLE_TIMEOUT_SECONDS` or `BASYX_SERVER_IDLE_TIMEOUT_SECONDS` |
| `server.shutdownTimeoutSeconds` | `SERVER_SHUTDOWN_TIMEOUT_SECONDS` or `BASYX_SERVER_SHUTDOWN_TIMEOUT_SECONDS` |
| `server.pagination.defaultLimit` | `SERVER_PAGINATION_DEFAULT_LIMIT` or `BASYX_SERVER_PAGINATION_DEFAULT_LIMIT` |
| `server.pagination.maxLimit` | `SERVER_PAGINATION_MAX_LIMIT` or `BASYX_SERVER_PAGINATION_MAX_LIMIT` |
| `abac.policyFileImport` | `ABAC_POLICY_FILE_IMPORT` or `BASYX_ABAC_POLICY_FILE_IMPORT` |
| `abac.policyScope` | `ABAC_POLICY_SCOPE` or `BASYX_ABAC_POLICY_SCOPE` |
| `abac.managementApi.enabled` | `ABAC_MANAGEMENT_API_ENABLED`, `ABAC_MANAGEMENTAPI_ENABLED`, or `BASYX_ABAC_MANAGEMENT_API_ENABLED` |
| `general.bulkBatchLimit` | `GENERAL_BULK_BATCH_LIMIT` or `BASYX_GENERAL_BULK_BATCH_LIMIT` |
| `history.mode` | `BASYX_HISTORY_MODE` |
| `history.retentionDays` | `BASYX_HISTORY_RETENTION_DAYS` |
| `history.fullSnapshotInterval` | `BASYX_HISTORY_FULL_SNAPSHOT_INTERVAL` |
| `history.immutability` | `BASYX_HISTORY_IMMUTABILITY` |
| `history.auditIdentityMode` | `BASYX_AUDIT_IDENTITY_MODE` |
| `history.evidence.enabled` | `BASYX_HISTORY_EVIDENCE_ENABLED` |
| `history.evidence.provider` | `BASYX_HISTORY_EVIDENCE_PROVIDER` |
| `history.evidence.bucket` | `BASYX_HISTORY_EVIDENCE_BUCKET` |
| `history.evidence.prefix` | `BASYX_HISTORY_EVIDENCE_PREFIX` |
| `history.evidence.region` | `BASYX_HISTORY_EVIDENCE_REGION` |
| `history.evidence.endpoint` | `BASYX_HISTORY_EVIDENCE_ENDPOINT` |
| `history.evidence.accessKeyId` | `BASYX_HISTORY_EVIDENCE_ACCESS_KEY_ID` |
| `history.evidence.secretAccessKey` | `BASYX_HISTORY_EVIDENCE_SECRET_ACCESS_KEY` or `BASYX_HISTORY_EVIDENCE_SECRET_KEY` |
| `history.evidence.pathStyle` | `BASYX_HISTORY_EVIDENCE_PATH_STYLE` or `BASYX_HISTORY_EVIDENCE_USE_PATH_STYLE` |
| `history.evidence.retentionMode` | `BASYX_HISTORY_EVIDENCE_RETENTION_MODE` |
| `history.evidence.retentionDays` | `BASYX_HISTORY_EVIDENCE_RETENTION_DAYS` |
| `history.evidence.writeTimeoutSeconds` | `BASYX_HISTORY_EVIDENCE_WRITE_TIMEOUT_SECONDS` |
| `history.evidence.signing.privateKeyPath` | `BASYX_HISTORY_EVIDENCE_SIGNING_PRIVATE_KEY_PATH` |
| `history.evidence.signing.publicKeyPath` | `BASYX_HISTORY_EVIDENCE_SIGNING_PUBLIC_KEY_PATH` |
| `history.evidence.signing.required` | `BASYX_HISTORY_EVIDENCE_SIGNING_REQUIRED` |
| `history.integrityAnchor.provider` | `BASYX_HISTORY_INTEGRITY_ANCHOR_PROVIDER` |
| `eventing.enabled` | `BASYX_EVENTING_ENABLED` |
| `eventing.format` | `BASYX_EVENTING_FORMAT` |
| `eventing.sinks` | `BASYX_EVENTING_SINKS` (comma-separated) |
| `eventing.outboxEnabled` | `BASYX_EVENTING_OUTBOX_ENABLED` |
| `eventing.topicPrefix` | `BASYX_EVENTING_TOPIC_PREFIX` |
| `eventing.sourceBaseUrl` | `BASYX_EVENTING_SOURCE_BASE_URL` |
| `eventing.schemaBaseUrl` | `BASYX_EVENTING_SCHEMA_BASE_URL` |
| `eventing.mqtt.broker` | `BASYX_EVENTING_MQTT_BROKER` |
| `eventing.mqtt.clientId` | `BASYX_EVENTING_MQTT_CLIENT_ID` |
| `eventing.mqtt.sinkId` | `BASYX_EVENTING_MQTT_SINK_ID` |
| `eventing.mqtt.qos` | `BASYX_EVENTING_MQTT_QOS` |
| `eventing.mqtt.retained` | `BASYX_EVENTING_MQTT_RETAINED` |
| `eventing.mqtt.username` | `BASYX_EVENTING_MQTT_USERNAME` |
| `eventing.mqtt.password` | `BASYX_EVENTING_MQTT_PASSWORD` |
| `eventing.mqtt.usernameFile` | `BASYX_EVENTING_MQTT_USERNAME_FILE` |
| `eventing.mqtt.passwordFile` | `BASYX_EVENTING_MQTT_PASSWORD_FILE` |
| `eventing.mqtt.caFile` | `BASYX_EVENTING_MQTT_CA_FILE` |
| `eventing.mqtt.certificateFile` | `BASYX_EVENTING_MQTT_CERTIFICATE_FILE` |
| `eventing.mqtt.keyFile` | `BASYX_EVENTING_MQTT_KEY_FILE` |
| `eventing.kafka.brokers` | `BASYX_EVENTING_KAFKA_BROKERS` (comma-separated) |
| `eventing.kafka.topic` | `BASYX_EVENTING_KAFKA_TOPIC` |
| `eventing.kafka.sinkId` | `BASYX_EVENTING_KAFKA_SINK_ID` |
| `eventing.kafka.clientId` | `BASYX_EVENTING_KAFKA_CLIENT_ID` |
| `eventing.kafka.producerBatchMaxBytes` | `BASYX_EVENTING_KAFKA_PRODUCER_BATCH_MAX_BYTES` |
| `eventing.kafka.tlsEnabled` | `BASYX_EVENTING_KAFKA_TLS_ENABLED` |
| `eventing.kafka.caFile` | `BASYX_EVENTING_KAFKA_CA_FILE` |
| `eventing.kafka.certificateFile` | `BASYX_EVENTING_KAFKA_CERTIFICATE_FILE` |
| `eventing.kafka.keyFile` | `BASYX_EVENTING_KAFKA_KEY_FILE` |
| `eventing.kafka.saslMechanism` | `BASYX_EVENTING_KAFKA_SASL_MECHANISM` |
| `eventing.kafka.username` | `BASYX_EVENTING_KAFKA_USERNAME` |
| `eventing.kafka.password` | `BASYX_EVENTING_KAFKA_PASSWORD` |
| `eventing.kafka.usernameFile` | `BASYX_EVENTING_KAFKA_USERNAME_FILE` |
| `eventing.kafka.passwordFile` | `BASYX_EVENTING_KAFKA_PASSWORD_FILE` |
| `eventing.amqp.broker` | `BASYX_EVENTING_AMQP_BROKER` |
| `eventing.amqp.address` | `BASYX_EVENTING_AMQP_ADDRESS` |
| `eventing.amqp.sinkId` | `BASYX_EVENTING_AMQP_SINK_ID` |
| `eventing.amqp.hostName` | `BASYX_EVENTING_AMQP_HOST_NAME` |
| `eventing.amqp.username` | `BASYX_EVENTING_AMQP_USERNAME` |
| `eventing.amqp.password` | `BASYX_EVENTING_AMQP_PASSWORD` |
| `eventing.amqp.usernameFile` | `BASYX_EVENTING_AMQP_USERNAME_FILE` |
| `eventing.amqp.passwordFile` | `BASYX_EVENTING_AMQP_PASSWORD_FILE` |
| `eventing.amqp.caFile` | `BASYX_EVENTING_AMQP_CA_FILE` |
| `eventing.amqp.certificateFile` | `BASYX_EVENTING_AMQP_CERTIFICATE_FILE` |
| `eventing.amqp.keyFile` | `BASYX_EVENTING_AMQP_KEY_FILE` |
| `eventing.feed.enabled` | `BASYX_EVENTING_FEED_ENABLED` |
| `eventing.feed.maxAgeDays` | `BASYX_EVENTING_FEED_MAX_AGE_DAYS` |
| `eventing.feed.hardDeleteGraceDays` | `BASYX_EVENTING_FEED_HARD_DELETE_GRACE_DAYS` |
| `eventing.feed.sourceBaseUrl` | `BASYX_EVENTING_FEED_SOURCE_BASE_URL` |
| `eventing.feed.schemaBaseUrl` | `BASYX_EVENTING_FEED_SCHEMA_BASE_URL` |
| `eventing.feed.cleanupIntervalHours` | `BASYX_EVENTING_FEED_CLEANUP_INTERVAL_HOURS` |
| `eventing.feed.publishIntervalMillis` | `BASYX_EVENTING_FEED_PUBLISH_INTERVAL_MILLIS` |
| `rebac.subjectClaim` | `REBAC_SUBJECT_CLAIM` |
| `rebac.groupClaim` | `REBAC_GROUP_CLAIM` |
| `rebac.administrators` | `REBAC_ADMINISTRATORS` (comma-separated) |

The legacy Viper-derived names without word-separating underscores, such as `SERVER_READTIMEOUTSECONDS`, remain supported. The explicit aliases are applied after normal environment-variable decoding and therefore take precedence when both forms are set.

To use a complete PostgreSQL DSN, set only `POSTGRES_DSN` for the connection details. Do not also set individual connection variables such as `POSTGRES_HOST` or `POSTGRES_SSLMODE`:

```bash
POSTGRES_DSN=postgres://admin:secret@db:5432/basyx?sslmode=require
POSTGRES_MAXOPENCONNECTIONS=50
POSTGRES_MAXIDLECONNECTIONS=25
POSTGRES_CONNMAXLIFETIMEMINUTES=5
POSTGRES_CONNMAXIDLETIMEMINUTES=0
```

## Security Files

The shared configuration may reference these security-sensitive files:

- `oidc.trustlistPath`
- `abac.modelPath`
- `jws.privateKeyPath`
- `jws.certificateChainPath`
- `history.evidence.signing.privateKeyPath`
- `history.evidence.signing.publicKeyPath`
- `postgres.sslcert`
- `postgres.sslkey`
- `postgres.sslrootcert`
- `eventing.mqtt.usernameFile`, `eventing.mqtt.passwordFile`, `eventing.mqtt.caFile`, `eventing.mqtt.certificateFile`, `eventing.mqtt.keyFile`
- `eventing.kafka.usernameFile`, `eventing.kafka.passwordFile`, `eventing.kafka.caFile`, `eventing.kafka.certificateFile`, `eventing.kafka.keyFile`
- `eventing.amqp.usernameFile`, `eventing.amqp.passwordFile`, `eventing.amqp.caFile`, `eventing.amqp.certificateFile`, `eventing.amqp.keyFile`

In containers, paths are resolved inside the container filesystem. Mount the files or their parent directory and point the YAML value or environment variable to the mounted path.

## Notes

- The BaSyx Configuration Service mainly uses the `postgres` section.
- Repository, environment, and AASX File Server endpoints use `general.uploadMaxSizeBytes` for compressed request limits.
- Submodel Repository and AAS Environment use `general.delegatedOperationResponseMaxSizeBytes` for delegated Submodel Operation responses.
- Services that process AASX packages use the `general.aasxMax*` settings for expanded package limits.
- AAS Environment additionally supports `general.aasPreconfigPaths`.
- AAS Repository, Submodel Repository, and AAS Environment use the registry synchronization settings when enabled.
- AAS Repository, Submodel Repository, and AAS Environment use the `eventing` settings.
- AAS Repository, Submodel Repository, Concept Description Repository, AAS Registry, Submodel Registry, Discovery, AASX File Server, AAS Environment, and DPP API use the `rebac` settings. Company Lookup and Digital Twin Registry always disable ReBAC.
