| 1 | # Azure production readiness |
| 2 | |
| 3 | Status: development and static validation complete; deployment is intentionally out of scope. |
| 4 | |
| 5 | Audit date: 2026-09-11 |
| 6 | |
| 7 | > Iteration 5A addendum: the internal MCP server and `mcp-gateway` workload are now implemented. The inventory and validation totals below remain the completed pre-5A audit baseline; current MCP behavior and validation are recorded in [`05a-local-internal-mcp-gateway.md`](05a-local-internal-mcp-gateway.md). |
| 8 | |
| 9 | This document is the source-of-truth inventory for moving the AI Investment Intelligence Platform from local k3d development to AKS. The audit covered the frontend, Java services, Python AI engines, IBKR connector, Helm chart, k3d configuration, Terraform, scripts, environment examples, migrations, Dockerfiles, and repository documentation. Generated build output, dependency caches, extracted binaries, and virtual environments were excluded from source findings. |
| 10 | |
| 11 | The repository has no separate `k8s/` or `docker/` manifest tree, no Compose definition, and no GitHub Actions, GitLab, Jenkins, CircleCI, or Azure Pipelines configuration. Kubernetes application resources live in the Helm chart; container definitions live beside each service. |
| 12 | |
| 13 | No command in this pass deployed a workload, contacted a Kubernetes cluster, created or changed an Azure resource, applied Terraform, ran an Azure deployment, changed live data, or updated an external documentation system. |
| 14 | |
| 15 | ## Target architecture |
| 16 | |
| 17 | ```mermaid |
| 18 | flowchart LR |
| 19 | User[Browser] --> DNS[Public DNS and TLS] |
| 20 | DNS --> Ingress[Chosen AKS ingress] |
| 21 | Ingress --> Gateway[API gateway ClusterIP] |
| 22 | Gateway --> Services[Internal Java and AI ClusterIP services] |
| 23 | Services --> PG[(External PostgreSQL)] |
| 24 | Services --> Redis[(Managed Redis compatible cache)] |
| 25 | Services --> Kafka[(Managed Kafka or Event Hubs Kafka endpoint)] |
| 26 | Services --> Providers[Approved external providers] |
| 27 | Services -. future .-> Blob[(Azure Blob temporary or durable objects)] |
| 28 | Pods[AKS service accounts] --> WI[Entra Workload Identity] |
| 29 | WI --> KV[Azure Key Vault through CSI] |
| 30 | ACR[Azure Container Registry] --> AKS[AKS kubelet identity] |
| 31 | Services --> OTEL[OpenTelemetry collector] |
| 32 | OTEL --> Monitor[Azure Monitor or another OTLP backend] |
| 33 | ``` |
| 34 | |
| 35 | The design keeps business behavior independent of the hosting environment. LOCAL, TEST, and AZURE choose endpoints, credentials, transport security, persistence, and Kubernetes behavior through configuration. The Azure overlay contains identifiers and obvious `REPLACE_WITH_*` values only. It is an example that must be completed by a release system; it is not deployable production configuration. |
| 36 | |
| 37 | ## Readiness result |
| 38 | |
| 39 | The inventory below contains **100 entries**: **67 AZURE_READY**, **7 LOCAL_ONLY_BUT_ACCEPTABLE**, and **26 deferred or externally blocked**. The 26 comprise 3 backup/recovery, 1 configuration, 3 deployment-seam, 1 identity, 1 networking, 2 observability, 3 scaling, 3 security, 2 storage-abstraction, and 7 external-decision entries. This pass implemented **66** inventory changes; 34 rows are either pre-existing controls, retained local behavior, or deferred work. An `AZURE_READY` row means the code or deployment seam can support the target behavior. It does not mean the external Azure service exists. `LOCAL_ONLY_BUT_ACCEPTABLE` identifies behavior deliberately retained for k3d or a single connector process. Any other classification is an explicit remaining decision or implementation item. |
| 40 | |
| 41 | ## Environment matrix |
| 42 | |
| 43 | | Concern | LOCAL | TEST | AZURE | |
| 44 | |---|---|---|---| |
| 45 | | Profile | `LOCAL` | `TEST` | `AZURE` | |
| 46 | | Images | `localhost:5001`, mutable developer tags allowed | test supplied | ACR repository plus immutable commit tag | |
| 47 | | PostgreSQL | local or in-cluster PostgreSQL, SSL may be disabled | test database | external endpoint, `verify-full`, small configurable pools | |
| 48 | | Redis | local `redis:6379`, TLS disabled | test supplied | external endpoint, TLS and secret-backed auth | |
| 49 | | Kafka | in-cluster PLAINTEXT development broker | test supplied | optional provider-neutral `SASL_SSL` configuration | |
| 50 | | Secrets | uncommitted environment or development Kubernetes Secret | fixtures or test Secret | Key Vault CSI and Workload Identity references | |
| 51 | | Ingress and CORS | local ingress and explicit localhost origins | test supplied | one controlled gateway ingress, TLS, explicit public origin | |
| 52 | | Storage | local-path PostgreSQL PVC and optional IBKR package PVC | ephemeral/test supplied | no local-path dependency; managed data services and future Blob seam | |
| 53 | | Replicas | one by default | one by default | two plus HPA/PDB for proven stateless entry services; unsafe workloads remain one | |
| 54 | | NetworkPolicy | disabled | disabled | ingress restrictions enabled; egress waits for the chosen CNI/firewall design | |
| 55 | | Telemetry | OTEL SDK disabled | disabled unless a test enables it | OTLP variables enabled; collector or operator still required | |
| 56 | | Optional features | local flags | explicit flags | MCP, LLM, scheduled acquisition, Kafka, object storage, and IBKR disabled until approved | |
| 57 | |
| 58 | The Helm sources are [`values.yaml`](../../infrastructure/helm/ai-investment-platform/values.yaml), [`values-dev.yaml`](../../infrastructure/helm/ai-investment-platform/values-dev.yaml), [`values-test.yaml`](../../infrastructure/helm/ai-investment-platform/values-test.yaml), and [`values-azure.yaml`](../../infrastructure/helm/ai-investment-platform/values-azure.yaml). `values-prd.yaml` remains a compatibility overlay, but Azure validation requires the complete Azure overlay rather than treating that legacy file as production-ready by itself. |
| 59 | |
| 60 | ## Identity and secrets |
| 61 | |
| 62 | The chart can create a ServiceAccount, add the Workload Identity client and tenant annotations, label selected pods with `azure.workload.identity/use: "true"`, and mount a `SecretProviderClass`. Terraform enables the AKS OIDC issuer and Workload Identity, creates a user-assigned identity and federated credential, grants that identity `Key Vault Secrets User`, and grants the kubelet identity `AcrPull`. |
| 63 | |
| 64 | Database, Redis, Kafka, JWT, SMTP, research-provider, and broker token mappings are secret references. The chart never requires `AZURE_CLIENT_SECRET`, a service-principal password, an ACR password, or a storage account key. The broker Key Vault adapter now reads the environment variables projected by the deployment layer, avoiding an Azure SDK dependency in business code. |
| 65 | |
| 66 | One shared platform identity is currently the example default. Before production, split it into service-specific identities and vault scopes if the security review requires least privilege. The exact vault names, object names, identity client IDs, tenant ID, and access boundaries are release inputs. |
| 67 | |
| 68 | ## Data, storage, and recovery |
| 69 | |
| 70 | | Data | Storage classification | Recovery classification | Azure direction | |
| 71 | |---|---|---|---| |
| 72 | | User identities, credentials metadata, verification state | DURABLE | MUST_BACKUP | PostgreSQL point-in-time recovery plus restore drills | |
| 73 | | Broker connections and canonical provider mappings | DURABLE | MUST_BACKUP | PostgreSQL backup with the owning schema | |
| 74 | | Instrument master and canonical identity mappings | DURABLE | MUST_BACKUP | PostgreSQL backup; preserve uniqueness and provenance | |
| 75 | | Current persisted portfolios, watchlists, account configuration | DURABLE under current implementation | MUST_BACKUP until privacy migration | PostgreSQL backup; later separate durable preferences from TTL private position context | |
| 76 | | Public research facts, evidence metadata, scores, market history | DURABLE but reconstructable | CAN_REBUILD, expensive | Back up for recovery-time control; upstream sources remain authoritative where applicable | |
| 77 | | Flyway schema history | DURABLE | MUST_BACKUP with database | Restore atomically with each database/schema | |
| 78 | | Key Vault secrets and signing material | DURABLE security configuration | MUST_RECOVER | soft delete/purge protection plus an authorized rotation/recreation runbook; never export into Git | |
| 79 | | ACR release images and provenance | immutable build artifacts | CAN_REBUILD from source, expensive during incident | retain approved release digests/SBOM/signatures according to release policy | |
| 80 | | Redis quotes and future ephemeral portfolio context | CACHE or EPHEMERAL | EPHEMERAL | TLS, authentication, TTL, eviction, and no backup assumption for private context | |
| 81 | | Kafka events if eventing is enabled later | depends on event contract | UNCLASSIFIED today | decide whether topics are replayable transport or a durable source before choosing retention/backup | |
| 82 | | Downloaded NSE PDF binary | transient | EXTERNAL_SOURCE_OF_TRUTH | Continue parsing without durable binary retention | |
| 83 | | Uploaded portfolio file | transient input | EPHEMERAL | Current code processes bytes without a permanent local file; future reports need a TTL store | |
| 84 | | Generated report/download | not implemented | EPHEMERAL target | Implement a TTL object-store interface before the feature ships | |
| 85 | | IBKR Gateway package | proprietary DEV_ONLY artifact | EXTERNAL_SOURCE_OF_TRUTH | Never copy the development RWO PVC model to AKS; use an approved immutable distribution channel | |
| 86 | | Container `/tmp` and frontend cache | EPHEMERAL | CAN_REBUILD | bounded `emptyDir`; no durability promise | |
| 87 | |
| 88 | The chart exposes storage-class and object-storage settings without selecting one storage product for every workload. Azure Disk fits a true single-writer durable workload. Azure Files is reserved for a proven shared-filesystem requirement. Blob storage is the preferred future shape for temporary downloads or durable objects with object semantics. No current application requires Azure Files. |
| 89 | |
| 90 | The Terraform in this repository does not create PostgreSQL, Redis, Kafka, Blob, private endpoints, backup policies, or restore automation. Select those services, define RPO/RTO and retention, enable provider-native backup, and complete a restore exercise before production data is admitted. |
| 91 | |
| 92 | ## Database and migration policy |
| 93 | |
| 94 | The Java database clients accept host, port, database, username, password reference, SSL mode, connect/socket timeouts, and Hikari limits. The research engine accepts the corresponding PostgreSQL parameters and statement timeout. The Azure example uses `sslmode=verify-full` and a maximum pool of six connections per pod to prevent replica multiplication from exhausting PostgreSQL. |
| 95 | |
| 96 | Flyway remains the schema owner in each Java service. Migration settings validate applied migrations, reject out-of-order changes, retry initial connection, and never authorize editing an applied migration. The Python research engine waits for `research-service` readiness when PostgreSQL persistence is enabled, so it does not race that schema owner. Production releases must apply backward-compatible, expand-and-contract migrations before code that depends on the new shape. Destructive contraction belongs in a later release after old replicas are gone. A dedicated migration job can replace startup ownership later, but the present singleton `research-service` release strategy is safe for the first controlled deployment. |
| 97 | |
| 98 | ## Scaling and duplicate-work classification |
| 99 | |
| 100 | | Workload or mechanism | Classification | Current Azure control | Requirement before scaling further | |
| 101 | |---|---|---|---| |
| 102 | | frontend | HORIZONTALLY_SCALABLE | two replicas, HPA, PDB | validate cache and ingress behavior under load | |
| 103 | | api-gateway | HORIZONTALLY_SCALABLE | two replicas, HPA, PDB | add distributed/ingress rate limiting | |
| 104 | | auth-service | HORIZONTALLY_SCALABLE | two replicas, HPA, PDB | confirm mail provider and DB connection budget | |
| 105 | | company, recommendation, risk, notification services | HORIZONTALLY_SCALABLE based on present stateless code | HPA/PDB controls available, disabled by default | load and dependency tests before enabling | |
| 106 | | valuation, ranking, portfolio-optimizer | HORIZONTALLY_SCALABLE based on present stateless code | HPA/PDB controls available, disabled by default | load tests before enabling | |
| 107 | | portfolio-service `ACTIVE_SYNCS` | NEEDS_DB_LOCK or NEEDS_REDIS_LOCK | one replica, `Recreate` | replace process set with a cross-pod claim; keep idempotent DB writes | |
| 108 | | instrument reconciliation | IDEMPOTENT_ENOUGH plus DB lock | PostgreSQL advisory/pessimistic locking exists | verify contention and timeout behavior | |
| 109 | | research-engine acquisition and official-filing flights | NEEDS_DISTRIBUTED_DEDUPE | one replica, `Recreate`, backend explicitly `process` | implement Redis/DB lock or durable work queue with idempotency keys | |
| 110 | | research-service Flyway ownership | SINGLETON_BY_DESIGN for release | one replica, `Recreate` | move migrations to an explicit release phase before HPA | |
| 111 | | broker-service credential/session/rate/circuit state | STATEFUL and NEEDS_DISTRIBUTED_LOCK | one replica, `Recreate` | externalize transient state and coordinate runtime allocation | |
| 112 | | IBKR connector session lock | PROCESS_LOCAL_OK per connector instance | one connector per broker runtime | keep one active replica for each Gateway session | |
| 113 | | scheduled/global refresh feature | NEEDS_DISTRIBUTED_DEDUPE if enabled | feature disabled; no Kubernetes CronJob | choose one scheduler leader, queue, or DB/Redis lease | |
| 114 | | future MCP gateway | UNKNOWN until implementation | disabled, internal-only seam | require stateless handlers or distributed session state before HPA | |
| 115 | |
| 116 | No Spring `@Scheduled` job or Kubernetes CronJob currently performs a global refresh. Research uses request-triggered background tasks and local single-flight coordination. The `scheduledJobsEnabled` flag is therefore a guard for future work, not a distributed scheduler implementation. |
| 117 | |
| 118 | ## Networking, TLS, and egress |
| 119 | |
| 120 | Every rendered Azure Service is `ClusterIP`. Only the API gateway renders an Ingress. TLS secret, host, ingress class, and controller annotations are release inputs. MCP renders no workload or ingress because it has not been implemented, and its reserved exposure is `INTERNAL_ONLY`. |
| 121 | |
| 122 | Azure CORS values require explicit HTTPS origins. The frontend uses same-origin API calls when no public API base is compiled in, keeping server credentials out of `NEXT_PUBLIC_*`. Auth public URLs, broker callbacks, connector login URLs, and provider redirect URLs are configurable. |
| 123 | |
| 124 | The enabled NetworkPolicy restricts namespace ingress and admits the ingress controller only to the gateway. Same-namespace traffic remains allowed because the current service call graph is broad. Kubernetes NetworkPolicy cannot safely express the dynamic FQDN set used by market research. Outbound restriction therefore remains disabled until the team selects Azure CNI behavior plus NAT Gateway, firewall, proxy, or another FQDN-aware egress control. |
| 125 | |
| 126 | | Destination class | Known destinations or behavior | Configuration / control | |
| 127 | |---|---|---| |
| 128 | | NSE and Indian market reference data | `www.nseindia.com`, `nsearchives.nseindia.com`, `www.niftyindices.com` | configured endpoints where stable; fetcher rejects loopback/private/metadata targets | |
| 129 | | Yahoo Finance | `query1.finance.yahoo.com`, `query2.finance.yahoo.com`, provider document links | configured discovery/quote/summary roots; HTTPS verification enabled | |
| 130 | | SEC EDGAR | `www.sec.gov`, `data.sec.gov` | configured ticker and company-facts endpoints | |
| 131 | | EODHD | `eodhd.com` | configured HTTPS base URL; API key comes from a secret reference | |
| 132 | | Search | disabled, SearXNG, or configured search API | explicit endpoint, allowed domains, quotas, timeouts, and secret references | |
| 133 | | Issuer and evidence sites | discovered public HTTPS hosts | application SSRF controls plus future firewall/proxy policy; cannot be a static IP list alone | |
| 134 | | IBKR | configured OAuth/API endpoint and loopback Gateway inside its connector workload | callbacks and public login base configurable; stable egress may be required | |
| 135 | | ICICI Direct | configured official login and Breeze API hosts | disabled until documentation is approved; secrets are referenced | |
| 136 | | HDFC Securities | configured provider API and callback behavior | disabled until approved; avoid URL logging because provider contracts can place a key in a query | |
| 137 | | SMTP | configured host and port | TLS/auth supplied by selected mail provider and Key Vault-backed Secret | |
| 138 | | Future LLM/MCP | no active endpoint | disabled flags; require explicit hostname and egress review before enablement | |
| 139 | |
| 140 | Provider certificate verification is on by default. The IBKR development connector retains an explicit insecure switch for a local self-signed Gateway; the Azure overlay forces it off. PostgreSQL, Redis, Kafka, ingress, callbacks, and OTLP have secure Azure examples. Internal service TLS remains an infrastructure decision; current traffic is private HTTP inside the cluster. |
| 141 | |
| 142 | ## Availability, security, and observability |
| 143 | |
| 144 | All 14 application deployments render startup, readiness, and liveness probes, requests and limits, a non-root pod context, `RuntimeDefault` seccomp, disabled privilege escalation, dropped Linux capabilities, and an optional read-only root filesystem. Bounded `emptyDir` storage supplies `/tmp` where a read-only image still needs temporary writes. Slow startup is absorbed by startup probes. Readiness checks application health and does not intentionally call an optional external provider. |
| 145 | |
| 146 | Rolling update settings, topology spread, node selectors, tolerations, affinity, HPA, and PDB are values-driven. Stateful or locally coordinated services use one replica and `Recreate`; stateless entry services use rolling updates. The example permits zone spread but does not require unavailable zones in a low-cost cluster. |
| 147 | |
| 148 | Java logs use a structured logfmt pattern with UTC timestamp, service, environment, level, event/message, and request ID. The Python research and IBKR services emit structured JSON and redact credential-shaped fields. Raw email verification and reset tokens are no longer logged. `X-Request-ID` and `X-Correlation-ID` are validated, returned, and propagated through gateway, portfolio, research, and IBKR calls where those paths exist. W3C trace context can be added by the selected OpenTelemetry instrumentation. |
| 149 | |
| 150 | The chart emits standard OTEL service name, endpoint, protocol, enable/disable, exporter, and resource attributes. This configuration is compatible with an OpenTelemetry Collector exporting to Azure Monitor or another backend. No Azure Monitor SDK was added to business services. A collector/operator or language agents and sampling policy remain required for actual distributed traces and metrics. Telemetry must exclude raw positions, portfolio files, credentials, authorization headers, cookies, provider query secrets, and document bodies. |
| 151 | |
| 152 | ## CI/CD and cost controls |
| 153 | |
| 154 | No repository-native GitHub Actions, GitLab, Jenkins, or other CI pipeline was present, so this pass did not invent a deployment workflow. [`scripts/validate_azure_readiness.py`](../../scripts/validate_azure_readiness.py) is the static CI entry point. A future pipeline should test, build, scan, generate an SBOM, tag images with an immutable commit digest, authenticate to Azure using OIDC federation, push to ACR, run Helm lint/template and policy checks, obtain approval, and only then perform a separately authorized Helm deployment. No Azure client secret should be stored in CI. Terraform also has no remote-state backend declaration; choose an encrypted, access-controlled backend with locking and recovery before any shared infrastructure workflow. |
| 155 | |
| 156 | The chart exposes replica counts, HPA min/max, resource requests/limits, optional service flags, provider timeouts/retries, cache TTLs, research refresh windows, and disabled development dependencies. Terraform exposes node count and size. Non-production shutdown scheduling is an operations policy because stopping a shared cluster or managed database is outside application code. |
| 157 | |
| 158 | ## Future MCP and LLM services |
| 159 | |
| 160 | MCP is represented only by a disabled configuration seam: `ClusterIP`, no public ingress, optional Workload Identity, Key Vault, OTEL, NetworkPolicy, and HPA settings. It can later support local STDIO/HTTP and an internal AKS HTTP service without forking business logic. No MCP server or deployment is rendered today. |
| 161 | |
| 162 | LLM integration is also disabled. A future orchestrator must use secret references, bounded HTTP pools/timeouts, explicit model/provider configuration, OTEL with private prompt/content filtering, controlled egress, cost quotas, and idempotent/durable work coordination before horizontal scaling. |
| 163 | |
| 164 | ## Files changed by this audit |
| 165 | |
| 166 | The pass changed 69 files. Some were already part of an in-progress working tree; the entries below identify the files to which this audit added Azure-readiness changes. |
| 167 | |
| 168 | - Configuration and documentation: `config/dev/application.env.example`, `config/prd/application.env.example`, `docs/architecture/azure-production-readiness.md`. |
| 169 | - Frontend: `frontend/Dockerfile`, `frontend/app/config.ts`, `frontend/app/components/investment-workspace.tsx`. |
| 170 | - AI images: `ai/portfolio-optimizer/Dockerfile`, `ai/ranking-engine/Dockerfile`, `ai/research-engine/Dockerfile`, `ai/valuation-engine/Dockerfile`. |
| 171 | - Helm values: `infrastructure/helm/ai-investment-platform/values.yaml`, `values-dev.yaml`, `values-test.yaml`, `values-azure.yaml`, `values-prd.yaml`. |
| 172 | - Helm templates: `templates/_helpers.tpl`, `ai-services.yaml`, `api-gateway-ingress.yaml`, `dev-dependencies.yaml`, `frontend.yaml`, `ibkr-connector.yaml`, `java-services.yaml`, `network-policies.yaml`, `platform-resources.yaml`, `validate.yaml`. |
| 173 | - Terraform environment: `infrastructure/terraform/environments/prd/main.tf`, `outputs.tf`, `variables.tf`. |
| 174 | - Terraform modules: `infrastructure/terraform/modules/acr/outputs.tf`; `modules/aks/main.tf`, `outputs.tf`, `variables.tf`; `modules/key-vault/main.tf`, `outputs.tf`, `variables.tf`. |
| 175 | - Audit scripts: `scripts/scan_repository_secrets.py`, `scripts/validate_azure_readiness.py`. |
| 176 | - Java configuration: `services/api-gateway/src/main/resources/application.yml`, `services/auth-service/src/main/resources/application.yml`, `services/broker-service/src/main/resources/application.yml`, `services/company-service/src/main/resources/application.yml`, `services/notification-service/src/main/resources/application.yml`, `services/portfolio-service/src/main/resources/application.yml`, `services/recommendation-service/src/main/resources/application.yml`, `services/research-service/src/main/resources/application.yml`, `services/risk-service/src/main/resources/application.yml`. |
| 177 | - Java behavior: `services/api-gateway/src/main/java/com/aiinvestment/apigateway/ApiGatewayCorsConfiguration.java`, `PortfolioRouteController.java`; `services/auth-service/src/main/java/com/aiinvestment/auth/ConfiguredAuthEmailSender.java`; `services/broker-service/src/main/java/com/aiinvestment/broker/connector/ibkr/IBKRIndividualConnector.java`, `services/broker-service/src/main/java/com/aiinvestment/broker/security/AzureKeyVaultSecretProvider.java`; `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/application/Nifty500ReferenceService.java`, `PortfolioService.java`, `StructuredMarketClient.java`; and `shared/java/web/src/main/java/com/aiinvestment/shared/web/CorrelationIdFilter.java`. |
| 178 | - Java tests: `services/api-gateway/src/test/java/com/aiinvestment/apigateway/ApiGatewayCorsConfigurationTest.java`. |
| 179 | - Research engine: `ai/research-engine/app/main.py`, `postgres_persistence.py`, `repository.py`, `settings.py`, `source_discovery.py`, `structured_market.py`, `tests/test_azure_configuration.py`, `tests/test_logging_configuration.py`, `tests/test_portfolio_service_configuration.py`. |
| 180 | - IBKR connector: `services/ibkr-connector/app/main.py`, `observability.py`, `settings.py`, `tests/test_observability.py`. |
| 181 | |
| 182 | ## Complete Azure-readiness inventory |
| 183 | |
| 184 | | # | Classification | File | Symbol / resource | Current assumption | Azure risk | Required change | Implemented now? | Why | |
| 185 | |---:|---|---|---|---|---|---|:---:|---| |
| 186 | | 1 | AZURE_READY | `infrastructure/helm/ai-investment-platform/values*.yaml` | environment profiles | The chart previously mixed DEV/PRD conventions. | Profile drift can scatter Azure checks through services. | Define LOCAL, TEST, and AZURE centrally. | YES | The base and three overlays now select explicit profiles. | |
| 187 | | 2 | AZURE_READY | `config/dev/application.env.example`, `config/prd/application.env.example` | environment examples | Examples used DEV and PRD labels. | Manual launches could bypass standardized behavior. | Align examples to LOCAL and AZURE and show transport controls. | YES | Examples now expose profile, TLS, pool, Kafka, and OTEL settings without secrets. | |
| 188 | | 3 | AZURE_READY | `values-azure.yaml` | non-secret Azure overlay | No complete AKS example existed. | Release configuration would require redesign or ad hoc flags. | Add ACR, identity, Key Vault, external data, ingress, security, scale, and telemetry examples. | YES | The overlay uses obvious identifiers/placeholders only. | |
| 189 | | 4 | AZURE_READY | `scripts/validate_azure_readiness.py` | static profile validator | Azure invariants were not executable. | Regressions could reintroduce local paths or inline secrets. | Render both environments and assert safety properties. | YES | Thirty named readiness requirements are covered by static assertions. | |
| 190 | | 5 | AZURE_READY | Helm helpers and workload templates | image repository and tag | Image composition was chart-specific and partly hard-coded. | ACR migration could leak into application code. | Centralize registry and allow per-service repository/tag overrides. | YES | Images are deployment configuration. | |
| 191 | | 6 | LOCAL_ONLY_BUT_ACCEPTABLE | `values.yaml`, `values-dev.yaml`, `infrastructure/k3d/cluster-dev.yaml` | local registry | k3d uses `localhost:5001`. | That address is invalid in AKS. | Keep only in LOCAL; reject it from AZURE. | YES | Azure validation blocks localhost/k3d registries. | |
| 192 | | 7 | AZURE_READY | `infrastructure/terraform/modules/acr` | ACR module | ACR already had a module and login-server output. | None at source level. | Retain as a deploy-time registry. | NO | Existing abstraction was adequate. | |
| 193 | | 8 | AZURE_READY | `infrastructure/terraform/environments/prd/main.tf` | `aks_acr_pull` | AKS pull authorization was not joined to ACR. | Pods would require registry passwords or fail to pull. | Grant the kubelet identity `AcrPull`. | YES | Managed identity integration removes pull secrets. | |
| 194 | | 9 | AZURE_READY | `values-azure.yaml`, `templates/validate.yaml` | immutable image tag | Local `latest` tags were inherited. | Rollback and provenance would be ambiguous. | Require a release-supplied immutable tag. | YES | Azure render rejects `latest` and demonstrates a Git SHA. | |
| 195 | | 10 | AZURE_READY | `templates/platform-resources.yaml`, `_helpers.tpl` | Kubernetes ServiceAccount | Workloads relied on default service-account behavior. | Workload federation and token control were unavailable. | Make creation, name, annotations, and token mounting configurable. | YES | A named account is rendered and tokens default off. | |
| 196 | | 11 | AZURE_READY | `_helpers.tpl`, workload templates | Workload Identity pod label | Pods had no Azure workload label. | The webhook would not inject federated identity settings. | Label only configured Azure consumers. | YES | Selected secret consumers receive the required label. | |
| 197 | | 12 | AZURE_READY | `infrastructure/terraform/modules/aks` | AKS OIDC and Workload Identity | Cluster module did not enable either feature. | Long-lived service-principal credentials might be used. | Enable the OIDC issuer and Workload Identity. | YES | Both settings are configurable and enabled by default in the module. | |
| 198 | | 13 | AZURE_READY | `infrastructure/terraform/environments/prd/main.tf` | federated identity credential | No Kubernetes-to-Entra trust existed. | ServiceAccount tokens could not obtain an Azure identity. | Bind namespace and ServiceAccount to a user-assigned identity. | YES | Terraform declares the identity and federation subject. | |
| 199 | | 14 | AZURE_READY | repository-wide | Azure credential model | No actual client secret was required, but absence was not enforced. | A future edit could add a long-lived client secret. | Prohibit `AZURE_CLIENT_SECRET` and service-principal passwords in rendered config. | YES | Static validation and the secret scan enforce the current model. | |
| 200 | | 15 | AZURE_READY | `templates/platform-resources.yaml` | `SecretProviderClass` | Key Vault values were present but not rendered. | Pods could not consume Key Vault material. | Render the Azure provider with workload identity metadata. | YES | CSI objects and optional synchronized Kubernetes Secrets render. | |
| 201 | | 16 | AZURE_READY | `values-azure.yaml`, workload templates | secret mappings | DB, Redis, Kafka, JWT, SMTP, research, and broker values lacked a unified reference example. | Inline values could leak into Helm releases or ConfigMaps. | Map vault object names to referenced Secret keys. | YES | Sensitive env vars use `secretKeyRef`; no secret value is in the overlay. | |
| 202 | | 17 | AZURE_READY | `infrastructure/terraform/modules/key-vault` | Key Vault security | Vault defaults did not express production recovery/RBAC. | Deletion or legacy access policies could weaken recovery and control. | Enable RBAC, purge protection, and 90-day retention. | YES | Module defaults are production-oriented. | |
| 203 | | 18 | AZURE_READY | `AzureKeyVaultSecretProvider` | broker secret provider | The Azure branch threw an unsupported exception. | Azure broker credentials could not be injected without code changes. | Read deployment-injected environment values behind the provider interface. | YES | CSI/Secret injection works without Azure SDK coupling. | |
| 204 | | 19 | MISSING_AZURE_IDENTITY | `values-azure.yaml` | identity granularity | One shared platform ServiceAccount and identity are the example. | Compromise of one pod can grant broader vault access than needed. | Decide and implement per-service identities/vault roles where required. | NO | Requires a production least-privilege and operations decision. | |
| 205 | | 20 | AZURE_READY | four Java `application.yml` files | JDBC endpoint configuration | Local host and disabled SSL were defaults. | External Flexible Server would need TLS and bounded startup behavior. | Expose host, port, DB, user, password, SSL, and timeouts. | YES | Local defaults remain; Helm supplies Azure values. | |
| 206 | | 21 | AZURE_READY | `ai/research-engine/app/settings.py`, `postgres_persistence.py` | Python PostgreSQL client | SSL and timeouts were incomplete. | Connections could be insecure or hang during network faults. | Bind SSL mode, connect timeout, and statement timeout. | YES | Settings flow into psycopg connection options. | |
| 207 | | 22 | AZURE_READY | `values-azure.yaml` | external PostgreSQL | In-cluster PostgreSQL was the practical default. | Stateful data in AKS would inherit a dev topology. | Disable dev dependencies and point clients at an external host. | YES | No PostgreSQL workload/PVC renders in Azure. | |
| 208 | | 23 | AZURE_READY | Helm values and JDBC/Python clients | PostgreSQL TLS | Local `disable` could carry into production. | Credentials and data could traverse without verified TLS. | Use configurable SSL and require `verify-full` in Azure. | YES | Azure render validation asserts it. | |
| 209 | | 24 | AZURE_READY | Java Hikari config and Helm database pool | database connections | Library defaults multiplied by replicas were not controlled centrally. | PostgreSQL connection exhaustion. | Expose maximum/minimum size and lifetime/timeouts. | YES | Azure example limits each pod to six connections. | |
| 210 | | 25 | BLOCKED_EXTERNAL_DECISION | Helm `database.authMode` | PostgreSQL authentication | The implemented production example uses a Key Vault-backed password. | Entra database auth may be preferred but needs token-aware pool behavior. | Choose password versus Entra auth and implement token refresh if selected. | NO | Workload Identity alone does not make JDBC/psycopg token refresh safe. | |
| 211 | | 26 | AZURE_READY | database service `application.yml` files | Flyway startup | External database latency and rolling semantics were implicit. | Pods could fail early or accept an incompatible migration order. | Configure retries, validation, and out-of-order false. | YES | Startup behavior is explicit. | |
| 212 | | 27 | AZURE_READY | `templates/ai-services.yaml` | research schema order | Python could start before the Java Flyway owner. | Queries could race missing schema. | Gate research-engine startup on research-service readiness. | YES | An init container waits only when persistence is enabled. | |
| 213 | | 28 | MISSING_AZURE_DEPLOYMENT_SEAM | service-owned Flyway migrations | cross-release migration phase | Migrations still execute during Java service startup. | Complex multi-service changes may be harder to order and roll back. | Adopt schema-first release steps or a dedicated migration job. | NO | Current singleton/backward-compatible policy is adequate for a controlled first deployment, but CI/CD must own ordering. | |
| 214 | | 29 | LOCAL_ONLY_BUT_ACCEPTABLE | `templates/dev-dependencies.yaml` | local PostgreSQL RWO PVC | k3d persists dev data on `local-path`. | `local-path` is node-bound and unsuitable for AKS production data. | Keep dev-only and omit from Azure. | YES | Azure render contains neither PVC nor local-path. | |
| 215 | | 30 | AZURE_READY | portfolio Redis application/Helm config | Redis endpoint | The client assumed local unauthenticated Redis. | Managed Redis needs TLS, auth, timeouts, and bounded pools. | Expose URL/host/port, TLS, secret auth, timeouts, and pool sizes. | YES | Local and Azure transports are separate values. | |
| 216 | | 31 | MISSING_AZURE_STORAGE_ABSTRACTION | portfolio context code | future ephemeral portfolio store | Private portfolio state is still database/domain oriented; no shared TTL store interface exists. | Future privacy flow cannot move between replicas through Redis cleanly. | Introduce `RedisEphemeralPortfolioContextStore` or equivalent when privacy migration starts. | NO | Full portfolio migration was explicitly outside this pass. | |
| 217 | | 32 | AZURE_READY | Helm Kafka values/templates | Kafka transport configuration | Local broker assumed PLAINTEXT and no auth. | Managed Kafka or Event Hubs requires TLS/SASL, consumer identity, trust, and retry controls. | Add bootstrap, protocol, mechanism, username, secret, consumer-group prefix, DNS, trust-bundle mount, retries, and timeouts. | YES | Provider-neutral settings render when Kafka is enabled. | |
| 218 | | 33 | BLOCKED_EXTERNAL_DECISION | repository-wide eventing | Kafka provider and runtime | No business event producer/consumer currently uses the Kafka settings. | Topic/consumer semantics and service compatibility are unproven. | Select managed Kafka, Confluent, or Event Hubs only when eventing is implemented. | NO | Avoids premature Azure-specific coupling. | |
| 219 | | 34 | AZURE_READY | Helm `objectStorage` | object-storage deployment seam | No centralized Azure object settings existed. | A future report feature might bind directly to pod files. | Add provider, endpoint, container, auth, and temporary TTL values. | YES | Workload Identity is the expected auth mode and the feature is off. | |
| 220 | | 35 | MISSING_AZURE_STORAGE_ABSTRACTION | application services | object-storage implementation | No Blob/file interface exists because no durable object feature needs one today. | Future reports or evidence uploads could add local persistence. | Implement an object interface before durable or temporary objects ship. | NO | Adding an Azure SDK now would be unused vendor coupling. | |
| 221 | | 36 | AZURE_READY | `PortfolioImportService` | uploaded portfolio files | Multipart bytes are parsed in request flow, with no permanent file write. | No unavoidable pod-local durability currently exists. | Preserve transient processing and add TTL storage only if asynchronous processing is introduced. | NO | Existing behavior is already compatible. | |
| 222 | | 37 | AZURE_READY | research fetch/extraction/persistence | NSE PDF binary | The binary is fetched and parsed; evidence metadata/text is stored. | Long-lived local binaries could leak data or require shared disks. | Continue without durable binary retention. | NO | Current architecture matches the accepted rule. | |
| 223 | | 38 | AZURE_READY | workload templates | `/tmp` and frontend cache | Read-only filesystems still need writable temporary space. | Pods could crash or accidentally treat temp files as durable. | Mount bounded `emptyDir` and document ephemerality. | YES | All app pods receive the temporary seam. | |
| 224 | | 39 | LOCAL_ONLY_BUT_ACCEPTABLE | `ibkr-connector.yaml`, `values-dev.yaml` | IBKR Gateway package PVC | Development uses a proprietary package on an RWO claim and loopback Gateway. | Node affinity and mutable package content cannot be the AKS architecture. | Keep explicitly DEV_ONLY and disable in Azure. | YES | Azure rendering has no package claim or connector workload. | |
| 225 | | 40 | BLOCKED_EXTERNAL_DECISION | IBKR runtime architecture | immutable Gateway distribution | The repository cannot redistribute or invent the proprietary package. | Production connector pods cannot be recreated legally/reliably yet. | Agree on an approved immutable package source and lifecycle. | NO | Vendor/legal distribution decision is external. | |
| 226 | | 41 | AZURE_READY | all Helm Service resources | service exposure | Service types were not consistently controlled. | Internal research, broker, MCP, or data services could become public. | Default every service to ClusterIP. | YES | Static validation checks all Azure Services. | |
| 227 | | 42 | AZURE_READY | `api-gateway-ingress.yaml` | public ingress | Ingress was tied to local defaults and lacked complete TLS inputs. | Host/controller/TLS changes could require template edits. | Parameterize class, annotations, host, path, and TLS secret. | YES | Azure exposes only the gateway. | |
| 228 | | 43 | AZURE_READY | `mcpGateway` values and validation | future MCP exposure | No MCP workload exists. | A future default could expose it publicly. | Reserve ClusterIP/internal-only behavior and prohibit ingress. | YES | Azure validation asserts that no MCP resource renders. | |
| 229 | | 44 | AZURE_READY | gateway CORS config and Azure values | allowed origins | Local localhost origins were the effective default. | Broad or wildcard credentialed CORS is unsafe. | Supply explicit Azure HTTPS origins and reject wildcard/localhost. | YES | Gateway binding and render checks enforce the overlay. | |
| 230 | | 45 | AZURE_READY | auth/broker Helm values and env examples | public and OAuth callback URLs | Local callback bases were common. | OAuth/email links would point at localhost or the wrong host. | Parameterize public host, auth URL, broker callbacks, and redirects. | YES | Azure values use explicit HTTPS placeholders. | |
| 231 | | 46 | AZURE_READY | `serviceEndpoints`, workload templates | service discovery | Several application defaults use localhost. | Localhost inside a pod is the pod itself. | Override every inter-service URL with Kubernetes DNS. | YES | Local code defaults remain useful; Helm injects service names. | |
| 232 | | 47 | LOCAL_ONLY_BUT_ACCEPTABLE | `services/ibkr-connector/app/main.py` | Gateway host allowlist | Connector proxies only loopback IBKR Gateway hosts. | It cannot target an arbitrary remote Gateway. | Retain for same-workload security until the approved broker topology changes. | NO | Loopback is deliberate isolation, not AKS service discovery. | |
| 233 | | 48 | AZURE_READY | `network-policies.yaml` | ingress policy | No chart NetworkPolicy governed public-to-internal traffic. | Internal pods were reachable from any namespace by default. | Add opt-in restrictive ingress and ingress-controller-to-gateway rule. | YES | Enabled only in Azure to preserve local parity. | |
| 234 | | 49 | MISSING_AZURE_NETWORKING | `network-policies.yaml` | egress policy | Provider egress spans dynamic public FQDNs; egress restriction is off. | A compromised pod could make unintended outbound calls. | Choose FQDN-aware firewall/proxy/CNI behavior and add least-privilege rules. | NO | Kubernetes IP blocks alone are unsafe for changing provider addresses. | |
| 235 | | 50 | BLOCKED_EXTERNAL_DECISION | Terraform networking and AKS variables | private endpoints and cluster API | VNet/subnet exist; private cluster and local-account disabling default off; managed data endpoints are absent. | Control plane/data services may be public unless the release design closes them. | Decide private AKS, Entra admin access, private DNS, endpoints, NAT, and operator connectivity. | NO | These choices depend on the Azure landing zone. | |
| 236 | | 51 | MISSING_AZURE_CONFIG | Java HTTP clients | outbound client bounds and pools | The gateway and research engine have configurable bounds; several broker/market clients use bounded constants, and some construct clients locally. | Traffic shifts may require tuning, while per-client construction can waste connections. | Move remaining clients to shared bounded factories with configurable connect/read/pool/retry policy; never retry unsafe writes. | NO | The unbounded gateway path was fixed now, but the broader client refactor needs provider-specific regression coverage. | |
| 237 | | 52 | AZURE_READY | IBKR settings, `templates/validate.yaml` | insecure TLS flag | Local Gateway can require `insecureTls`. | Carrying it to Azure would trust a spoofed endpoint. | Force all insecure switches false in Azure. | YES | The Azure overlay and chart validation reject insecure mode. | |
| 238 | | 53 | AZURE_READY | Java, AI, frontend, and connector Helm templates | application probes | Many deployments lacked a uniform startup/readiness/liveness set. | Slow pods could restart repeatedly or receive traffic early. | Render all three probes with configurable thresholds. | YES | Fourteen Azure deployments pass the probe assertions. | |
| 239 | | 54 | LOCAL_ONLY_BUT_ACCEPTABLE | `dev-dependencies.yaml` | dependency probes | Dev PostgreSQL, Redis, Kafka, and SearXNG needed local health gates. | Unready local dependencies make parity tests flaky. | Add appropriate probes. | YES | The additions affect only local dependencies. | |
| 240 | | 55 | AZURE_READY | broker and IBKR connector probe values | slow broker startup | Broker/Gateway startup can exceed ordinary liveness timing. | Kubernetes could kill it before it becomes usable. | Use a generous startup probe and independent liveness/readiness. | YES | Startup thresholds are values-driven and provider health is not the app readiness contract. | |
| 241 | | 56 | AZURE_READY | all workload values/templates | requests and limits | Resource controls were incomplete. | AKS scheduling and HPA would be unreliable. | Add modest defaults and per-environment overrides. | YES | Every rendered app container has requests and limits. | |
| 242 | | 57 | AZURE_READY | workload templates and Azure overrides | HPA | Replica counts were static. | Stateless entry points could not respond to load. | Add autoscaling/v2 settings and enable only proven services. | YES | Frontend, gateway, and auth enable HPA; all others expose the seam. | |
| 243 | | 58 | AZURE_READY | workload templates and Azure overrides | PDB | Planned replicas had no voluntary-disruption protection. | Node maintenance could remove all entry pods. | Add configurable PDBs for multi-replica services. | YES | Frontend, gateway, and auth enable PDBs. | |
| 244 | | 59 | AZURE_READY | global and workload rollout values | rollout strategy | A single strategy would be unsafe for local-state workloads. | Rolling two active singleton instances can duplicate work. | Make rolling settings configurable and use `Recreate` for constrained services. | YES | Portfolio, broker, research-service, and research-engine remain one-at-a-time. | |
| 245 | | 60 | AZURE_READY | workload templates | topology and scheduling | Scheduling controls were incomplete or tied to local runtime concerns. | Replicas could land on one zone/node or require a dev node. | Add optional spread, affinity, selectors, and tolerations without node names. | YES | Azure uses soft zone spread; no fixed node is required. | |
| 246 | | 61 | AZURE_READY | Helm pod/container security values | runtime security context | Secure defaults were not rendered consistently. | Root/capabilities/writable roots widen container impact. | Set non-root, RuntimeDefault seccomp, no escalation, drop all, read-only root. | YES | Static render checks the controls on every app deployment. | |
| 247 | | 62 | AZURE_READY | 15 Dockerfiles | container user | Java/IBKR images used numeric non-root users, while frontend/Python images used named users. | Kubernetes can reject a named image user when `runAsNonRoot` cannot prove its UID. | Use explicit numeric non-root UID/GID declarations. | YES | All application images now align with the pod policy. | |
| 248 | | 63 | MISSING_AZURE_SECURITY | Java/Python Dockerfiles | image supply chain | Most backend images are single-stage and tags are not digest-pinned. | Build tools/layers and mutable bases increase supply-chain surface. | Add CI scanning, SBOM/signing, digest policy, and optimize stages where material. | NO | Functional images pass checks; supply-chain policy belongs with CI design. | |
| 249 | | 64 | AZURE_READY | nine Spring `application.yml` files | structured Java logs | Default console logs lacked a common cloud shape. | Central searches could not reliably correlate services. | Emit UTC logfmt fields for service, environment, level, request ID, and safe message. | YES | All Java services share the pattern. | |
| 250 | | 65 | AZURE_READY | research and IBKR Python logging | structured/redacted logs | Logs were conventional text and inconsistent. | Tokens or multiline data could leak into central logging. | Emit structured JSON and redact credential-shaped fields. | YES | Both active Python network services install safe logging. | |
| 251 | | 66 | AZURE_READY | `ConfiguredAuthEmailSender` | auth lifecycle logging | DEV logging included raw verification/reset tokens. | Central logs would become an account takeover source. | Log event metadata without token value. | YES | Only token presence and safe identifiers remain. | |
| 252 | | 67 | MISSING_AZURE_OBSERVABILITY | application telemetry policy | financial-data field policy | Log redaction exists, but no organization-wide schema/sampling review exists. | Positions, uploads, prompts, or evidence text could enter telemetry through future code. | Define tested field allowlists and retention before centralized production telemetry. | NO | Requires privacy and operations policy beyond configuration. | |
| 253 | | 68 | AZURE_READY | shared correlation filter and outbound clients | request correlation | Generated request IDs were not consistently returned or propagated. | Cross-service incidents would be hard to trace. | Validate/generate headers and propagate downstream. | YES | Gateway, portfolio, research, and IBKR paths now carry IDs. | |
| 254 | | 69 | AZURE_READY | Helm common env and Azure values | OpenTelemetry configuration | No vendor-neutral deployment surface existed. | Azure Monitor integration might spread vendor SDK code across services. | Add standard OTEL endpoint, service, protocol, exporters, flags, and resource attributes. | YES | OTEL is disabled locally and Azure-compatible when enabled. | |
| 255 | | 70 | MISSING_AZURE_OBSERVABILITY | workload images and cluster add-ons | actual OTEL instrumentation/export | Env settings alone do not create traces or metrics. | Operators may expect telemetry that is not emitted. | Select collector/operator or agents, sampling, credentials, and Azure Monitor exporter. | NO | No Azure SDK was added without a chosen operating model. | |
| 256 | | 71 | AZURE_READY | `frontend/app/config.ts`, Dockerfile | browser API base | Production fallback pointed at localhost. | Browser requests would fail or bypass ingress. | Default production to same-origin and expose build-time public URL. | YES | Local `.env.example` still supplies its explicit URL. | |
| 257 | | 72 | AZURE_READY | frontend source and static validator | `NEXT_PUBLIC_*` boundary | Only public API/dev-login settings are exposed. | Server secrets in a public variable become browser assets. | Enforce absence of DB/provider/JWT/SMTP secrets. | YES | Static scanning covers the frontend source and build inputs. | |
| 258 | | 73 | AZURE_READY | auth/gateway Helm env | JWT signing key | A per-pod/default key could invalidate sessions and diverge replicas. | Restart or HPA could break authentication. | Inject one external secret reference into auth and gateway. | YES | Azure maps the signing key from Key Vault-backed Secret. | |
| 259 | | 74 | MISSING_AZURE_SECURITY | auth JWT implementation | signing-key rotation | One active symmetric key is supported; overlapping key IDs are not. | Rotation can invalidate all sessions or require downtime. | Add key IDs and verify-current-plus-previous or adopt an approved external issuer. | NO | Authentication redesign was outside the infrastructure pass. | |
| 260 | | 75 | AZURE_READY | auth application and Helm email values | SMTP | Mail settings and secrets were not represented end-to-end in Azure. | Verification/reset mail could rely on local assumptions or inline credentials. | Configure host, port, from, public URL, and secret-backed credentials. | YES | Azure example carries references only. | |
| 261 | | 76 | MISSING_AZURE_SECURITY | gateway/ingress values | rate limiting | No distributed application or selected ingress limiter protects login/reset/research. | Abuse can amplify provider cost and authentication traffic. | Select ingress/application limiter and define endpoint-specific production policies. | NO | Controller and shared counter backend are external decisions. | |
| 262 | | 77 | AZURE_READY | Helm `featureFlags` and integration values | optional capabilities | Optional integrations were controlled inconsistently. | Azure could activate providers, schedulers, MCP, or LLM unintentionally. | Default optional features off and expose explicit flags. | YES | Azure starts with MCP, LLM, scheduled acquisition, Kafka, Blob, and IBKR off. | |
| 263 | | 78 | MISSING_AZURE_SCALING | research repository/settings | process-local single-flight | Acquisition deduplicates only within one process. | Multiple replicas repeat expensive global provider work. | Implement DB/Redis lease or durable idempotent queue. | NO | Azure holds research-engine at one replica and labels the backend `process`. | |
| 264 | | 79 | MISSING_AZURE_SCALING | `PortfolioService.ACTIVE_SYNCS` | process-local portfolio sync set | Duplicate suppression is JVM-local. | Two pods can sync the same portfolio simultaneously. | Replace with DB/Redis claim while retaining idempotent writes. | NO | Azure holds portfolio-service at one replica. | |
| 265 | | 80 | MISSING_AZURE_SCALING | broker in-memory stores/rate/circuit state | broker transient state | Credentials, sessions, tokens, limits, and circuit state are partly process-local. | Failover loses state; replicas can conflict over a connector. | Externalize approved transient state and coordinate allocation. | NO | Azure holds broker-service at one replica. | |
| 266 | | 81 | LOCAL_ONLY_BUT_ACCEPTABLE | IBKR connector synchronization | per-Gateway lock | The connector uses process-local serialization. | Two replicas against one Gateway session would conflict. | Preserve a single connector instance per broker runtime. | NO | This is the correct isolation boundary until broker architecture changes. | |
| 267 | | 82 | AZURE_READY | feature flags and Helm values | scheduled jobs switch | Future global timers had no environment guard. | Every replica could run the same task. | Default scheduled jobs off. | YES | The switch is rendered to workloads. | |
| 268 | | 83 | AZURE_READY | repository-wide scheduler audit | CronJobs and Spring schedules | No Kubernetes CronJob or active `@Scheduled` global refresh exists. | There is no current duplicate cluster schedule. | Require a lease/queue before introducing one. | NO | Existing async tasks are request-triggered. | |
| 269 | | 84 | MISSING_AZURE_BACKUP_RECOVERY | PostgreSQL schemas | identity/account backup | Canonical identities, auth state, broker mappings, and current portfolio data need recovery. | Loss would be unrecoverable from public providers. | Define managed DB PITR, retention, RPO/RTO, encryption, and restore drills. | NO | No Azure data service is provisioned in this pass. | |
| 270 | | 85 | MISSING_AZURE_BACKUP_RECOVERY | research/market schemas | rebuildable research data | Data can be rebuilt but provider access and history make that expensive. | Rebuild time may exceed recovery objectives. | Decide backup tier and document rebuild runbooks. | NO | Business RTO/cost decision is pending. | |
| 271 | | 86 | AZURE_READY | Redis/session classification | ephemeral cache recovery | Cache and future private TTL context should not become accidental durable truth. | Backing up private ephemeral data would violate the target model. | Mark it EPHEMERAL and rebuild/expire it. | YES | The recovery table records that contract. | |
| 272 | | 87 | MISSING_AZURE_BACKUP_RECOVERY | release and data operations | restore/migration runbook | Flyway history is coupled to DB state, but no Azure restore rehearsal exists. | Restored data and application versions can be incompatible. | Restore DB plus Flyway history, validate, then release a compatible image. | NO | Requires selected services and an operations environment. | |
| 273 | | 88 | AZURE_READY | this document and configurable provider URLs | egress inventory | External provider destinations were distributed across clients. | Firewall/NAT design lacked a review list. | Centralize destination classes and controls. | YES | The preceding egress table records current and future destinations. | |
| 274 | | 89 | BLOCKED_EXTERNAL_DECISION | broker/provider and network configuration | stable outbound IP | Some broker/provider contracts may allowlist source IP or callback hosts. | Dynamic AKS egress can fail authentication or onboarding. | Confirm each provider and choose NAT Gateway/firewall/public callback DNS. | NO | Vendor account requirements are external. | |
| 275 | | 90 | AZURE_READY | `scripts/validate_azure_readiness.py`, tests | CI validation seam | No single Azure static gate existed. | A future pipeline could deploy invalid charts. | Provide a read-only test/render command. | YES | It performs no cluster or Azure operation. | |
| 276 | | 91 | MISSING_AZURE_DEPLOYMENT_SEAM | repository CI/CD and Terraform | federated build/release and state | No CI platform or remote Terraform state backend exists. | Builds, ACR pushes, policy checks, approvals, locking, and shared state recovery remain manual. | Choose CI, configure OIDC federation, secured remote state, immutable promotion, and deployment approval. | NO | Adding provider/account-specific workflow and backend identifiers without the selected platform would be speculative. | |
| 277 | | 92 | BLOCKED_EXTERNAL_DECISION | Terraform AKS/networking | Entra administration/private control plane | Private-cluster and local-account controls are exposed but disabled. | The default module alone is not an approved production landing zone. | Supply Entra admin groups, private DNS/connectivity, policy, and reviewed variables. | NO | Landing-zone and operator-access decisions are required. | |
| 278 | | 93 | AZURE_READY | Helm `mcpGateway` seam | future MCP gateway | 5A server code is absent. | Implementing it now would invent product behavior. | Reserve identity, secret, OTEL, scale, policy, and internal exposure settings. | YES | No public resource is rendered. | |
| 279 | | 94 | MISSING_AZURE_DEPLOYMENT_SEAM | future MCP implementation | MCP state/egress/auth | No handler, protocol server, auth, or provider allowlist exists. | It cannot be deployed safely yet. | Implement 5A, threat-model it, and validate internal-only behavior. | NO | Infrastructure readiness does not substitute for the application. | |
| 280 | | 95 | AZURE_READY | Helm feature flags/OTEL/secret surfaces | future LLM seam | No production LLM integration is active. | Accidental enablement could expose private data or create unbounded cost. | Keep disabled and use existing secret/telemetry/egress seams later. | YES | Azure requires deliberate enablement. | |
| 281 | | 96 | BLOCKED_EXTERNAL_DECISION | future LLM architecture | provider, data policy, quotas | Provider/model/residency and prompt retention are undecided. | Private financial context may leave approved boundaries. | Choose provider, privacy contract, quotas, redaction, and egress before implementation. | NO | This is a product, privacy, and procurement decision. | |
| 282 | | 97 | AZURE_READY | Helm and Terraform values | cost controls | Resource/replica/retention knobs were incomplete. | Low-use Azure environments could be unnecessarily expensive. | Expose small pools, replicas, HPA bounds, service flags, polling, TTL, and node size. | YES | Defaults remain modest and optional services are off. | |
| 283 | | 98 | AZURE_READY | `scripts/scan_repository_secrets.py` | repository secret scan | No dependency-free, value-safe scan existed. | Cloud logging/release could expose committed credentials. | Scan likely keys and high-confidence formats without printing values. | YES | All findings are redacted and categorized. | |
| 284 | | 99 | LOCAL_ONLY_BUT_ACCEPTABLE | `platform.ps1`, `services/deploy-ibkr-3g.ps1`, `services/deploy-ibkr-3h.ps1` | local deployment scripts | Scripts contain k3d imports and imperative kubectl/Helm/Terraform operations. | Running them against production would bypass a release pipeline. | Keep documented for local/manual development; never use as Azure CI. | NO | This audit did not execute or rewrite authorized developer workflows. | |
| 285 | | 100 | AZURE_READY | Java `Clock.systemUTC`, Python timezone-aware code, structured logs | time handling | Market calendars need regional zones while infrastructure should be UTC. | Host-local timezone can shift schedules and evidence timestamps. | Use UTC internally and explicit market timezones. | NO | Audited active code follows that pattern. | |
| 286 | |
| 287 | ## Exact blockers before the first AKS deployment |
| 288 | |
| 289 | 1. Approve the Azure landing zone: subscription, region, names, tags, policies, budget, and resource ownership. |
| 290 | 2. Choose private-cluster/operator access, Entra Kubernetes RBAC admin groups, local-account policy, private DNS, and network connectivity. |
| 291 | 3. Select PostgreSQL, Redis-compatible, and optional Kafka services; define private endpoints, DNS, SKUs, TLS/auth modes, capacity, and connection budgets. |
| 292 | 4. Define database RPO/RTO, backup retention, restore ownership, and complete a restore rehearsal with Flyway history. |
| 293 | 5. Create service-specific or approved shared Workload Identities, federated subjects, Key Vault objects, and least-privilege RBAC. No credential value belongs in Git. |
| 294 | 6. Select ingress, public DNS, certificate issuance/renewal, WAF/rate limiting, trusted origins, and the final callback/public host names. |
| 295 | 7. Select a stable egress design and verify provider allowlists, licensing, quotas, terms, callback requirements, and firewall/FQDN rules. |
| 296 | 8. Implement distributed dedupe/state before scaling portfolio, research, or broker services beyond the single-replica constraints in the Azure overlay. |
| 297 | 9. Establish schema-first release ordering and backward-compatible migration review in the chosen CI/CD system. |
| 298 | 10. Choose the CI platform and configure federated Azure login, encrypted/locked remote Terraform state, image build/scan/SBOM/signing, immutable ACR promotion, validation gates, approval, and rollback. |
| 299 | 11. Select and deploy OpenTelemetry instrumentation/collector/export, sampling, privacy filtering, retention, dashboards, and alerts. |
| 300 | 12. Decide JWT rotation behavior and configure the production SMTP provider and credentials. |
| 301 | 13. Approve a legal, immutable IBKR Gateway package distribution model before enabling IBKR on AKS. |
| 302 | 14. Implement a TTL object-storage/session abstraction before asynchronous private uploads or report downloads are released. |
| 303 | 15. Move the remaining Java provider clients to centrally configured bounded connection pools/timeouts and validate provider-specific retry semantics. |
| 304 | 16. Perform capacity, failover, load, penetration, network-policy, and disaster-recovery tests in a non-production AKS environment. |
| 305 | |
| 306 | MCP and LLM are not blockers while disabled. They become blockers if included in the first release and must meet the controls described above before enablement. |
| 307 | |
| 308 | ## Validation record |
| 309 | |
| 310 | The audit uses static operations only: |
| 311 | |
| 312 | - `python scripts/validate_azure_readiness.py` |
| 313 | - `helm lint infrastructure/helm/ai-investment-platform -f .../values-dev.yaml` |
| 314 | - `helm lint infrastructure/helm/ai-investment-platform -f .../values-azure.yaml` |
| 315 | - `terraform fmt -check -recursive infrastructure/terraform` |
| 316 | - `terraform validate -no-color` from `infrastructure/terraform/environments/prd` |
| 317 | - Java Maven reactor tests |
| 318 | - research-engine and IBKR connector pytest suites |
| 319 | - frontend unit tests, lint, and production build |
| 320 | - `docker build --check` for every Dockerfile |
| 321 | - `python scripts/scan_repository_secrets.py` |
| 322 | |
| 323 | | Validation | Result | |
| 324 | |---|---| |
| 325 | | Functional tests | 1,128 passed: Java 339, research engine 604, IBKR connector 83, frontend 102; zero failures/errors/skips | |
| 326 | | Azure static assertions | 30 passed | |
| 327 | | Helm render | LOCAL 49 YAML documents; AZURE 40; 14 Azure application Deployments; Kafka-enabled Azure variant also rendered | |
| 328 | | Helm lint | LOCAL and AZURE passed; only the informational missing-chart-icon recommendation | |
| 329 | | Terraform | recursive format check passed; PRD configuration validation passed | |
| 330 | | Frontend | lint passed with 0 errors and 3 existing exhaustive-deps warnings; production build passed with 5 routes | |
| 331 | | Python bytecode compile | four AI application packages and the IBKR connector passed | |
| 332 | | Dockerfile validation | 15 of 15 passed `docker build --check`; full image builds and pushes were not performed | |
| 333 | | Secret scan | 208 candidate lines reviewed: 0 suspected secrets, 39 placeholders, 102 secret references, 33 test fixtures, 1 documentation example, 33 false positives | |
| 334 | |
| 335 | The Python suites each emit one upstream Starlette/httpx deprecation warning. These do not affect correctness but should be addressed during the dependency upgrade. The three frontend lint warnings concern existing `react-hooks/exhaustive-deps` dependencies and are not Azure-specific. |
| 336 | |
| 337 | No `kubectl`, Helm install/upgrade, Azure CLI deployment, Terraform plan/apply, Bicep deployment, container push, or database mutation is part of this validation record. |