Runtime Configuration
Runtime configuration is an explicit deployment contract. Every new variable must have one authoritative application definition and one deliberate mapping for each deployed environment that needs it.
Configuration Locations
Section titled “Configuration Locations”| Purpose | Authoritative location | May contain secrets |
|---|---|---|
| Local developer values | local .env, copied from .env.example | local placeholders only |
| Variable name, validation, default, and operational impact | service or package configuration schema | no values |
| Portable Helm defaults and mapping shape | infra/helm/sens-platform/values.yaml | no |
| Test overrides | sens-platform-infra/environments/test/sens-platform.values.yaml | no |
| Production overrides | sens-platform-infra/environments/production/sens-platform.values.yaml | no |
| Test application secret values | OpenBao KV-v2 mount sens-test | yes |
| Production application secret values | OpenBao KV-v2 mount sens-production | yes |
| Runtime secret copies | ESO-managed Kubernetes Secrets in each namespace | yes |
| Immutable image tags | generated release.values.yaml in each infra environment | no |
The local .env file is ignored by Git and is never copied into a container or
Kubernetes deployment. GitHub Actions Secrets are for CI/CD credentials, not
for application runtime configuration. Argo CD stores references and desired
state, not application secret values. The release GitHub App installation token
dispatches the test GitOps workflow and grants release-status steps bounded read
access to private Production release metadata. Its variable
SENS_RELEASE_APP_CLIENT_ID and secret SENS_RELEASE_APP_PRIVATE_KEY are
CI/CD configuration, not application runtime values. The client ID comes from
the GitHub App’s general settings and is not the numeric App ID. Source-map
builds likewise use SENTRY_AUTH_TOKEN as a GitHub Actions Secret, while
SENTRY_ORG and the repository-specific project name remain GitHub Actions
Variables.
Helm Contract
Section titled “Helm Contract”Each deployable service supports explicit single-key references and folder-synchronized Secret references:
services: platformApi: env: SENS_EXAMPLE_MODE: standard secretEnv: SENS_EXAMPLE_TOKEN: secretName: sens-test-platform-api key: example-token secretEnvFrom: - secretName: sens-test-platform-api-runtime optional: false reloadOnSecretChange: trueenvcontains non-secret values that may be committed.secretEnvcontains only a Kubernetes Secret name and key, never the value.secretEnvFromimports every key from a named Kubernetes Secret. It is the standard mapping for an OpenBao catalog group synchronized by ESO.reloadOnSecretChangeadds an explicit Reloader annotation for all referenced Secrets so a value change rolls only the affected Deployment.- The same variable name must not appear in both maps. Helm rendering fails if it does.
- Missing non-optional Secret references prevent the Pod or Job from starting.
The database migration Job uses the same env and secretEnv contract under
databaseMigrations.
SENS_VISUAL_FLOW_BACKUP_CONFIRMED defaults to false. Set it to true only
for the controlled upgrade that removes the legacy decoder schema and only
after a recoverable backup has been verified. Fresh installations do not need
the confirmation because no legacy decoder table exists.
Platform API authentication contract
Section titled “Platform API authentication contract”platform-api requires these runtime values:
| Variable | Secret | Default | Bounds / operational impact |
|---|---|---|---|
SENS_PLATFORM_API_DATABASE_URL | yes | none | PostgreSQL is authoritative for every protected request and readiness. |
SENS_AUTH_JWT_SIGNING_KEY | yes | none | Base64url, at least 32 decoded bytes; rotation invalidates access JWTs. |
SENS_AUTH_IDENTIFIER_HMAC_KEY | yes | none | Separate base64url key of at least 32 bytes; rotation resets identifier correlation. |
SENS_PLATFORM_API_ALLOWED_ORIGINS | no | empty | Comma-separated exact HTTP(S) origins; empty disables browser refresh/logout. |
SENS_PLATFORM_VERSION | no | 0.0.0-local.0 | Exact image tag and /version value; its release channel must match SENS_ENVIRONMENT. |
SENS_AUTH_ACCESS_TOKEN_TTL_SECONDS | no | 1800 | 60–3600; access-JWT lifetime. |
SENS_AUTH_REFRESH_IDLE_TTL_SECONDS | no | 604800 | 3600–2592000; sliding inactivity deadline. |
SENS_AUTH_MAX_SESSIONS_PER_USER | no | 10 | 1–10; a new login revokes the least recently used excess session. |
SENS_AUTH_MAX_CONCURRENT_PASSWORD_CHECKS | no | 4 | 1–4; bounds Argon2 memory pressure per API replica. |
Generate the JWT and identifier-HMAC keys independently. Do not reuse the database password or copy either key between test and production. The test allowlist contains the exact API, web-console, and documentation origins. Production allows the exact web-console and documentation origins.
Web console contract
Section titled “Web console contract”The web-console has no runtime Secret or OpenBao group. Its validated runtime
values are:
| Variable | Default | Bounds / operational impact |
|---|---|---|
SENS_WEB_CONSOLE_HTTP_HOST | local 127.0.0.1 | Kubernetes sets 0.0.0.0; controls only the listener bind address. |
SENS_WEB_CONSOLE_HTTP_PORT | 8080 | Integer 1–65535; used by Service and probes. |
SENS_WEB_CONSOLE_API_BASE_URL | local API origin | Pure credential-free HTTP(S) origin; required and HTTPS outside local. |
SENS_ENVIRONMENT | local | local, test, or production; must match the image release channel. |
SENS_PLATFORM_VERSION | 0.0.0-local.0 | Immutable image tag exposed by /version and browser runtime configuration. |
The test API allowlist is exactly
https://api.dev.iot-sens.schlossers.at,https://test-frontend.dev.iot-sens.schlossers.at,https://docs.dev.iot-sens.schlossers.at.
The Production allowlist is exactly
https://app.iot-sens.schlossers.at,https://docs.iot-sens.schlossers.at.
Production sets SENS_WEB_CONSOLE_API_BASE_URL to
https://api.iot-sens.schlossers.at. These origins grant browser transport
only; every request still requires normal authentication and authorization.
Optional error and product signals
Section titled “Optional error and product signals”| Owner | Variable | Secret | Default / effect |
|---|---|---|---|
| all Node deployables | SENS_SENTRY_ENABLED | no | false; keeps Sentry completely disabled |
| all Node deployables | SENS_SENTRY_DSN | configuration | required only when enabled |
| all Node deployables | SENS_SENTRY_TRACES_SAMPLE_RATE | no | Production 0.05, Test 0.2, local disabled |
| Platform API | SENS_PRODUCT_ANALYTICS_ENABLED | no | false; controls the additive auth identity |
| Platform API | SENS_PRODUCT_ANALYTICS_ID_HMAC_KEY | yes | environment-specific base64url key, required when analytics is enabled |
| web console | SENS_WEB_CONSOLE_SENTRY_ENABLED, SENS_WEB_CONSOLE_SENTRY_DSN, SENS_WEB_CONSOLE_SENTRY_TRACES_SAMPLE_RATE | DSN is external configuration | optional browser error reporting |
| web console | SENS_WEB_CONSOLE_POSTHOG_ENABLED, SENS_WEB_CONSOLE_POSTHOG_HOST, SENS_WEB_CONSOLE_POSTHOG_PROJECT_KEY | project key is external configuration | Test and Production; SDK still requires browser consent |
Keep the analytics HMAC key for at least the 12-month event retention. Rotate it
only for a security incident because rotation deliberately begins a new
pseudonymous identity generation. Source-map CI uses SENTRY_AUTH_TOKEN,
SENTRY_ORG, and SENTRY_PROJECT_WEB_CONSOLE; the token is a GitHub Secret,
the other two are GitHub Variables, and none enters runtime images.
The authoritative runtime locations are the environment-specific Helm/GitOps
values plus the consuming service’s OpenBao catalog group. In particular,
SENS_PRODUCT_ANALYTICS_ID_HMAC_KEY belongs to runtime/platform-api in the
environment-specific mount and is required whenever analytics is enabled.
SENS_SENTRY_DSN belongs to each enabled Node deployable’s runtime
configuration. The browser variables SENS_WEB_CONSOLE_SENTRY_DSN,
SENS_WEB_CONSOLE_POSTHOG_HOST, and SENS_WEB_CONSOLE_POSTHOG_PROJECT_KEY
belong to the web-console runtime configuration. SENS_SENTRY_ENABLED,
SENS_SENTRY_TRACES_SAMPLE_RATE, SENS_PRODUCT_ANALYTICS_ENABLED,
SENS_WEB_CONSOLE_SENTRY_ENABLED,
SENS_WEB_CONSOLE_SENTRY_TRACES_SAMPLE_RATE, and
SENS_WEB_CONSOLE_POSTHOG_ENABLED remain explicit, non-secret Helm values. All
SaaS settings are optional in Test and Production and stay disabled for local
and customer-owned on-premise environments unless deliberately enabled.
Temporary IOTA snapshot import
Section titled “Temporary IOTA snapshot import”The manual IOTA snapshot import intentionally adds no environment variable, Helm value, Kubernetes Secret, OpenBao entry, or persisted platform configuration. A platform administrator enters the constrained IOTA base URL, username, and password for one request in the web console. The Platform API uses them only in memory and the web console clears the password after every attempt.
Do not add IOTA_USERNAME, IOTA_PASSWORD, or an IOTA base URL to .env or to
deployment configuration for this feature. Kubernetes needs only outbound DNS
and HTTPS reachability from platform-api to the validated
commonapi-<slug>.xaas-a0a0.cloud host. IOTA availability is not part of
platform health or readiness. See
IOTA snapshot import v1 for the
temporary contract and mandatory removal checklist.
Netmore raw-ingestion contract
Section titled “Netmore raw-ingestion contract”mqtt-ingestion-worker validates the following receiver values at startup:
| Variable | Secret | Contract |
|---|---|---|
SENS_MQTT_INGESTION_HTTP_HOST | no | local 127.0.0.1, Kubernetes 0.0.0.0 |
SENS_MQTT_INGESTION_HTTP_PORT | no | default 3001 |
SENS_NETMORE_MQTT_URL | no | mqtts:// only; default Netmore port 8883 |
SENS_NETMORE_MQTT_TOPIC | no | exact sensor/sensor_network_services/+/payload |
SENS_NETMORE_MQTT_USERNAME | yes | required shared portal user |
SENS_NETMORE_MQTT_PASSWORD | yes | required and rotated before live use |
SENS_NETMORE_MQTT_CLIENT_ID | yes | required stable, distinct per environment |
SENS_NETMORE_EXCLUDED_CUSTOMERS | no | required exact topic-segment list |
SENS_NETMORE_MAX_PAYLOAD_BYTES | no | default and maximum 262144 |
SENS_NATS_URL | no | required cluster-internal URL |
SENS_NATS_USERNAME | yes | publisher identity |
SENS_NATS_PASSWORD | yes | publisher identity |
SENS_NATS_PUBLISH_TIMEOUT_MS | no | default 5000 |
raw-storage-worker uses SENS_RAW_STORAGE_HTTP_HOST,
SENS_RAW_STORAGE_HTTP_PORT, existing database configuration, consumer NATS
credentials, and SENS_RAW_CONSUMER_CONCURRENCY with default 4 and bounds
1–16.
The raw-ingestion-retention Job requires SENS_DATABASE_URL, uses
SENS_RAW_RETENTION_DAYS=30 for the registered-device cutoff, and uses
SENS_RAW_UNREGISTERED_DEVICE_RETENTION_HOURS=168 for rows whose DevEUI is not
in the platform registry when cleanup runs. The unregistered cutoff must be
shorter than the general raw-retention period. It also uses
SENS_OUTBOX_PUBLISHED_RETENTION_DAYS=7 to delete delivered transactional
outbox events by published_at. The value is bounded to 1–365 days. Pending
events with published_at IS NULL are never eligible for retention.
rawIngestionBootstrap.migrateRawStoredRetention is a non-secret Helm-only
maintenance switch and defaults to false. When enabled, the bootstrap command
receives --migrate-raw-stored-retention. Helm requires the bootstrap Job and
retention CronJob to remain enabled and requires MQTT ingestion, raw storage,
and the Flow worker to be disabled. No new environment variable or Secret is
introduced.
NATS server authentication, bootstrap, publisher, and consumer credentials are
referenced from the environment-specific sens-*-nats-auth Secret. The
Netmore folder supplies only username, password, and the environment’s stable
client ID. Test and production share the newly rotated username/password by
explicit agreement, but never the client ID. Coordinated password rotation
restarts both receiver Deployments and can interrupt both feeds at once.
Telemetry Flow services
Section titled “Telemetry Flow services”All three Flow workloads remain disabled by default. The Platform API may only enable sandbox requests when the sandbox worker is enabled and reachable.
| Owner | Variable | Secret | Default and effect |
|---|---|---|---|
| Platform API | SENS_FLOW_SANDBOX_REQUESTS_ENABLED | no | false; permits custom-script release loads and preview execution |
| Platform API | SENS_FLOW_TEST_RUN_TIMEOUT_MS | no | 5000; total synchronous preview deadline |
| Platform API | SENS_SANDBOX_REQUEST_TIMEOUT_MS | no | 500; NATS request/reply deadline |
| Platform API | SENS_NATS_URL, SENS_NATS_USERNAME, SENS_NATS_PASSWORD | credentials yes | sandbox requester identity; required only when requests are enabled |
| Flow worker | SENS_FLOW_WORKER_HTTP_HOST, SENS_FLOW_WORKER_HTTP_PORT | no | operations listener, default port 3003 |
| Flow worker | SENS_RAW_STORED_CONSUMER_CONCURRENCY | no | bounded Raw Stored concurrency, default 4 |
| Flow worker | SENS_FLOW_SANDBOX_REQUESTS_ENABLED | no | false; custom-script nodes fail closed when disabled |
| Flow worker | SENS_SANDBOX_REQUEST_TIMEOUT_MS | no | 500; bounded sandbox request |
| Sandbox worker | SENS_SANDBOX_HTTP_HOST, SENS_SANDBOX_HTTP_PORT | no | operations listener, default port 3004 |
| Sandbox worker | SENS_SANDBOX_EXECUTION_TIMEOUT_MS | no | 100; QuickJS interrupt deadline |
| Sandbox worker | SENS_SANDBOX_MEMORY_LIMIT_BYTES | no | 16777216 (16 MiB) per request |
| Sandbox worker | SENS_SANDBOX_MAX_INPUT_BYTES, SENS_SANDBOX_MAX_OUTPUT_BYTES | no | 262144 each |
| Action worker | SENS_FLOW_ACTION_HTTP_HOST, SENS_FLOW_ACTION_HTTP_PORT | no | operations listener, default port 3005 |
| Action worker | SENS_FLOW_ACTION_CONCURRENCY | no | bounded consumer concurrency, default 4 |
| Action worker | SENS_FLOW_ACTIONS_ENABLED | no | false; when false no Action consumer starts |
| Action worker | SENS_FLOW_ACTION_MAX_ATTEMPTS | no | 10; durable job attempts before Dead Letter |
| Action worker | SENS_FLOW_EMAIL_RATE_LIMIT_PER_TENANT_PER_MINUTE | no | 60; PostgreSQL-shared minute bucket |
| Action worker | SENS_SMTP_HOST, SENS_SMTP_PORT, SENS_SMTP_SECURE, SENS_SMTP_FROM | no | deployment-wide SMTP endpoint; host/from are required when Actions are enabled |
| Action worker | SENS_SMTP_AUTH_REQUIRED, SENS_SMTP_REQUIRE_TLS | no | explicit authentication and STARTTLS gates; both are true in test/production |
| Action worker | SENS_SMTP_USERNAME, SENS_SMTP_PASSWORD | yes | complete credential pair required for authenticated Actions |
Every worker also requires its own SENS_NATS_USERNAME and
SENS_NATS_PASSWORD. Identities are not interchangeable: Flow Worker, Action
Worker, Platform API and Sandbox have distinct publish/subscribe permissions.
Database URLs are required only by Platform API, Flow Worker and Action Worker;
the sandbox must never receive one.
The chart-level monitoring.prometheusRule.enabled switch optionally renders
the initial Flow Replay backlog, Dead Letter recorder backlog, and sandbox
failure rules. Its thresholds and windows are configured below
monitoring.prometheusRule. The switch defaults to false; enabling it requires
the target cluster to provide the Prometheus Operator PrometheusRule CRD.
embedded.nats.metricsExporter.enabled controls the metrics-only NATS sidecar
and defaults to true. embedded.nats.metricsExporter.image.repository,
.tag, .pullPolicy, and .resources are the portable image and resource
contract. The exporter reads NATS’ Pod-local monitoring endpoint and exposes
only Prometheus metrics through the existing platform Service selector; it has
no credentials and adds no Secret or environment variable.
Adding a Non-Secret Variable
Section titled “Adding a Non-Secret Variable”- Add the variable to the owning service or package configuration schema with validation, default behavior, and operational-impact metadata.
- Add a safe local example to
.env.examplewhen developers need the value. - Add the portable default or mapping to the correct
envmap ininfra/helm/sens-platform/values.yaml. - Add test and production overrides only where their values differ, using the
static
sens-platform.values.yamlfiles in the infra repository. - Update the owning module or operations documentation.
- Add configuration tests and render both Helm environments.
Do not add runtime variables to release.values.yaml. That file is generated
and contains image tags only.
Adding a Secret Variable
Section titled “Adding a Secret Variable”- Mark the variable as secret in the owning configuration metadata and avoid values in examples, logs, errors, fixtures, Git, and image layers.
- Open the Ops portal and select the correct environment and catalog group.
- Add the exact environment-variable key and its value in the UI.
- Update the application schema, tests, and documentation when the key is new.
- Verify the
ExternalSecretReady condition and the affected rollout without printing or decoding the synchronized Kubernetes Secret.
Workloads import their generated Kubernetes Secret through secretEnvFrom.
Most ExternalSecret resources synchronize a complete OpenBao group, so a
new key does not need an additional per-key YAML mapping. Raw Storage, Flow
Worker, and Flow Action Worker use explicit absolute mappings for the repeated
SENS_DATABASE_URL key to avoid recursive provider deduplication; new keys in
those folders require a GitOps mapping update. A new deployable service needs a
dedicated folder and GitOps mapping. See
Secret Management for the complete UI, bootstrap,
isolation, failure, and recovery model.
Release and Promotion Behavior
Section titled “Release and Promotion Behavior”The infra repository separates configuration from release selection:
sens-platform.values.yamlis static, reviewed environment configuration.release.values.yamlis generated and contains only immutable image tags.release.yamlrecords the product commit and image tags for auditability.
The automatic test workflow updates only the test Application revision,
release.values.yaml, and release.yaml. It cannot erase newly added runtime
configuration. Production promotion updates the equivalent production release
files through a reviewed pull request and leaves production configuration
unchanged.
The Platform version channel is part of runtime validation: local uses
MAJOR.MINOR.PATCH-local.0, test uses
MAJOR.MINOR.PATCH-alpha.RUN.ATTEMPT, future staging uses
MAJOR.MINOR.PATCH-rc.NUMBER, and Production uses stable
MAJOR.MINOR.PATCH. Helm derives SENS_PLATFORM_VERSION from the same
platform-api image tag, and runtime checks require exact equality. See
Platform Release Version for
the three release-state sources, bump workflow, and failure behavior.
Production release authorization belongs to SENS Platform Ops RBAC plus fresh
TOTP. The removed SENS_PRODUCTION_RELEASE_PASSWORD_SCRYPT variable must not be
present in local files, Helm, OpenBao, Kubernetes, GitHub Actions, images, or
runtime environments.
Database Migration Ordering
Section titled “Database Migration Ordering”Every release publishes a dedicated database-migrations image. Argo CD sync
waves apply TimescaleDB and NATS first, the database migration second, the raw
stream bootstrap next, then the raw consumer, and finally the MQTT receiver.
The migration runner waits for database readiness and applies repeatable,
versioned SQL migrations. A failed migration blocks the later rollout wave.
Test and production reuse the existing TimescaleDB authentication Secret. The
migration Job receives the password through secretEnv; host, port, database,
and user are non-secret environment values. TimescaleDB policy migrations stay
disabled while the embedded OSS image does not support them.
Required Validation
Section titled “Required Validation”Run before merging a runtime-configuration change:
pnpm lintpnpm typecheckpnpm testpnpm buildpnpm helm:lintpnpm helm:template:testpnpm helm:template:productionpnpm helm:check-runtime-configReview the rendered resources for the intended namespace, Secret names, non-secret values, migration image tag, and Argo CD sync waves.
Telemetry flow workers
Section titled “Telemetry flow workers”The flow worker uses SENS_FLOW_WORKER_HTTP_HOST,
SENS_FLOW_WORKER_HTTP_PORT, SENS_RAW_STORED_CONSUMER_CONCURRENCY, and
SENS_SANDBOX_REQUEST_TIMEOUT_MS. It also requires the documented database and
NATS credentials.
The isolated sandbox uses SENS_SANDBOX_HTTP_HOST,
SENS_SANDBOX_HTTP_PORT, SENS_SANDBOX_EXECUTION_TIMEOUT_MS,
SENS_SANDBOX_MEMORY_LIMIT_BYTES, SENS_SANDBOX_MAX_INPUT_BYTES, and
SENS_SANDBOX_MAX_OUTPUT_BYTES. Its Pod receives no database or SMTP secrets.
The action worker uses SENS_FLOW_ACTION_HTTP_HOST,
SENS_FLOW_ACTION_HTTP_PORT, SENS_FLOW_ACTION_CONCURRENCY,
SENS_FLOW_ACTIONS_ENABLED, and
SENS_FLOW_EMAIL_RATE_LIMIT_PER_TENANT_PER_MINUTE. SMTP is configured with
SENS_SMTP_HOST, SENS_SMTP_PORT, SENS_SMTP_SECURE, and SENS_SMTP_FROM;
test and production additionally set SENS_SMTP_AUTH_REQUIRED=true and
SENS_SMTP_REQUIRE_TLS=true. SENS_SMTP_USERNAME and SENS_SMTP_PASSWORD are
Kubernetes Secret references with no defaults. When Actions are enabled but
host, sender, username, or password is missing, startup fails. Delivery also
fails when STARTTLS cannot be negotiated. Test and production start with Actions
disabled.
The reviewed non-secret sender for test and production is
SENS_SMTP_FROM=sens.ops@sens.at. Changing it requires synchronized product
Helm values, generated infra release values, runtime-contract checks, and SMTP
sender authorization. It does not replace the separate SMTP credentials.