---
url: /reference/configuration.md
description: Every key of the host's wow.* configuration, its type, default and meaning.
---

# Configuration Keys

Every key of the `wow.*` configuration a Spring Boot host reads, 233 in all, generated from the host's configuration metadata ([`spring-configuration-metadata.json`](https://github.com/Ahoo-Wang/Wow/blob/main/host/spring-boot/src/main/resources/META-INF/spring-configuration-metadata.json)). Spring Boot binds them relaxed: `max-concurrent`, `maxConcurrent` and `MAX_CONCURRENT` are one key. A `wow.*` key that no property names is reported when the node starts. A key below a map takes the map's key in brackets when it holds a dot: `wow.aggregates.[shop.cart].tenant`.

## `wow`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.aggregates` | `Map<String,Aggregate>` |  | Per aggregate type, by `<context>.<aggregate>` (bracketed in a property name: `wow.aggregates.[shop.cart]`). |
| `wow.backend` | `String` | `auto` | The backend of a node that names no `wow.stores`: `auto` (the default) takes PostgreSQL or MongoDB by the application's `DataSource` or `MongoClient`, and refuses to start without either unless a profile of `wow.internal.dev-profiles` is active or the internal port binds the loopback interface alone; `memory` keeps the data in memory on purpose, for one node whose data does not outlive it. |
| `wow.ddl` | `StoreDdl` | `auto` | How the stores manage the framework's tables, collections and indexes: `auto` creates what is missing (the default), `validate` creates nothing and refuses a store whose schema is missing or in another format, `none` creates and checks nothing; `wow.stores.<name>.ddl` overrides it for one store. |
| `wow.schedules` | `Map<String,Schedule>` |  | Per `@OnSchedule` job, by its name: `wow.schedules.<job>.*`. |
| `wow.service` | `String` |  | The service's name; `spring.application.name` when not set. |
| `wow.standby` | `Boolean` | `false` | Whether the node starts on standby: through the dual-run window of a migration's cut-over it serves reads of what `migrate-data` imports, refuses every command (`wow.node.standby`, 503), and handles no subscription, retries no parked entry and runs no job, until its operator resumes it on the internal port (`POST /wow/node/resume` with the cluster token, naming who resumes it). A subscription that starts at the end of its stream starts where the stream ends at the resume. |
| `wow.stores` | `Map<String,Store>` |  | The named store instances, by name: `wow.stores.<name>.type` and its settings. |
| `wow.subscriptions` | `Map<String,SubscriptionSettings>` |  | Per subscription, by its name (bracketed in a property name: `wow.subscriptions.[CartSaga.onOrderCreated]`). |

## `wow.calls`

The synchronous calls of the node's sagas to the command gateways of other contexts: each context's base URL (contexts, `wow.calls.contexts.<context>.url`: the target's internal port, `http://host:port` of an address in its internal network, never its public port, which refuses calls), the service token the node presents as `Authorization: Bearer` (token; an application's me.ahoo.wow.client.ServiceCredential bean, such as one that renews a CoSec token, replaces it), how long one call may take (timeout) and connect (connectTimeout). A saga that calls a context without a URL fails, then parks.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.calls.connect-timeout` | `Duration` | `PT2S` |  |
| `wow.calls.contexts` | `Map<String,CallTarget>` |  |  |
| `wow.calls.timeout` | `Duration` | `PT10S` |  |
| `wow.calls.token` | `String` |  |  |

## `wow.clickhouse`

ClickHouse's configuration, `wow.clickhouse.*`, read when wow-clickhouse is on the classpath (ClickHouseAutoConfiguration): - `wow.clickhouse.endpoint`: the server's HTTP endpoint, such as `http://localhost:8123`; ClickHouse is wired only when it is set. `username` (`default`) and `password` (empty) sign in; `database` (`default`) holds the tables. - `wow.clickhouse.sink.enabled`: whether the Manifest's published streams are written to ClickHouse; on by default. - `wow.clickhouse.sink.ddl`: `auto` (create what is missing, then validate; the default), `validate` or `none`. - `wow.clickhouse.sink.batch-size`: the most records one insert carries (1000). - `wow.clickhouse.sink.deduplication-window`: how many recent inserts each table remembers to drop a retried one (1000), the `non_replicated_deduplication_window` of the tables `auto` creates. - `wow.clickhouse.sink.aggregates.<context>.<aggregate>.history.enabled`: whether every commit is kept (on by default); off keeps only the current state. `...history.ttl`: how long the history is kept, forever when unset. - `wow.clickhouse.query.enabled`: whether the datasets are served from ClickHouse; on by default. - `wow.clickhouse.query.metadata-ttl`: how long a dataset table's columns and engine are reused before `system.columns` is read again (1m). - `wow.clickhouse.query.settings.<name>`: server settings every dataset query runs under, such as `max_memory_usage`; the execution time comes from each query's budget (`wow.query.http.timeout`), not from here.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.clickhouse.database` | `String` | `default` |  |
| `wow.clickhouse.endpoint` | `String` |  |  |
| `wow.clickhouse.password` | `String` | `""` |  |
| `wow.clickhouse.username` | `String` | `default` |  |

## `wow.clickhouse.query`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.clickhouse.query.enabled` | `Boolean` | `true` |  |
| `wow.clickhouse.query.metadata-ttl` | `Duration` | `PT1M` |  |
| `wow.clickhouse.query.settings` | `Map<String,String>` |  |  |

## `wow.clickhouse.sink`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.clickhouse.sink.aggregates` | `Map<String,Map<String,Aggregate>>` |  | By context, then by aggregate name. |
| `wow.clickhouse.sink.batch-size` | `Integer` | `1000` |  |
| `wow.clickhouse.sink.ddl` | `String` | `auto` |  |
| `wow.clickhouse.sink.deduplication-window` | `Integer` | `1000` |  |
| `wow.clickhouse.sink.enabled` | `Boolean` | `true` |  |

## `wow.clients`

The typed clients of the peer contexts over HTTP, `wow-client`'s: a peer context listed in contexts is reached over HTTP by the service's me.ahoo.wow.api.client.CommandClient and me.ahoo.wow.api.read.QueryApi beans, in its ClientTarget.mode (or mode); any other context as without these clients. Where each is: its public routes' base ClientTarget.url (user mode) and its internal port's ClientTarget.internalUrl (service mode; `wow.calls.contexts.<context>.url` when not set, which the sagas' calls use too), or what an application's me.ahoo.wow.client.EndpointResolver bean resolves. Each request is answered within timeout (a command's longer wait aside) after connecting within connectTimeout, and retried as retry says; a user-mode request forwards the forwardedHeaders of the public request it serves. The service mode presents the me.ahoo.wow.client.ServiceCredential bean (`wow.calls.token`).

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.clients.connect-timeout` | `Duration` | `PT2S` |  |
| `wow.clients.contexts` | `Map<String,ClientTarget>` |  |  |
| `wow.clients.forwarded-headers` | `List<String>` | `Authorization` |  |
| `wow.clients.mode` | `Mode` | `service` |  |
| `wow.clients.timeout` | `Duration` | `PT30S` |  |

## `wow.clients.retry`

How a problem a retry may get past is retried (me.ahoo.wow.client.RetryPolicy).

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.clients.retry.backoff` | `Duration` | `PT0.2S` |  |
| `wow.clients.retry.max-attempts` | `Integer` | `3` |  |
| `wow.clients.retry.max-retry-after` | `Duration` | `PT5S` |  |

## `wow.cluster`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.cluster.drain-timeout` | `Duration` | `PT10S` |  |
| `wow.cluster.forward-timeout` | `Duration` |  | How long a forward waits for the owner's reply, capped by the command's own deadline: `wow.wait.default-timeout` when not set, so a node whose waits are longer forwards as long. A forward the owner received and did not answer by then is reported unknown (`wow.command.unknown`), not run here, so a retry with the same request id is safe. |
| `wow.cluster.heartbeat-interval` | `Duration` | `PT3S` |  |
| `wow.cluster.lease-parallelism` | `Integer` | `16` |  |
| `wow.cluster.lease-ttl` | `Duration` | `PT10S` |  |
| `wow.cluster.max-in-flight-per-peer` | `Integer` | `1024` |  |
| `wow.cluster.member-ttl` | `Duration` | `PT10S` |  |
| `wow.cluster.reconcile-interval` | `Duration` | `PT1S` |  |
| `wow.cluster.reconcile-timeout` | `Duration` | `PT1M` | How long one reconcile may run before the node takes itself as stuck and leaves the members until it ends. |
| `wow.cluster.renew-interval` | `Duration` | `PT3S` |  |
| `wow.cluster.route-refresh-interval` | `Duration` | `PT1S` |  |
| `wow.cluster.router` | `Routing` |  | Where commands run; `local` on the memory backend and `partitioned` on the others when not set. |

## `wow.contexts`

The bounded contexts the node serves: every context of the domain modules on the class path by default; only those include names when set; less those exclude names. A name that is no context on the class path is refused at startup.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.contexts.exclude` | `Set<String>` |  |  |
| `wow.contexts.include` | `Set<String>` |  |  |

## `wow.cosec`

The keys wow-cosec reads (`wow.cosec.*`), bound here so they are checked and documented as the others are.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.cosec.trust-injected-app` | `Boolean` | `false` | Whether a service in CoSec's inject mode, behind a CoSec gateway (`cosec.inject.enabled=true` with `cosec.authorization.enabled=false`), records the application of the requests the gateway forwards, their `CoSec-App-Id`, as the caller's app and the request fact `app_id`: a trust decision, off by default; in any other mode it fails the startup. |

## `wow.cosec.authorizer`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.cosec.authorizer.enabled` | `Boolean` | `false` | Whether CoSec authorizes every command as its caller (`CoSecCommandAuthorizer`), not only the public routes by path; the services' calls are authorized by CoSec whatever this says. |

## `wow.cosec.cross-scope`

The callers that may act across scopes: by CoSec role or policy.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.cosec.cross-scope.policies` | `Set<String>` |  |  |
| `wow.cosec.cross-scope.roles` | `Set<String>` |  |  |

## `wow.cosec.service`

The service identities, whose synchronous calls the internal port takes.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.cosec.service.policies` | `Set<String>` |  |  |
| `wow.cosec.service.roles` | `Set<String>` |  |  |

## `wow.cosec.tenant-level`

The tenant-level callers, who read and write every aggregate of their tenant.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.cosec.tenant-level.policies` | `Set<String>` |  |  |
| `wow.cosec.tenant-level.roles` | `Set<String>` |  |  |

## `wow.cosid`

The keys wow-cosid reads (`wow.cosid.*`), bound here as cosec is.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.cosid.global-generator` | `String` |  | The CosId generator of the node's global ids (command ids, wait ids); CosId's own `cosid` when not set. |

## `wow.executor`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.executor.lane-capacity` | `Integer` | `1024` | The commands one aggregate may have waiting in the executor. |
| `wow.executor.max-in-flight` | `Integer` | `4096` | The commands the node may have in flight, all contexts together: beyond it a command is refused as `wow.command.busy` (429, with `Retry-After`) instead of queued. |

## `wow.health`

The node's health and its probes: `wow.health.*`.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.health.enabled` | `Boolean` | `true` | Whether the host contributes the node's health to Actuator and its probe groups. |
| `wow.health.sample-interval` | `Duration` | `PT10S` | How often the node samples what a probe must not wait for (the logs' heads, the subscriptions' lag and parked entries) and probes its stores. |

## `wow.health.executor`

The node's executors.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.health.executor.saturation` | `Double` | `0.9` | The share of `wow.executor.max-in-flight` in flight at which the executor is degraded. |

## `wow.health.liveness`

Whether the node is stuck, so a restart helps; a storage outage never fails it.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.health.liveness.stall-timeout` | `Duration` | `PT3M` | How long the node's dispatcher may not tick, or commands in flight not finish while every log answers, before the node is not live. |

## `wow.health.ownership`

The node's share of each lease group's partitions.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.health.ownership.handover-grace` | `Duration` | `PT1M` | How long the owned partitions may differ from the node's share (a hand-over) before the group is degraded, and down (the node not ready) when it owns none of a share that is not empty. |

## `wow.health.stores`

The node's event logs, as its own calls and a probe of their heads see them.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.health.stores.down-after` | `Duration` | `PT15S` | How long a log must keep failing, with no success since, before it is down and the node not ready: a failed call or probe, or a probe that timed out (after this long, at least a second); a probe still under way is no failure. `0s` is down at the first failure. |
| `wow.health.stores.window` | `Duration` | `PT1M` | A log with a failure this recent is degraded. |

## `wow.health.subscriptions`

The node's subscriptions, never a reason to take it out of service.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.health.subscriptions.lag-degraded-at` | `Long` | `10000` | The entries a subscription may be behind the log's head before it is degraded. |
| `wow.health.subscriptions.parked-degraded-at` | `Integer` | `100` | The parked entries of a subscription, counted up to this, at which it is degraded (as when they grow). |

## `wow.health.topics`

The topics of other contexts the node reads.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.health.topics.required` | `Boolean` | `true` | Whether a topic the node is still joining makes it not ready; degraded only when false. |

## `wow.http`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.http.client-facts` | `Boolean` | `true` | Whether the public port records each command's client address and `User-Agent` as the request facts `remote_ip` and `user_agent` (09 §17 G-10, as 9.x did), for audit. They are personal data, and written into the event stream, which is never deleted: once recorded, they cannot be erased. On by default; off records neither. |
| `wow.http.errors` | `Map<String,Error>` |  | The HTTP status of an error code, by code (bracketed in a property name: `wow.http.errors.[a.b].status`). |
| `wow.http.global-errors` | `Boolean` | `true` | Answers what fails on the application's own routes as the framework routes' problem+json: on by default; off leaves those errors to Spring's error handling. |
| `wow.http.request-facts` | `Set<String>` |  | The request headers copied into a command's headers, beside the standard request facts the edge records: `app_id`, the application the caller resolver authenticated, and on the public port `user_agent` and `remote_ip` (09 §17 G-10); none of the three can be listed. `remote_ip` is the server's remote address: behind a proxy, set `server.forward-headers-strategy` to have the server take it from the proxy's forwarded headers. |
| `wow.http.services` | `Map<String,Service>` |  | The service identities the internal port trusts by their static tokens, by service name: a call whose `Authorization: Bearer` token is one of them is that service's, which alone may send `Wow-Call`, and only to the internal port. For a deployment without CoSec; with CoSec, `wow.cosec.service.roles` names its service principals. |

## `wow.http.manifest`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.http.manifest.enabled` | `Boolean` | `true` | Serves `GET /wow/manifest`. |

## `wow.http.openapi`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.http.openapi.enabled` | `Boolean` | `true` | Serves `GET /wow/openapi`, the OpenAPI 3.1 document of the routes. |

## `wow.http.openapi.springdoc`

The springdoc bridge, when the application has springdoc.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.http.openapi.springdoc.enabled` | `Boolean` | `true` | Merges the Wow routes into springdoc's documents. |
| `wow.http.openapi.springdoc.group` | `String` | `wow` | The springdoc group of the Wow routes alone: `/v3/api-docs/<group>`. |

## `wow.internal`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.internal.connect-timeout` | `Duration` | `PT1S` | How long a peer has to accept a connection of the cluster channel or the signal sender. |
| `wow.internal.dev-profiles` | `Set<String>` | `dev, local, test, demo` | The development profiles, `dev`, `local`, `test` and `demo`: in them the internal port may run without token, and `wow.backend=auto` falls back to memory when no durable backend is available. |
| `wow.internal.host` | `String` |  | The address the internal server binds, `0.0.0.0` for every interface; when not set, the host of the member address the node advertises (`wow.node.address`, else this host's address). |
| `wow.internal.port` | `Integer` | `9090` | The port of the internal server: the cluster channel and the wait signals; 0 for one the system chooses, which the node advertises once the server is bound. |
| `wow.internal.signal-queue-capacity` | `Integer` | `1024` | The signals queued per origin node before they are dropped. |
| `wow.internal.token` | `List<String>` |  | The service's cluster tokens, at least 32 characters each: the secret its nodes share, which alone opens the cluster channel (`/wow/internal/forward`) and the wait signals of the internal port. A node sends the first and takes any, so a token is rotated by adding the new one second, then moving it first on every node, then removing the old one; a comma-separated value (`WOW_INTERNAL_TOKEN`) is a list. A service token of `wow.http.services` opens the call routes, not these, and this token no call route. A node without one starts only in a profile of devProfiles, or with its internal port bound to the loopback interface alone, and warns: its cluster routes then take every request. |

## `wow.kafka`

The Kafka transport's configuration, `wow.kafka.*`, read when wow-kafka is on the classpath (KafkaAutoConfiguration): - `wow.kafka.bootstrap-servers`: the brokers; the transport, the Kafka Sink and the Kafka-sourced subscriptions are wired only when it is set. - `wow.kafka.properties.*`: passed to every producer and consumer as they are (`security.protocol`, `client.id`, `linger.ms`,...), except the settings the transport's guarantees rest on (`me.ahoo.wow.kafka.KafkaOptions`). - `wow.kafka.topic-prefix`: put before every topic name, such as `staging.` for `staging.wow.shop.order.state`; none by default, so the topics are the Manifest's. The sinks write under it and the subscriptions of other contexts' streams read under it. - `wow.kafka.poll-timeout`: the longest a consumer's poll waits (200 ms by default); a read that is cancelled, and a partition that joins or leaves a topic's shared consumer, take effect within it. - `wow.kafka.offset-gap`: what a read of another context's topic does when its partition no longer holds the offset it is to read from (the retention deleted it, or the topic was created again): `fail` (the default) fails the read, so the subscription stays where it is, logging errors; to recover, set `earliest` and restart the node. `earliest` reads on from the earliest offset retained; the skipped range is lost to that subscription. Either way the gap is logged as an error and counted (`wow.kafka.offset.gaps`). - `wow.kafka.command-intake`: whether each context the node hosts executes the commands other contexts send to its command topic, `wow.<context>.commands`; off by default. The topic must exist with as many partitions as the context's log, and the node checks it at start. Sending commands to other contexts and reading their receipts need no switch. A context with an aggregate type routed through Kafka takes them whatever this says. - `wow.kafka.topics.ddl`: how the node manages the topics its contexts own at its start, see Topics: `validate` (the default) fails the start when one is missing or a command topic does not have exactly `wow.log.partitions` partitions; `auto` creates the missing ones first; `none` checks only the command topics the node reads, whose partitions must still be the log's. - `wow.kafka.topics.replication-factor` (the broker's default), `wow.kafka.topics.min-insync-replicas` (the broker's default) and `wow.kafka.topics.configs.*` (`retention.ms`,...): what `auto` creates a topic with. A command accepted on a command topic is durable only when both are at least 2: at start the node warns when they are below that, as configured or as the command topics of its contexts are, unless a profile of Topics.devProfiles is active (`dev`, `local`, `test`, `demo`). - `wow.kafka.topics.partition-refresh` (30s): how often the node looks for partitions added to the topics it reads (other contexts' streams, its receipts), and its consumers' `metadata.max.age.ms`; the new partitions are read within about twice this.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.kafka.bootstrap-servers` | `String` |  |  |
| `wow.kafka.command-intake` | `Boolean` | `false` |  |
| `wow.kafka.offset-gap` | `OffsetGapPolicy` | `fail` |  |
| `wow.kafka.poll-timeout` | `Duration` | `PT0.2S` |  |
| `wow.kafka.properties` | `Map<String,String>` |  |  |
| `wow.kafka.topic-prefix` | `String` | `""` |  |

## `wow.kafka.topics`

The topics the node's contexts own: their DDL, and how `auto` creates them.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.kafka.topics.configs` | `Map<String,String>` |  |  |
| `wow.kafka.topics.ddl` | `TopicDdl` | `validate` |  |
| `wow.kafka.topics.dev-profiles` | `Set<String>` | `dev, local, test, demo` | The profiles that run without durable topics, a single broker: no warning about it. |
| `wow.kafka.topics.min-insync-replicas` | `Integer` |  |  |
| `wow.kafka.topics.partition-refresh` | `Duration` | `PT30S` |  |
| `wow.kafka.topics.replication-factor` | `Short` |  |  |

## `wow.log`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.log.max-poll-interval` | `Duration` | `PT1S` | The longest a tail waits between polls that find nothing: an idle tail backs off up to it. |
| `wow.log.partitions` | `Integer` | `64` | The partitions of each context's log: fixed once the log holds entries, a power of two that divides 4096, refused at start otherwise. |
| `wow.log.poll-interval` | `Duration` | `PT0.05S` | How often a tail polls while it finds entries. |
| `wow.log.store` | `String` |  | The store of every aggregate's log that `wow.aggregates.[<context>.<aggregate>].log.store` does not name: one of `wow.stores`, the only one when not set. |

## `wow.log.append-queue`

The partition append queue; each setting not given is the backend's preset (AppendQueueOptions).

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.log.append-queue.enabled` | `Boolean` |  |  |
| `wow.log.append-queue.max-batch` | `Integer` |  |  |

## `wow.log.handoff`

The hand-over of the node's own commits to its tails in memory: a tail waits its gap wait for a missing sequence and maxPollInterval without a commit before it reads the store.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.log.handoff.capacity` | `Integer` | `1024` |  |
| `wow.log.handoff.enabled` | `Boolean` | `true` |  |
| `wow.log.handoff.gap-wait` | `Duration` | `PT1S` | The longest a tail waits for a missing sequence that an append of the node still in flight may hold; once every such append returned without it, the tail reads the store at once. |

## `wow.mongo`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.mongo.database` | `String` | `wow` | The database of the log and the stores on the MongoDB backend. |
| `wow.mongo.transaction-timeout` | `Duration` | `PT5S` | How long an append's transaction, its retries included, may take on a store that sets none in its `options.transaction-timeout`: below the default wait timeout, so a command appending during an outage is answered before its wait ends. |

## `wow.node`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.node.address` | `String` |  | The `host:port` of this node's internal port, as the other nodes reach it. |
| `wow.node.id` | `String` |  | This node's id among the nodes of the service; a new one each start when not set. |

## `wow.opentelemetry`

Observability's configuration, `wow.opentelemetry.*`, read when wow-opentelemetry is on the classpath (ObservabilityAutoConfiguration): - `wow.opentelemetry.enabled`: whether the node is traced and measured; on by default. - `wow.opentelemetry.sample-interval`: how often the gauges that are read, not heard (parked entries, the subscriptions' positions and a rebuild's progress), are sampled (15s).

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.opentelemetry.enabled` | `Boolean` | `true` |  |
| `wow.opentelemetry.sample-interval` | `Duration` | `PT15S` |  |

## `wow.ops`

The operator's side of the parked entries: enabled serves their routes under `/wow/ops/parked` on the public port, off by default; webhook tells a URL when an entry parks or spends its retries.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.ops.enabled` | `Boolean` | `false` |  |

## `wow.ops.publish`

Publishing the failure ledger to the ops context: when enabled, each entry goes to the `parked` aggregate of context (`ops`) through its command topic, at once when it parks and on a scan every interval (30s); the node needs the Kafka transport.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.ops.publish.context` | `String` | `ops` |  |
| `wow.ops.publish.enabled` | `Boolean` | `false` |  |
| `wow.ops.publish.interval` | `Duration` | `PT30S` |  |

## `wow.ops.webhook`

The webhook of the parked entries, posted to url when set: in Wow's JSON (`generic`) or as a WeCom robot's Markdown message (`wecom`) (format), for the events listed (`parked`, `exhausted`; both by default), within timeout, with headers added to each request.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.ops.webhook.events` | `Set<Kind>` | `parked, exhausted` |  |
| `wow.ops.webhook.format` | `Format` | `generic` |  |
| `wow.ops.webhook.headers` | `Map<String,String>` |  |  |
| `wow.ops.webhook.timeout` | `Duration` | `PT5S` |  |
| `wow.ops.webhook.url` | `java.net.URI` |  |  |

## `wow.query`

The query plane's configuration, `wow.query.*`, read by QueryAutoConfiguration: - `wow.query.enabled` (true): whether the query routes and gateways are wired at all. - `wow.query.http.*`: the HTTP budget (QueryBudget) every query route runs under: `max-list-size` (1000), `max-page-size` (100), `max-page-window` (10000), `max-filter-nodes` (128), `max-filter-values` (1000), `allow-expensive-operators` (true), `max-residual-groups` (10000), `count-limit`, `allow-exact-count` (false), `timeout` (30s), `unindexed-sort-expensive` (false); and `default-list-size` (100), the limit of a list that names none. A limit of `0` is off. - `wow.query.in-process.*`: the budget of in-process queries, the typed me.ahoo.wow.api.read.QueryApi's, of trusted code too: the same keys and defaults as `wow.query.http.*`, its `default-list-size` the limit of an in-process list that names none. - `wow.query.in-process-timeout` (30s): the timeout of an in-process query that has no budget, trusted code's that dropped it (`QueryApi.trusted().unbounded()`). - `wow.query.require-explicit-entry`, `wow.query.require-authenticated-scope` (both false): the entry gates. - `wow.query.cross-tenant.enabled`: whether a caller that may act across scopes may query across tenants on the query routes, `Wow-Scope: tenant=*` or `tenant=("t1" "t2")`; 403 `wow.auth.forbidden` while off. A type whose tenant is fixed is still read in that tenant alone, and every such query's audit is logged at INFO (`me.ahoo.wow.query.CrossTenantAudit`). The commands and the state reads never cross tenants. - `wow.query.abac.*`: `enabled` (false), `require-principal-tags` (false), `match-missing-tag-key` (true), `attribute-prefix` (`abac.`): the caller's `abac.<key>` attributes matched against the snapshots' `tags`. - `wow.query.sensitivity.display-comparable` (true): whether filters and sorts may compare a `DISPLAY` value. - `wow.query.index.ddl`: how the snapshot backend manages the indexes of each aggregate's `x-wow-index` at startup. - `wow.query.count-cache.*`: `enabled` (true), `max-entries` (10000), `ttl` (30s): the cache of the snapshot counts, valid while the type's snapshot write checkpoint holds and for at most `ttl` (another node's write can make a cached count lag the rows by up to `ttl`); `paged-totals` serves the totals of paged queries from it too, which by default are counted with their page so that a total agrees with its rows; `events-enabled` caches the events' counts the same way, though the log runs ahead of that checkpoint, so a cached events count may lag the streams for up to `ttl`. - `wow.query.dataset.config-directory`: the working directory whose `wow/dataset/` holds dataset files, `{context}.{dataset}.json`; each takes precedence over the classpath's `META-INF/wow/dataset/` file of the same dataset. The datasets of both are served when a dataset backend is wired (ClickHouse's, say). - `wow.query.memory.enabled`: whether the snapshots on a store in memory are queried, from the in-memory reference query backend, and the events of a log in memory, scanned in memory: for local runs, demos and tests, not for production. - `wow.query.events.*`: the `events` model, served from each aggregate's log store, or the store its `wow.aggregates.[<context>.<aggregate>].query.events.store` names: `mongo-read-preference` (none, the primary), the MongoDB read preference of its queries (`secondaryPreferred`, say), so a free filter scans a secondary rather than the primary the log appends to; `wow.stores.<name>.query.read-preference` takes its place for one store, and sets that of its snapshot queries too. `unpinned`: whether an events query that names no `aggregateId` runs, over which `wow.stores.<name>.query.events.unpinned` and `wow.aggregates.[<context>.<aggregate>].query.events.unpinned` decide for one store or aggregate type; refused, it is `wow.query.invalid` (`STORAGE_UNSUPPORTED` on `aggregateId`), and the model's descriptor lists the constraint `AGGREGATE_REQUIRED`. - `wow.query.dataset.max-concurrent`: how many dataset queries the node runs at once, under one bulkhead, `datasets`, which keeps the analytics queries from taking a warehouse the Sink writes to; `0` sets none. A query beyond it waits for `wow.query.dataset.max-wait` (`wow.query.max-wait` when unset) in a queue of `wow.query.dataset.max-queued` (`wow.query.max-queued`, else 4 per permit), then is `wow.query.busy`. The bound is fixed, not adaptive: the warehouse's writes (the ClickHouse Sink's) give the node no write pressure to adapt to. - `wow.query.max-concurrent`: how many snapshot and `events` queries each store runs at once, whichever entry asks, beyond which a query is `wow.query.busy` (429, with `Retry-After`); `wow.stores.<name>.query.max-concurrent` takes its place for one store; `0` for no bound. A query beyond the bound waits in a FIFO queue at most `wow.query.max-wait` (200ms) for a permit, and is refused when the wait runs out or when `wow.query.max-queued` (4 per permit) queries wait already; `wow.stores.<name>.query.max-wait` and `.max-queued` take their place for one store. - `wow.query.adaptive.*`: the bound of a store whose `max-concurrent` neither `wow.query.max-concurrent` nor `wow.stores.<name>.query.max-concurrent` fixes adapts to the store's write pressure, evaluated every `interval` (1s): `periods` (2) periods in a row in which its appends' p90 latency is above `latency-factor` (1.5) times its baseline and at least `min-latency` (5ms), or in which `connection-waits` (1) callers wait for a connection of its pool, halve it down to `floor` (1); a calm period in which a query waited or was refused raises it by one up to `ceiling` (none: the provider's, PostgreSQL half a pool the appends share, all of a `query-data-source`'s). `enabled` (true) turns it off; `wow.stores.<name>.query.adaptive.enabled`, `.floor` and `.ceiling` take their place for one store. - `wow.query.state.max-replay`: the most commits a state point read replays from the aggregate's snapshot, or from version 1 (`GET …/state?version=` or `?at=`, `…/state/trace`, the latest state before the first snapshot); a state further away is 400 `wow.query.invalid` (`REPLAY_TOO_LONG`). `min-version-wait`: how long a read of `?minVersion=` (or `QueryApi` state of a minimum version) waits for the aggregate to reach it, its snapshot caught up with its log meanwhile; past it the read is 409 `wow.query.min-version-not-reached` with the current `version`. - `wow.query.stream.*`: a `list` streamed as server-sent events reads keyset chunks of `chunk-size` (500) records, a query permit per chunk, each record's `id` its position; after `max-hold` (30s; `0` for no bound) it ends with `end` `{"reason":"hold-expired","resumable":true}` and the client resumes it with `Last-Event-ID`. Neither `http.max-list-size` nor `http.default-list-size` bounds such a stream. A list whose sort cannot order a cursor streams without positions, under those bounds, and every `end` it sends (the hold time's included) says `"resumable":false`.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.query.enabled` | `Boolean` | `true` |  |
| `wow.query.in-process-timeout` | `Duration` | `PT30S` |  |
| `wow.query.max-concurrent` | `Integer` |  |  |
| `wow.query.max-queued` | `Integer` |  |  |
| `wow.query.max-wait` | `Duration` | `PT0.2S` |  |
| `wow.query.require-authenticated-scope` | `Boolean` | `false` |  |
| `wow.query.require-explicit-entry` | `Boolean` | `false` |  |

## `wow.query.abac`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.query.abac.attribute-prefix` | `String` | `abac.` |  |
| `wow.query.abac.enabled` | `Boolean` | `false` |  |
| `wow.query.abac.match-missing-tag-key` | `Boolean` | `true` |  |
| `wow.query.abac.require-principal-tags` | `Boolean` | `false` |  |

## `wow.query.adaptive`

`wow.query.adaptive.*`: how a store's query bound adapts to its write pressure.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.query.adaptive.ceiling` | `Integer` |  |  |
| `wow.query.adaptive.connection-waits` | `Integer` | `1` |  |
| `wow.query.adaptive.enabled` | `Boolean` | `true` |  |
| `wow.query.adaptive.floor` | `Integer` | `1` |  |
| `wow.query.adaptive.interval` | `Duration` | `PT1S` |  |
| `wow.query.adaptive.latency-factor` | `Double` | `1.5` |  |
| `wow.query.adaptive.min-latency` | `Duration` | `PT0.005S` |  |
| `wow.query.adaptive.periods` | `Integer` | `2` |  |

## `wow.query.count-cache`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.query.count-cache.enabled` | `Boolean` | `true` |  |
| `wow.query.count-cache.events-enabled` | `Boolean` | `false` |  |
| `wow.query.count-cache.max-entries` | `Integer` | `10000` |  |
| `wow.query.count-cache.paged-totals` | `Boolean` | `false` |  |
| `wow.query.count-cache.ttl` | `Duration` | `PT30S` |  |

## `wow.query.cross-tenant`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.query.cross-tenant.enabled` | `Boolean` | `false` |  |

## `wow.query.dataset`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.query.dataset.config-directory` | `String` | `config` |  |
| `wow.query.dataset.max-concurrent` | `Integer` | `8` |  |
| `wow.query.dataset.max-queued` | `Integer` |  |  |
| `wow.query.dataset.max-wait` | `Duration` |  |  |

## `wow.query.events`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.query.events.mongo-read-preference` | `String` |  |  |
| `wow.query.events.unpinned` | `UnpinnedEvents` | `allow` |  |

## `wow.query.http`

The budget of the query routes, `wow.query.http.*`.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.query.http.allow-exact-count` | `Boolean` | `false` |  |
| `wow.query.http.allow-expensive-operators` | `Boolean` | `true` |  |
| `wow.query.http.count-limit` | `Integer` | `10000` |  |
| `wow.query.http.default-list-size` | `Integer` | `100` |  |
| `wow.query.http.max-filter-nodes` | `Integer` | `128` |  |
| `wow.query.http.max-filter-values` | `Integer` | `1000` |  |
| `wow.query.http.max-list-size` | `Integer` | `1000` |  |
| `wow.query.http.max-page-size` | `Integer` | `100` |  |
| `wow.query.http.max-page-window` | `Long` | `10000` |  |
| `wow.query.http.max-residual-groups` | `Integer` | `10000` |  |
| `wow.query.http.timeout` | `Duration` | `PT30S` |  |
| `wow.query.http.unindexed-sort-expensive` | `Boolean` | `false` |  |

## `wow.query.in-process`

The budget of the in-process me.ahoo.wow.api.read.QueryApi, `wow.query.in-process.*`: the HTTP defaults.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.query.in-process.allow-exact-count` | `Boolean` | `false` |  |
| `wow.query.in-process.allow-expensive-operators` | `Boolean` | `true` |  |
| `wow.query.in-process.count-limit` | `Integer` | `10000` |  |
| `wow.query.in-process.default-list-size` | `Integer` | `100` |  |
| `wow.query.in-process.max-filter-nodes` | `Integer` | `128` |  |
| `wow.query.in-process.max-filter-values` | `Integer` | `1000` |  |
| `wow.query.in-process.max-list-size` | `Integer` | `1000` |  |
| `wow.query.in-process.max-page-size` | `Integer` | `100` |  |
| `wow.query.in-process.max-page-window` | `Long` | `10000` |  |
| `wow.query.in-process.max-residual-groups` | `Integer` | `10000` |  |
| `wow.query.in-process.timeout` | `Duration` | `PT30S` |  |
| `wow.query.in-process.unindexed-sort-expensive` | `Boolean` | `false` |  |

## `wow.query.index`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.query.index.ddl` | `QueryIndexDdl` | `auto` |  |

## `wow.query.memory`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.query.memory.enabled` | `Boolean` | `false` |  |

## `wow.query.sensitivity`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.query.sensitivity.display-comparable` | `Boolean` | `true` |  |

## `wow.query.state`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.query.state.max-replay` | `Integer` | `10000` |  |
| `wow.query.state.min-version-wait` | `Duration` | `PT2S` |  |

## `wow.query.stream`

`wow.query.stream.*`: how a streamed `list` reads its chunks and how long it runs.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.query.stream.chunk-size` | `Integer` | `500` |  |
| `wow.query.stream.max-hold` | `Duration` | `PT30S` |  |

## `wow.snapshot`

Where the snapshots are by default: `wow.snapshot.store`.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.snapshot.batch` | `Integer` | `100` |  |
| `wow.snapshot.interval` | `Duration` | `PT5S` |  |
| `wow.snapshot.pages` | `Integer` | `10` |  |
| `wow.snapshot.primary` | `String` |  |  |
| `wow.snapshot.store` | `String` |  |  |
| `wow.snapshot.targets` | `List<String>` |  |  |
| `wow.snapshot.write-batch` | `Integer` | `256` | How many snapshots of a partition the snapshot subscriptions write together at most. |

## `wow.state-cache`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.state-cache.capacity` | `Integer` |  | The aggregate states kept per context between commands; 0 keeps none. Not set (the default), each context's cache is sized from the heap (work package B.O-4): a tenth of the maximum heap (`-Xmx`), split between the node's contexts, over an estimated 1 KB per state plus 12 bytes per recent request (about 4 KB with the default 256), at least 4096 and at most 131072 states. A miss costs a snapshot and a tail read before the command is decided, so size it to the aggregates written often: set it when states are much larger than 1 KB (a capacity times the average state size should stay well within the heap), or when a context has more hot aggregates than the default holds. The `wow.state-cache` counter (`hit`, `miss`, `evicted`) shows the fit. |
| `wow.state-cache.recent-requests` | `Integer` | `256` | The requests each cached aggregate keeps of those it committed last, derived from the log, so that most commands skip looking their request id up; 0 looks every request id up. |

## `wow.state-views`

The bounds of each context's state views: `wow.state-views.*`.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.state-views.capacity` | `Integer` | `4096` |  |
| `wow.state-views.max-age` | `Duration` | `PT1M` |  |
| `wow.state-views.max-bytes` | `Long` | `67108864` |  |

## `wow.subscription`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.subscription.checkpoint-interval` | `Duration` | `PT1S` |  |
| `wow.subscription.max-in-flight` | `Integer` | `1024` |  |
| `wow.subscription.stop-timeout` | `Duration` | `PT10S` |  |

## `wow.subscription.backpressure`

How a handler whose downstream pushes back is retried, and its partition throttled.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.subscription.backpressure.initial-backoff` | `Duration` | `PT0.1S` |  |
| `wow.subscription.backpressure.jitter` | `Double` | `0.5` |  |
| `wow.subscription.backpressure.max-attempts` | `Integer` | `20` |  |
| `wow.subscription.backpressure.max-backoff` | `Duration` | `PT10S` |  |
| `wow.subscription.backpressure.min-concurrency` | `Integer` | `1` |  |

## `wow.subscription.parked`

How the parked entries are retried over the long horizon.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.subscription.parked.execution-timeout` | `Duration` | `PT2M` |  |
| `wow.subscription.parked.max-retries` | `Integer` | `10` |  |
| `wow.subscription.parked.min-backoff` | `Duration` | `PT3M` |  |
| `wow.subscription.parked.scan-interval` | `Duration` | `PT10S` |  |

## `wow.subscription.read-retry`

How a failing read of the log, or write of a store, is retried: until the node stops, the backoff growing for `max-attempts` (5) attempts, after which each failure is logged as an error and retried at `max-backoff`.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.subscription.read-retry.initial-backoff` | `Duration` | `PT0.1S` |  |
| `wow.subscription.read-retry.max-attempts` | `Integer` | `5` |  |
| `wow.subscription.read-retry.max-backoff` | `Duration` | `PT5S` |  |
| `wow.subscription.read-retry.multiplier` | `Double` | `2` |  |
| `wow.subscription.read-retry.timeout` | `Duration` |  |  |

## `wow.subscription.retry`

How a failing handler is retried before its entry is parked.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.subscription.retry.initial-backoff` | `Duration` | `PT0.1S` |  |
| `wow.subscription.retry.max-attempts` | `Integer` | `5` |  |
| `wow.subscription.retry.max-backoff` | `Duration` | `PT5S` |  |
| `wow.subscription.retry.multiplier` | `Double` | `2` |  |
| `wow.subscription.retry.timeout` | `Duration` |  |  |

## `wow.subscription.systemic`

When a partition pauses on a systemic failure.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.subscription.systemic.failing-aggregates` | `Integer` | `3` |  |
| `wow.subscription.systemic.initial-backoff` | `Duration` | `PT1S` |  |
| `wow.subscription.systemic.max-backoff` | `Duration` | `PT30S` |  |
| `wow.subscription.systemic.window` | `Duration` | `PT10S` |  |

## `wow.tenant`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.tenant.default` | `String` | `(0)` | The tenant of a caller that has none, such as the anonymous one. |

## `wow.timers`

The node's firing loop of the timers: how often it reads, with one query per timer store over every partition the node owns, the timers due within lookahead (pollInterval); how many attempts a technical failure to send one gets before it is parked under `wow.timers` (maxAttempts); when set, how late a timer may be sent before it expires instead (expiresAfter); how many commands a partition sends at once (parallelism) and, when set, at most a second (maxFireRate), the rate a backlog of overdue timers drains at; how long the claim on a timer being sent holds before another owner may send it (claimTimeout); how long a scheduled command waits in its target for its due time on the target node's clock before it fails instead, to be sent again later (maxEarlyWait).

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.timers.claim-timeout` | `Duration` | `PT30S` |  |
| `wow.timers.expires-after` | `Duration` |  |  |
| `wow.timers.lookahead` | `Duration` | `PT30S` |  |
| `wow.timers.max-attempts` | `Integer` | `10` |  |
| `wow.timers.max-early-wait` | `Duration` | `PT5S` |  |
| `wow.timers.max-fire-rate` | `Double` |  |  |
| `wow.timers.parallelism` | `Integer` | `64` |  |
| `wow.timers.poll-interval` | `Duration` | `PT1S` |  |

## `wow.topics`

The node's topics without Kafka (DX gap G27), see Topics.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.topics.in-memory` | `Boolean` | `false` | Whether the node's topics are in its JVM instead of Kafka. |

## `wow.wait`

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `wow.wait.default-timeout` | `Duration` | `PT10S` | How long a command whose request names no timeout waits (`Wow-Wait-Timeout`), whichever edge it came through: at most maxTimeout. |
| `wow.wait.max-hops` | `Integer` | `16` | The most hops of a wait plan, at most WaitOptions.MAX\_HOPS\_CAP; each saga a chain follows adds two. |
| `wow.wait.max-registered` | `Integer` | `16384` | The most waits the node holds at once: beyond it a command whose plan waits past ACCEPTED is not run and is answered `wow.command.busy` (429, with `Retry-After`), FAILED at ACCEPTED. |
| `wow.wait.max-timeout` | `Duration` | `PT1M` | The longest wait; a longer timeout is capped to it, and a node stops within it. |
| `wow.wait.unknown-contexts` | `UnknownContexts` | `allow` | Whether a wait plan may name a context the node knows no Manifest of: `allow`, checked where the chain arrives, or `reject`. |
