Skip to content

Feature gate registry

This page is the single index of the environment-variable gates that change the behaviour of the control plane. It exists so an operator can answer two questions without grepping the codebase:

  1. What is the default? Every gate below ships default-OFF / legacy behaviour preserved unless explicitly noted, per charter §12a Rule 4.
  2. Is it safe to flip? Each row states whether the gate is a production hardening toggle (safe to enable after a soak window), an escape hatch (only for a specific known-safe situation), or a local-debug-only switch that must never reach a deployed Container App.

Adding a new gate? Name it STRICT_<area> or ENFORCE_<area> (hardening) and register it here in the same change, with the planned flip date. That is the §12a Rule 4 contract.

Production hardening gates

New hardening gates follow the §12a Rule 4 lifecycle: ship default-OFF behind the env var, soak for one release cycle with the gate forced ON in dogfood + a green Persona Matrix run, then flip the shared deployment default in a separate change. Rows marked on below have completed that activation; their process-level fallback can still be OFF when the environment variable is absent.

Gate Default Effect when =true Read by
STRICT_JWT off Lowers the claims cache TTL from 300 s to 60 s and pins the token azp/audience on every validation. api/auth.py
STRICT_CORS off Locks the CORS allow-list to same-origin; STRICT_CORS_ALLOW_METHODS / STRICT_CORS_ALLOW_HEADERS (comma-separated) override the defaults for custom flows. api/main.py
STRICT_EXEC_RATE_LIMIT off Enables a per-window rate limit on the loopback exec server in the terminal sidecar. Setting it back to false re-opens the gate immediately. terminal/exec_server.py
STRICT_CSP off Emits a strict Content-Security-Policy response header on API + proxied SPA responses (kept in sync with web/nginx.conf). STRICT_CSP_POLICY overrides the default policy string. api/app/security_headers.py
STRICT_READINESS_DETAIL off Collapses the /api/health/ready body to the overall status only (drops the per-component components map that leaks internal topology to an anonymous recon probe). Default OFF preserves the full-detail body the cli-upgrade Tier-1 gate reads. api/routes/health.py
ENABLE_DOCS off Exposes FastAPI's internal /api/docs Swagger UI and /openapi.json. Production leaves both hidden; this does not affect the separate, authenticated product API Reference page. api/main.py
STRICT_SSE_TICKET_BINDING off Binds the one-shot SSE ticket to the caller object id, client IP, and User-Agent hash, and rejects consumption when any differs (audit P0 #2 #3). api/routes/monitor/sidecars.py, api/routes/monitor/logs.py
STRICT_AUDIT_HASH off Redacts PII out of jobhistory.payload_json by hashing matched fields before the append-blob audit write (audit P2 #13 #14). api/services/state/repository.py
STRICT_CLIENT_LOG_REDACTION off Hashes the authenticated caller identifier and removes query/fragment data from browser error-report URLs before logging. The 2026-08-15 review date passed without a shared-default flip; it remains off pending documented dogfood and Persona Matrix evidence. api/routes/client_log.py
ENFORCE_OPENAPI_EXEC_RBAC off (false in Bicep) Requires the caller to hold an Azure RBAC write role on the target resource group before a state-changing OpenAPI proxy call is forwarded under the admin token. See OpenAPI execution RBAC gate. api/services/openapi/exec_gate.py
ENFORCE_DASHBOARD_RBAC on (true in the shared deployment defaults; unset fallback is off) Requires the caller to hold a readable Azure role on the configured platform subscription or resource group before /api/me admits the SPA. Permission-enumeration failures degrade open so an ARM outage cannot lock out every operator; downstream Azure calls still enforce their own RBAC. api/services/dashboard_access.py
ALLOW_OPENAPI_TOKEN_AUTH on (true in the shared deployment defaults; unset fallback is off) Universal M2M shared-token auth (activated 2026-07). Lets every require_caller-gated route also accept the shared elb-openapi X-ELB-API-Token (constant-time compared against the api-sidecar env / Redis cache token) in addition to the MSAL bearer, so a peer-VNet automation caller manages one credential across the whole API surface — read and write. The shared token has no caller-specific Azure RBAC gate; keep ingress access controlled and rotate the token through Settings. Setting the gate to false restores MSAL-bearer-only authentication. api/auth.py
STRICT_RBAC_REMOVAL_HALT off (warn-only) Makes the azd preprovision RBAC-removal preflight halt azd provision when a Microsoft.Authorization/roleAssignments resource would be deleted, unless ACCEPT_RBAC_REMOVAL is set for the run. See charter §12a Rule 7. scripts/dev/check_rbac_removal.py
ENFORCE_AUTO_ORACLE_RBAC off (tenant-auth legacy path) Requires current AKS read capability for preference listing and current AKS plus Storage write capability for preference mutation/background dispatch. Permission lookup degradation fails closed. The 2026-09-10 review passed without a shared-default flip; it remains off pending documented dogfood and Persona Matrix evidence. api/services/auto_oracle_reconcile.py

Feature flags (behaviour switches, not hardening)

These select between two supported behaviours rather than tightening a safety check. They do not follow the §12a Rule 4 hardening lifecycle.

Gate Default Effect when =true Read by
BLAST_GATE_ENABLED off (legacy direct-submit path) Routes BLAST submit through the AKS capacity gate instead of submitting directly. The /api/blast/capacity preview endpoint reports the would-have-been decision even when the gate is off. api/routes/blast/capacity.py, api/tasks/blast/submit_task.py
BLAST_COORD_BACKEND redis (process and checked-in deployment fallback) Set to k8s to coordinate dashboard and direct OpenAPI submissions through one Kubernetes Lease plus an active-finalizer ceiling. The K8s backend takes precedence over BLAST_GATE_ENABLED and never touches the Redis capacity reservation store. A deployed environment may override the checked-in fallback; keep both submitters aligned. Cross-Path BLAST Submit Coordination
BLAST_JOBS_SHARED_VISIBILITY on (shared deployment; unset fallback is off) Lets every authenticated caller list and open all jobs, including jobs owned by other callers. Set it to false before multi-tenant or shared-subscription use to restore per-owner visibility checks. api/services/blast/job_state.py
BLAST_DB_METADATA_INVALIDATE_DISABLED off (cross-sidecar invalidation is on) Stops best-effort Redis publication/subscription for BLAST database metadata cache invalidation. Local invalidation still runs and TTL bounds stale reads; intended for tests or pub/sub incident isolation. api/services/blast/db_metadata.py
STRICT_BLUEGREEN off (single-revision upgrade) Enables staged green revision health checks, traffic cutover, confirm-window rollback, and revision garbage collection. It must be activated together with Container Apps activeRevisionsMode: Multiple; the checked-in Bicep remains Single. api/services/upgrade/revisions.py
OPENAPI_ALLOW_PUBLIC_LB on (shared deployment; unset fallback is off) Allows the dashboard proxy to forward the OpenAPI admin token to a public load-balancer IPv4 address. Set it to false to require a private, loopback, or link-local target. Public OpenAPI remains a separate operator opt-in and must retain its token, TLS, NSG, and rate-limit controls. api/routes/aks/openapi_proxy.py
SERVICEBUS_ENABLED empty (defer to Settings; saved config defaults disabled) Acts as a three-state deployment override: empty defers to the durable Settings row, truthy permits activation when that row is configured and enabled, and falsy is a kill switch. Service Bus integration
ENABLE_SB_SUBMIT_INGRESS off (direct OpenAPI submit) Sends eligible unscoped dashboard submissions through the configured Service Bus request queue. Explicitly scoped requests stay direct, and publish failure falls back to the direct path. api/services/blast/submit_ingress.py
SERVICEBUS_RESIDENT_CONSUMER on (shared deployment; unset fallback is off) Runs the low-latency resident request consumer in the dedicated Service Bus worker. The periodic Celery drain remains the recovery fallback. api/services/blast/resident_consumer.py
SERVICEBUS_QUEUE_AUTOSTART on (shared deployment; unset fallback is off) Starts an exactly Stopped AKS cluster when the request queue has pending work. A fail-closed Redis lease prevents duplicate cost-bearing starts; the default cooldown is 600 seconds. api/services/aks/queue_autostart.py
SERVICEBUS_ATOMIC_CLAIM on Atomically reserves a correlation id before OpenAPI submit. If disabled while drain concurrency exceeds one, the worker forces serial drain instead of risking duplicate execution. api/tasks/servicebus/tasks.py
SERVICEBUS_DRAIN_SINGLEFLIGHT on (false override retained for compatibility but ignored) Keeps one queue-scoped Redis drain lease across resident and periodic consumers. Current code always takes the lease because Settings routing changes use it as a mutation fence. api/tasks/servicebus/tasks.py
STORAGE_DATE_LAYOUT_ENABLED on Writes new native and external results under results/YYYY/MM/DD/<job_id>/. Existing flat-layout jobs remain readable through their persisted prefix. api/services/storage/job_prefix.py
JOBSTATE_TIME_INDEX_ENABLED on Uses the jobstateindex Table for bounded newest-first job listing and runs the repair reconciler. Existing deployments must complete the index backfill before enabling it. api/services/state/time_index.py
DB_CONSISTENCY_RECONCILE_ENABLED on Runs the guarded database consistency reconciler, which can prune ghost volume blobs and rebuild shard aliases. It skips when authority is missing, drift exceeds safety bounds, or a prepare lock is held. api/tasks/storage/reconcile_db_consistency.py
ENABLE_MGMT_CLIENT_POOL on (unset fallback is on) Reuses Azure management SDK clients by credential and subscription so dashboard polling keeps warm HTTP pools. Set false only to isolate client-pool diagnostics. api/services/azure_clients.py
JOBS_EVENTS_SSE_DISABLED off (job-event SSE is on) Kill switch for the one-shot-ticket job-change SSE channel. When true, clients retain their polling fallback. api/routes/monitor/jobs_events.py
K8S_CLUSTER_BREAKER_DISABLED off (breaker is on) Emergency bypass for the per-cluster Kubernetes connection-failure circuit breaker. Leaving it false prevents repeated probes from flooding a stopped or unreachable cluster. api/services/k8s/cluster_breaker.py
NCBI_DURABLE_CACHE_DISABLED off (durable cache is on) Bypasses the best-effort ops-Redis cache for NCBI record responses and falls back to process cache plus live lookup. api/services/ncbi/nuccore.py
NCBI_REDIS_BUCKET_DISABLED off (Redis limiter is on) Bypasses the cross-process NCBI request token bucket and uses the in-process fallback. Use only for diagnostics; disabling it weakens aggregate rate coordination. api/services/ncbi/_eutils.py
OPENAPI_PLS_ENABLED off Adds Private Link Service annotations to the OpenAPI internal LoadBalancer. Enabling requires an explicit OPENAPI_PLS_LB_SUBNET; see OpenAPI Direct Access. api/tasks/openapi/constants.py
PREPARE_DB_ORPHAN_RECONCILE_ENABLED on Kill switch for the idempotent prepare-DB orphan reconciler. Set false to pause metadata repair while investigating an active incident. api/services/storage/orphan_prepare_db.py
REQUEST_DETAIL_CAPTURE_ENABLED metadata-only when unset Tri-state request inspector: unset records bounded metadata, true additionally buffers eligible request/response bodies, and false disables detail samples while aggregate request metrics continue. api/app/middleware.py
SIDECAR_REPORTER_DISABLED off (reporters are on) Stops api/worker/beat/terminal cgroup metric reporters. Intended for tests and incident isolation; Sidecar Runtime loses local samples while disabled. api/main.py
STALE_DBOPS_RECONCILE_ENABLED on Kill switch for the stale warmup/prepare/oracle JobState terminalizer. Set false only while diagnosing rows the reconciler would repair. api/services/db/stale_dbops.py
STORAGE_DFS_ENABLED off (Blob fallback) Uses ADLS Gen2 directory APIs for HNS listing and recursive deletion, with Blob fallback where documented. Retention deletion also requires this gate. api/services/storage/dfs_client_pool.py
ELB_OPENAPI_ETA_ENABLED off Enables the sibling OpenAPI build overlay's learned queue-aware ETA projection. The overlay degrades to no ETA on storage or model failure. scripts/dev/openapi-overlays/eta.py
SERVICEBUS_EXTERNAL_CONSUMER off (no demo observer) Starts a worker-side demo consumer that drains the Service Bus completion topic into the in-memory event-bus (UI Live SB Events). The dashboard is the producer on elastic-blast-completions; the demo consumer exists only to exercise the contract. It defaults to the dedicated playground-observer subscription and does not consume shared default. Footgun (#78): an explicit SERVICEBUS_COMPLETION_SUBSCRIPTION override that includes default can still compete-consume with a real external integrator; startup emits a strong WARNING when configured that way. Queue completion mode keeps the observer disabled so it cannot steal external ACKs. api/services/service_bus_external_consumer.py
BLAST_AUTO_RETRY_ENABLED off (no auto-resubmit) Lets the blast-auto-retry-failed-jobs beat sweep auto-resubmit transient submit-phase failures (terminal sidecar / Azure auth / node-warmup) with bounded backoff, quarantining a job once its budget is exhausted. K8s runtime failures (blast_search_failed), cluster-state failures, and external-origin jobs are never auto-retried. Tunables: BLAST_AUTO_RETRY_MAX (default 2, 1–10), BLAST_AUTO_RETRY_SWEEP_LIMIT (resubmits per pass, default 5, 1–50), BLAST_AUTO_RETRY_SCAN_LIMIT (rows read per pass, default 200, 10–1000), CELERY_BEAT_BLAST_AUTO_RETRY_SECONDS (sweep interval, default 180). Planned flip date: TBD — keep OFF until the auto-retry UI affordance ships and one dogfood release cycle has soaked. api/tasks/blast/auto_retry_task.py, api/services/blast/auto_retry.py
COST_PRICING_LIVE off (static price map) Lets the Cost estimate card price VM SKUs from the public, no-auth Azure Retail Prices API (Linux on-demand, per region) instead of the hardcoded map. Positive prices use a 24h best-effort shared Redis cache plus the bounded process cache; misses use a 15-minute TTL so newly available SKUs recover without an operator cache flush. Redis or Retail API failure falls back to the bundled approximate map. Off keeps the bundled map and makes no external call. The card surfaces which source was used. api/services/cost/pricing.py, api/services/cost/estimate.py
WEBHOOK_NOTIFICATIONS_ENABLED off (no outbound POST) Lets the dispatch-job-webhooks beat sweep POST a provider-aware rich message (Slack Block Kit / Teams MessageCard / Discord embed; generic {text, content} for Logic Apps + custom integrators) to the configured webhook when a BLAST job reaches a terminal state. Requires a webhook configured + enabled in Settings → Webhooks. The URL is SSRF-validated against an allowlist (hooks.slack.com / *.webhook.office.com / Discord / Logic Apps, extendable via WEBHOOK_ALLOWED_HOSTS) on save AND at send time; https-only, IP literals rejected. Messages include a deep-link to the job ({DASHBOARD_PUBLIC_URL}/blast/jobs/{job_id} resolved via api.services.control_plane_url). Tunables: WEBHOOK_SWEEP_LIMIT (sends per pass, default 20, 1–100), CELERY_BEAT_WEBHOOK_SECONDS (sweep interval, default 60). api/tasks/webhooks.py, api/services/webhooks_pref.py
K8S_RUNTIME_GC_ENABLED on Runs a bounded five-minute sweep that deletes old terminal ElasticBLAST Jobs and terminal OpenAPI job ConfigMaps. Active, recent, and timestamp-invalid objects are preserved. Defaults: K8S_RUNTIME_GC_JOB_RETENTION_SECONDS=3600, K8S_RUNTIME_GC_CONFIGMAP_RETENTION_SECONDS=1209600 (14 days), K8S_RUNTIME_GC_MAX_DELETES=200, and K8S_RUNTIME_GC_DEADLINE_SECONDS=180 split across at most two clusters. Set the gate to false as an emergency retention hold. Durable dashboard history and results remain in Azure Table/Blob Storage after K8s GC. api/tasks/blast/runtime_gc_task.py, api/services/k8s/runtime_gc.py
AUTO_ORACLE_RECONCILE_ENABLED off (preferences only) Enables immediate and 120-second recovery reconciliation for DB order-oracle preferences after exact-scope Auto warm readiness. Work is capped at two builds globally and one per Storage account per pass. The 2026-09-10 review passed without a shared-default flip; it remains off pending an explicit activation decision. See Auto Oracle. api/services/auto_oracle_reconcile.py
AUTO_ORACLE_RETENTION_ENABLED off (no deletion) Enables the daily 14-day oracle-history sweep. Current, previous-ready, active, referenced, recent, malformed, and nonterminal runs are preserved; deletion is capped and tombstone-protected. Flip only in a separate review after reconcile soak. api/services/db/oracle_retention.py
PREPARE_DB_NCBI_DIRECT_ENABLED off (false in the shared deployment defaults) Enables an explicit operator-confirmed NCBI Direct (HTTPS) update when the official NCBI release is newer than the S3 cloud mirror. The AKS Indexed Job pins every archive URL, size, and MD5; stages into a generation prefix; and promotes only after marker, file-size, and shard-layout validation. It never silently falls back to an older S3 snapshot. Live rollout evidence was recorded on 2026-08-29, but the 2026-09-15 review passed without a shared-default flip; individual environments may still opt in explicitly. Tunables: PREPARE_DB_NCBI_DIRECT_PARALLELISM (default 4, capped at 8), PREPARE_DB_NCBI_DIRECT_TIMEOUT_SECONDS (default 8 hours), and PREPARE_DB_DIRECT_RECOVERY_GRACE_SECONDS (default 120 seconds, clamped to 30–3600) before the orphan reconciler promotes a completed Job after worker loss. api/services/storage/prepare_db_direct_dispatch.py, api/tasks/storage/prepare_db_direct.py

Escape hatches (use only for the specific named situation)

These intentionally relax a safety check. They are not hardening toggles — they exist so a known-safe operation is not blocked. Do not bake them into automation.

Gate Default Effect when set Read by
ALLOW_ANONYMOUS_CLIENT_LOG unset/off Allows pre-login browser error reports to reach /api/client-log without a bearer token. Payload sanitization and size limits still apply; use only when pre-auth diagnostics justify the relaxed route. api/routes/client_log.py
OPENAPI_RATE_LIMIT_DISABLED unset/off (limiter is on) Disables the token/IP sliding-window limiter for OpenAPI execution routes. Incident diagnostics only; never leave disabled on reachable ingress. api/app/openapi_rate_limit.py
ACCEPT_RBAC_REMOVAL unset Overrides STRICT_RBAC_REMOVAL_HALT for a single run. The value must encode the phase-2 PR, e.g. phase-2-of-pr-<N>, and is cross-checked against the matching phase-1 PR at review. scripts/dev/check_rbac_removal.py
ELB_ALLOW_SUB_MISMATCH unset Lets quick-deploy.sh proceed when the active az login subscription differs from the azd env subscription. Needed when the azd env points at one tenant but you are logged into another. scripts/dev/quick-deploy.sh
ELB_ALLOW_AUTH_BYPASS_IN_CLOUD unset Disarms the frontend deploy die-guard that aborts when VITE_AUTH_DEV_BYPASS=true would be baked into a cloud build. Only for a deliberate non-production sandbox. scripts/dev/quick-deploy.sh
ELB_SKIP_HOOKS unset Skips the version-controlled pre-commit / pre-push CI-mirror git hooks for one command. Emergency use only — never push a red build knowingly. scripts/dev/install-git-hooks.sh

Local-debug-only switches (never in a deployed Container App)

These change behaviour for a developer iterating from a laptop. Every one keeps a CONTAINER_APP_NAME guard so a deployed Container App can never honour them.

Gate Default Effect when =true Read by
AUTH_DEV_BYPASS false Returns a synthetic anonymous caller (OID 00000…0) instead of validating an MSAL bearer token. The cloud is_dev_bypass_caller() guard rejects this identity even if it slips through to a deployed revision. Toggle the full local "real az login" session with scripts/dev/local-run.sh auth-on / auth-off. api/auth.py
LOCAL_DEBUG_AUTO_OPEN_STORAGE false Lets the local backend call ensure_local_storage_access() to enable publicNetworkAccess with defaultAction=Deny, bypass=None, and exactly the caller's public IPv4 when a route has full Storage ARM scope. Normal local-run.sh and VS Code debug launches leave this unset/OFF; enable it only as an explicit local-debug opt-in and close Storage immediately afterward. The CONTAINER_APP_NAME guard prevents deployed apps from honoring it. See charter §9. api/services/storage/public_access.py