# Aviso Server example configuration (local-test friendly).
# Adjust URLs, secrets, and schema fields for your environment.
application:
# HTTP bind address/port.
host: "127.0.0.1"
port: 8000
# Used in generated metadata links.
base_url: "http://localhost"
# Landing-page assets.
static_files_path: "./src/static"
watch_endpoint:
# SSE heartbeat interval (seconds).
sse_heartbeat_interval_sec: 30
# Max watch connection lifetime (seconds).
connection_max_duration_sec: 3600
# Replay tuning.
replay_batch_size: 100
max_historical_notifications: 10000
replay_batch_delay_ms: 100
# Parallel event conversion workers.
concurrent_notification_processing: 15
notification_backend:
# `in_memory` or `jetstream`.
kind: in_memory
in_memory:
# Retained messages per topic.
max_history_per_topic: 10000
# Tracked topics.
max_topics: 100
# Authentication and authorization
# - disabled: API is public only when schemas do not declare stream auth rules
# - enabled:
# - admin endpoints always require auth + one of `admin_roles`
# - notify/watch/replay enforce auth only when stream has `auth.required: true`
# - if auth-o-tron is unreachable, authenticated requests fail with 503
auth:
# Enable authentication middleware.
enabled: true
# Authentication mode:
# - direct: Aviso authenticates credentials via auth-o-tron (JWT/Basic).
# - trusted_proxy: Aviso validates a forwarded Bearer JWT using jwt_secret.
mode: direct
# auth-o-tron base URL (required when enabled=true and mode=direct).
auth_o_tron_url: "http://localhost:8080"
# JWT secret (required when enabled=true).
# Keep empty when auth is disabled.
# Must match auth-o-tron signing configuration.
# For scripts/example_auth_config.yaml, this should be "your-shared-secret".
jwt_secret: "your-shared-secret"
# Realm-scoped roles allowed on /api/v1/admin/* (must be non-empty when enabled=true).
# Maps realm names (from JWT `realm` claim) to authorized role lists.
admin_roles:
localrealm: ["admin"]
# auth-o-tron request timeout (milliseconds).
timeout_ms: 5000
# trusted_proxy mode validates forwarded Bearer JWT using jwt_secret.
# No extra trusted_proxy block is needed.
# ECPDS destination authorization plugin (optional).
#
# Only honored when the binary is built with `--features ecpds`. Streams
# can opt in to this check by adding `plugins: ["ecpds"]` under their
# `auth` block (see the dissemination schema below).
#
# IMPORTANT: a build WITHOUT `--features ecpds` will refuse to start if
# any schema references the ecpds plugin, so the `plugins: ["ecpds"]`
# line and this whole section are commented out by default. Uncomment
# both together when running a build with the feature enabled.
#
# See docs/src/configuration-reference.md#ecpds for every field, and
# docs/src/authentication.md#ecpds-destination-authorization for the
# operator-facing setup guide.
#
# ecpds:
# # Service-account credentials for the ECPDS HTTP API (Basic auth).
# username: "ecpds-service-account"
# password: "service-password"
# # ECPDS server base URLs. Use https:// for any reachable host: the
# # plugin authenticates with HTTP Basic Auth, so plain http to a real
# # host would put the service-account password and per-user
# # destination lookups on the wire without TLS. Plain http:// is
# # accepted only for loopback ({127.0.0.1, [::1], localhost}) so the
# # mockito-based test fixtures can keep working; a typo from
# # https:// to http:// on a non-loopback host fails at startup.
# # No query string, no fragment.
# # All servers are queried per request and merged per partial_outage_policy.
# servers:
# - "https://ecpds-primary.example.int"
# - "https://ecpds-secondary.example.int"
# # Identifier field whose value is treated as the destination to
# # authorise. Must be present in the schema's `identifier` with
# # `required: true` so the value is guaranteed before the plugin
# # runs. The plugin reads it from the request's canonicalized
# # identifier params, NOT from topic routing, so the field does
# # NOT need to appear in `topic.key_order`.
# match_key: "destination"
# # JSON field of each ECPDS destination record matched against the
# # request's match_key value. Default: "name".
# target_field: "name"
# # Cache TTL per user (seconds). Default: 300.
# cache_ttl_seconds: 300
# # Maximum cache entries before moka TinyLFU eviction. Default: 10000.
# max_entries: 10000
# # Per-request HTTP timeouts (seconds). Defaults: 30 / 5.
# request_timeout_seconds: 30
# connect_timeout_seconds: 5
# # How tolerant the merge is when one configured server fails.
# # The destination list itself is always the union of per-server
# # responses; this option only governs failure handling.
# # - strict (default): every server must respond successfully or the
# # whole call returns 503. One ECPDS server going down takes the
# # plugin to 503 until it returns.
# # - any_success: take the union of whichever servers responded
# # successfully within the per-request timeout. Only fails when no
# # server responded usefully. Useful for federated deployments
# # where each server covers a different destination namespace and
# # you want to keep serving during a partial outage.
# partial_outage_policy: strict
metrics:
# Enable Prometheus metrics on a separate internal port.
enabled: false
# Bind address for the metrics server. Defaults to 127.0.0.1 (loopback).
# host: "127.0.0.1"
# Required when enabled=true; must differ from application.port.
# port: 9090
logging:
# OTel-aligned JSON logs.
level: info
format: json
# Schema definitions
# Each stream defines topic shape, identifier validation, payload requirement,
# and optional auth policy. These schemas are used by scripts/smoke_test.py.
notification_schema:
# Public stream (no auth block => anonymous access even when auth.enabled=true).
test_polygon:
payload:
required: true
topic:
base: "polygon"
key_order: ["date", "time"]
identifier:
date:
type: DateHandler
canonical_format: "%Y%m%d"
required: false
time:
type: TimeHandler
required: false
polygon:
type: PolygonHandler
required: true
# Authenticated stream with read/write role separation.
# - Any authenticated user in localrealm can read (wildcard).
# - Only producer or admin roles can write.
mars:
payload:
required: false
topic:
base: "mars"
key_order: ["class", "expver", "domain", "date", "time", "stream", "step"]
auth:
required: true
read_roles:
localrealm: ["*"]
write_roles:
localrealm: ["producer"]
identifier:
class:
type: StringHandler
max_length: 2
required: true
date:
type: DateHandler
canonical_format: "%Y%m%d"
required: false
domain:
type: EnumHandler
values: ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
required: false
expver:
type: ExpverHandler
default: "0001"
required: false
step:
type: IntHandler
range: [0, 100000]
required: false
stream:
type: StringHandler
required: false
time:
type: TimeHandler
required: false
# Authenticated stream with admin-only writes.
# - Any authenticated user can read (no read_roles restriction).
# - Only admins can write (no write_roles => defaults to admin_roles).
dissemination:
payload:
required: true
topic:
base: "diss"
key_order: ["destination", "target", "class", "expver", "domain", "date", "time", "stream", "step"]
auth:
required: true
# Uncomment the next line ONLY when running a build compiled with
# `--features ecpds` and after configuring the top-level `ecpds:`
# section above. Without that, startup will fail loudly. The plugin
# adds destination-level access control on watch/replay using the
# `destination` identifier field below as the match_key.
# plugins: ["ecpds"]
identifier:
class:
type: StringHandler
max_length: 2
required: true
date:
type: DateHandler
canonical_format: "%Y%m%d"
required: false
destination:
type: StringHandler
required: true
target:
type: StringHandler
required: false
domain:
type: EnumHandler
values: ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
required: false
expver:
type: ExpverHandler
default: "0001"
required: false
step:
type: IntHandler
range: [0, 100000]
required: false
stream:
type: StringHandler
required: false
time:
type: TimeHandler
required: false
# Smoke test auth verification (with scripts/example_auth_config.yaml):
# 1) test_polygon without Authorization => 200 (no auth block)
# 2) mars without Authorization => 401 (auth.required=true)
# 3) mars with reader-user => read 200, write 403
# 4) mars with producer-user => write 200
# 5) mars with admin-user => read 200, write 200
# 6) dissemination with reader-user => read 200, write 403 (admin-only writes)
# 7) dissemination with admin-user => read 200, write 200
#
# When the optional ECPDS plugin is enabled (top-level ecpds: section
# above is uncommented AND a stream has plugins: ["ecpds"]) the smoke
# script can additionally verify allow/deny against your real ECPDS.
# See docs/src/getting-started.md#optional-end-to-end-ecpds-plugin-smoke-test.