Technology Stack Matrix 2026
The single source of truth for every version pin in the codebase. When in doubt about which version of X to use, this doc is canonical. Refresh every 6 months (April + October). No engineer pins a version against this matrix without a written ADR.
Contents
1 — How to read this doc
Each row in the matrix has:
| Column | Meaning |
|---|---|
| Component | What it is, with the role in our stack |
| Pinned | The version we ship with today |
| Latest stable | What the upstream project considers stable as of refresh date |
| Why this pin | Reason we are not on latest (stability, EOL window, transitive risk) |
| EOL / runway | When this pin is no longer supported upstream |
| Migration path | What we plan to bump to when the pinned version goes EOL |
Rule. If a row says “pinned = latest stable”, no migration plan is needed yet. If they diverge, the Migration path column is mandatory.
2 — Pinning policy
- Pin everything that ships in production. No
latesttags in any Dockerfile, no unpinned PyPI deps in anyrequirements.txt, no version-floating in any Helm chart. - Bump in cohorts, not one-off. When this matrix is refreshed (April + October), all bumps land in a single PR. No piecemeal bumps in the middle of a wave.
- Major-version bumps need an ADR. A minor or patch bump can land via PR review; a major bump (e.g. Python 3.12 → 3.13, Postgres 17 → 18) needs an ADR documenting blast radius + migration plan.
- Edge ≠ cloud. The Edge Gateway box runs leaner versions (e.g. Python 3.12 not 3.13) because of RAM constraints. Cloud-side components run the latest reasonable.
- No bleeding-edge in customer-facing code. Ratified-but-not-yet-widely-supported specs (e.g. Iceberg v3, Sparkplug B 4.0) wait until at least 2 of our consumed engines support them.
3 — The matrix
3.1 — Protocols & specs (the contracts)
| Component | Pinned | Latest stable | Why this pin | EOL / runway | Migration path |
|---|---|---|---|---|---|
| Sparkplug B spec | 3.0 (ISO/IEC 20237) | 3.0 (ratified) | The protocol every domain agrees on. v4 in dev but not stabilized. | No EOL — international standard | Watch v4.0 in 2027; do not adopt until reference implementations exist |
| MQTT | 3.1.1 + 5.0 dual mode | 5.0 | Sparkplug B 3.0 mandates MQTT 3.1.1 baseline; we accept 5.0 features | n/a | Stay |
| OPC UA | 1.05 client side | 1.05 | Industry standard; backwards-compatible | n/a | Watch 1.06 |
| Modbus | TCP + RTU | n/a | Spec is ancient and stable | n/a | n/a |
| InfluxDB line protocol | v2 | v2 | What InfluxDB v2 + Telegraf use | n/a | If we move to TimescaleDB, line protocol becomes irrelevant — INSERT SQL |
3.2 — Brokers
| Component | Pinned | Latest stable | Why this pin | EOL / runway | Migration path |
|---|---|---|---|---|---|
| Mosquitto (edge) | 2.1.2 | 2.1.2 | Latest patch; edge box uses single-node Mosquitto for buffer + persistence | 2.x line, ~3 years runway | Stay; consider EMQX for edge in Wave 5 if customer demands clustering |
| EMQX (cloud, Wave 6+) | 5.8.6 (LTS, last fully OSS) | 6.2.0 (BSL 1.1) | License decision — see §4.1 | 5.8.x EOL Aug 27, 2027 | Either accept BSL on 6.x or migrate to NanoMQ / HiveMQ Community by mid-2027 |
3.3 — Languages & runtimes
| Component | Pinned | Latest stable | Why this pin | EOL / runway | Migration path |
|---|---|---|---|---|---|
| Python (cloud services) | 3.13.x | 3.13.x | Mature; full support through Oct 2028; PEP 703 free-threading + JIT in experimental | EOL Oct 2028 | Plan 3.14 in Q4 2027 |
| Python (Edge box) | 3.12.x | 3.13.x | 3.13 has ~20 % higher RAM at idle — Edge has tight RAM budget | EOL Oct 2028 | Re-evaluate when 3.14 ships with RAM optimizations |
| Go | 1.25.x | 1.25.x | k3s 1.35 builds with 1.25; aligns with driver SDK | 1.x line stable | Track quarterly |
| Node.js | 22 LTS | 24 (current) | Next.js 15 + React 19 supported; LTS = production-safe | Apr 2027 | Bump to 24 LTS (Oct 2026) when entering active LTS |
3.4 — Drivers (Python — Wave 2-4)
| Component | Pinned | Latest stable | Why this pin | EOL / runway | Migration path |
|---|---|---|---|---|---|
| paho-mqtt | 2.1.0 | 2.1.0 | Already pinned; supports MQTT 5.0 + new callback API | Active | Stay |
| protobuf (Python runtime) | 5.28.3 | 7.34.1 | 5.28.3 works; 7.x is a major-version jump that broke MessageFactory.GetPrototype (we have a cross-version helper) | Active | Q3 2026 — bump to 7.x in cohort with minor refactor |
| pymodbus | 3.13.0 | 3.13.0 | Wave 2 | Active | Track quarterly |
| python-snap7 | 3.0.0 | 3.0.0 | Wave 3 — pure-Python rewrite; eliminates the C snap7 library from our Docker image | Active | Stay; massive Docker simplification win |
| asyncua | 1.1.8 | 1.1.8 | Wave 4; OPC UA client | Active | Track quarterly |
| libplctag (C, Allen-Bradley) | 1.5.x (Phase 2) | 1.5.x | Phase-2 backlog driver per PLC Driver Coverage Matrix | Active | Stay when adopted |
3.5 — Storage
| Component | Pinned | Latest stable | Why this pin | EOL / runway | Migration path |
|---|---|---|---|---|---|
| InfluxDB v3 Core (default for new work) | 3.x | 3.x | Default time-series engine for new code — DataFusion-based; native SQL + InfluxQL; lifts the v2 cardinality ceiling that hurts UNS workloads (many tags × many devices). See §4.7. | Active | Track quarterly |
| InfluxDB v2 (notqin Wave 0 — legacy) | 2.7.x | 3.x | notqin Wave 0 stack already on 2.7 with Telegraf json_v2 config + Flux dashboards. Migrating mid-Wave-1 would rewrite all dashboards. NOTQIN carries v2 through Wave 5; Wave 6 cuts over to v3 Core (or TimescaleDB). | v2.x not formally EOL but feature-frozen direction | Wave 6 migration target — see §4.7 |
| TimescaleDB (DigiTPME prod) | 2.26.0 | 2.26.0 | Per PRD — SIBD-1 Industrial Data Fabric — chosen for Postgres compatibility + RLS-driven multi-tenancy | Active | Track quarterly |
| PostgreSQL (TimescaleDB host + app DB) | 17.x | 18.3 | 18 has 3× I/O improvements but is too new (Feb 2026 release) — prod waits 6 months on Postgres majors | 17 EOL Nov 2029 | Q4 2026 — bump to 18 in cohort |
| Apache Iceberg | v2 spec | v3 spec (ratified) | v3 not yet supported by all consumed engines (Trino, Athena, Snowflake all preview-only) — see §4.3 | v2 not deprecated | Q3 2027 — re-evaluate v3 |
| MinIO (object store) | ⚫ Out — replaced by SeaweedFS | RELEASE.2026-04-11 (AIStor enterprise only) | MinIO archived community binaries Apr 25, 2026 — see §4.2 | Source-only on AGPLv3 | SeaweedFS is our default S3-compatible object store from Wave 6 onward |
| SeaweedFS | 3.x | 3.x | Replaces MinIO for our open-architecture pitch | Active | Track |
| DuckDB (ad-hoc analytics) | 1.5.2 | 1.5.2 | Embedded analytics on Iceberg; stable production status | Active | Track quarterly |
3.6 — Time-series ingest & visualization
| Component | Pinned | Latest stable | Why this pin | EOL / runway | Migration path |
|---|---|---|---|---|---|
| Telegraf (Wave 0 fallback) | 1.32.x | 1.38.x | notqin Wave 0 path; not the primary ingest in Wave 1+ (we own the Sparkplug B decoder) | n/a | Will deprecate when fully migrated to UNS |
| Grafana | 12.4.x | 13.0.1 | 13 just shipped at GrafanaCON Apr 2026 — give 6 months in production. 12.4 still gets security + critical patches | 12.x active until ~Q4 2026 | Q4 2026 — bump to 13 LTS once stabilized |
3.7 — Edge / Cloud orchestration
| Component | Pinned | Latest stable | Why this pin | EOL / runway | Migration path |
|---|---|---|---|---|---|
| k3s (edge) | v1.34.x | v1.36.x | k8s 1.34 EOL Oct 2026 — gives us a 6-month migration window | EOL Oct 2026 | Q3 2026 — bump to 1.36 in cohort |
| Kubernetes (cloud) | 1.34.x | 1.36.x | Same logic as k3s | EOL Oct 2026 | Q3 2026 — bump to 1.36 |
| Docker | 27.x | 28.x | Compose v2 syntax; engines stable | Active | Track quarterly |
| ArgoCD | 3.2.10 | 3.2.10 | Rolling release; only top-3 minors get patches | n/a | Track quarterly |
3.8 — Backend / API / web
| Component | Pinned | Latest stable | Why this pin | EOL / runway | Migration path |
|---|---|---|---|---|---|
| FastAPI | 0.136.1 | 0.136.1 | Pin specific version per FastAPI prod guidance | Active | Track quarterly |
| uvicorn / gunicorn | uvicorn 0.32 | 0.32 | Production ASGI | Active | Track |
| Next.js | 15.x | 15.x | React 19 stable, App Router, Turbopack stable for dev | Active LTS | Watch 16 release for late 2026 |
| React | 19.x | 19.x | Latest stable; Next.js 15 native support | Active | Track |
| Auth.js (NextAuth.js) | 5.x | 5.x | Wave 6 multi-tenant auth | Active | Track |
3.9 — Stream processing (existing Flink job)
| Component | Pinned | Latest stable | Why this pin | EOL / runway | Migration path |
|---|---|---|---|---|---|
| Apache Flink | 1.20.x (notqin existing) | 2.2.0 | Bearing-anomaly job runs on 1.20 in notqin; 2.x is a major rewrite | 1.20 LTS through 2026 | notqin Q4 2026 — bump cohort with the rest of the matrix |
| Java (Flink runtime) | 17 LTS | 21 LTS | Flink 1.20 supports both | Active | Track with Flink |
3.10 — Security supply chain
| Component | Pinned | Latest stable | Why this pin | EOL / runway | Migration path |
|---|---|---|---|---|---|
| cosign | 3.0.2 | 3.0.2 | Production-ready bundle format | Active | Track |
| SOPS | 3.12.2 | 3.12.2 | Secrets in Git, age + KMS-friendly | Active | Track |
| Syft (SBOM) | latest in CI | latest | SBOM gen on every release | Active | Track |
| Trivy / Grype (vuln scan) | latest in CI | latest | CI gate on critical CVEs | Active | Track |
| age (encryption) | 1.2.x | 1.2.x | SOPS dependency | Active | Track |
3.11 — Observability
| Component | Pinned | Latest stable | Why this pin | EOL / runway | Migration path |
|---|---|---|---|---|---|
| Prometheus | 3.x | 3.x | Standard | Active | Track |
| Loki | 3.x | 3.x | Standard | Active | Track |
| OpenTelemetry SDK (Python) | 1.x | 1.x | If/when we add tracing | Active | Track |
4 — Strategic decisions log
These are non-trivial decisions with cross-domain impact. Each one needs an ADR with the disagree-and-commit notes preserved.
4.1 — EMQX licensing: 5.8 LTS (open) vs 6.x BSL
Issue. EMQX 5.9+ moved to Business Source License 1.1. The last fully Apache-2.0 LTS is 5.8.x with EOL August 2027. EMQX 6.x is BSL but transitions to Apache 2.0 after a “Change Date” (typically 4 years).
Our open-architecture pitch (per 01 — Positioning, Audience, Brand Voice) explicitly criticizes vendor lock-in. Customers will ask why we ship a BSL component.
Options.
| Option | Pros | Cons |
|---|---|---|
| A. Pin EMQX 5.8 LTS | True open source (Apache 2.0); 18-month runway | Forced migration before Aug 2027 |
| B. Adopt EMQX 6.x BSL | Latest features; future Apache transition | BSL contradicts the pitch; customers may push back |
| C. Migrate to NanoMQ | Lightweight, fully open, edge-friendly | Less proven at cloud cluster scale |
| D. HiveMQ Community Edition | Apache 2.0, mature | No native cluster in CE; clustering is paid |
Decision (locked). Option A — pin EMQX 5.8 LTS for cloud broker through July 2027. Track NanoMQ as the migration target. Re-evaluate at Q1 2027.
4.2 — Object storage: MinIO out, SeaweedFS in
Issue. MinIO archived its community binary releases on April 25, 2026. The community AGPLv3 source remains, but pre-built Docker images are no longer published. The successor product (AIStor) is enterprise-only.
Implication. PRD — GI-1 Infrastructure & Platform specifies “MinIO or SeaweedFS”. We default to MinIO in many places. This needs to flip.
Decision (locked). SeaweedFS becomes the default S3-compatible object store from Wave 6 onward. MinIO is permitted only if a customer site already runs it; otherwise prefer SeaweedFS. Update PRD — GI-1 Infrastructure & Platform accordingly.
4.3 — Apache Iceberg v2 vs v3
Issue. Iceberg v3 spec is ratified (2024) but as of April 2026 only Snowflake (preview) supports it. Trino, Athena, Spark, and Flink either don’t support v3 or have only experimental flags.
Decision (locked). Stay on Iceberg v2 through 2026. Re-evaluate Q3 2027 when 3+ of our consumed engines have stable v3 support. The v3 features (row lineage, native encryption, semi-structured data) are nice-to-haves, not blockers.
4.4 — Python 3.12 (edge) vs 3.13 (cloud)
Issue. Python 3.13 has roughly 20 % higher RAM use at idle than 3.12 due to PEP 703 free-threading scaffolding (even when unused). The Edge Gateway box has a tight RAM budget (1 GB image target).
Decision (locked). Edge runs Python 3.12; cloud runs 3.13. Both stay supported through Oct 2028. Re-evaluate when 3.14 ships with optimization (Oct 2026 GA).
4.5 — InfluxDB v2 (notqin) vs TimescaleDB (DigiTPME prod)
Issue. notqin is built on InfluxDB v2 + Telegraf. The DigiTPME production PRDs (PRD — SIBD-1 Industrial Data Fabric) specify TimescaleDB.
Decision (locked). notqin Wave 0 stays on InfluxDB v2.7 through Wave 5 to avoid mid-flight dashboard rewrites. Wave 6 migrates notqin to InfluxDB v3 Core or TimescaleDB — see §4.7. The DigiTPME production stack uses TimescaleDB. The two stacks share contracts (Sparkplug B + UNS topic tree) but not storage.
4.7 — InfluxDB v2 → v3 Core migration (or jump to TimescaleDB?)
Issue. InfluxDB v3 Core (Apache 2.0, OSS) launched May 2025 and is the latest stable as of 2026. It uses the DataFusion (Apache Arrow) engine, supports SQL + InfluxQL natively, and removes the cardinality ceiling that v2’s TSM engine has. As of May 27, 2026, the latest Docker tag for influxdb points to v3 Core.
Pinning notqin to v2 in 2026 is intentionally trailing two major versions. That’s the opposite of “minimize upgrade pain later,” and is hard to defend without a written reason.
Why v2 still in Waves 1-5.
- notqin Wave 0 already runs v2.7 with Telegraf
json_v2parser config + Grafana dashboards written against Flux. Re-querying every dashboard mid-Wave 1 would consume the wave. - The Wave 1 UNS bridge writes to v2 via the v2 line-protocol HTTP API, which is the same wire format as v3 (line protocol is forward-compatible). So our decoder code requires zero changes when the database flips.
- v3 Core OSS shipped without HA/clustering at GA (clustering is in Enterprise). For the notqin single-node lab that’s irrelevant; for cloud production we already use TimescaleDB.
Why we cut over in Wave 6.
- Wave 6 is “Cloud broker bridge + multi-tenancy” — the right wave to also bring in a production-grade time-series engine.
- By Wave 6, the team has v3-or-TimescaleDB hands-on experience to draw from.
- v2 will increasingly be feature-frozen; staying past Wave 6 is the path to upgrade pain.
Three candidates for Wave 6 cutover.
| Option | Pros | Cons |
|---|---|---|
| A. InfluxDB v3 Core (OSS) | Latest stable; lifts cardinality ceiling; native SQL; same line-protocol writes (decoder unchanged); Telegraf swap is config-only | No HA in OSS (Enterprise only); we’d still need TimescaleDB for DigiTPME prod — two engines |
| B. TimescaleDB straight away | One time-series engine across notqin + DigiTPME; battle-tested; Postgres tooling; RLS for multi-tenancy | Bigger migration than v3 (decoder rewrite from line protocol → SQL); learning curve for the SIBD team |
| C. Run both side-by-side in Wave 6 | Compare; let evidence decide | Operational overhead; slows Wave 6 |
Decision (locked). Option A — Wave 6 cuts notqin over to InfluxDB v3 Core. Reasoning:
- The decoder already speaks line protocol; the cutover is a Telegraf + Grafana datasource flip, no code change.
- v3 Core gives the team v3 hands-on before DigiTPME prod hits scale.
- TimescaleDB remains the DigiTPME production engine for multi-tenancy + RLS reasons; running both is intentional dual-engine experience.
- If v3 Core’s no-HA-in-OSS becomes a problem at scale, the migration to TimescaleDB is well-understood by then.
Wave 6 deliverables to add.
- InfluxDB v3 Core deployed as a sibling service to v2.7
- Decoder writes both for 1 week (parity check)
- Grafana dashboards converted to v3 datasource (SQL queries)
- v2.7 retired once parity green for 7 days
- PROTOTYPE_TO_PRODUCTION_MAPPING updated to reflect the cutover
4.6 — Custom Sparkplug B encoder vs tahu Python lib
Issue. Wave 1 implemented the Sparkplug B encoder hand-rolled on top of protobuf because no clean PyPI binding existed at the time of Wave 1.
Decision (locked). Keep the hand-rolled encoder. It is small (~150 LOC), has no transitive deps beyond protobuf, and gives us full control of alias / sequence handling. Eclipse Tahu Python bindings can be considered when they reach PyPI as a maintained package.
5 — Upgrade calendar (rolling)
Reviewed and refreshed every April + October.
| Quarter | Action |
|---|---|
| Q2 2026 (now) | Lock matrix v1; deploy Wave 1 with current pins. All new code targets InfluxDB v3 Core (notqin Wave 0 stack remains on v2.7 until Wave 6 cutover). |
| Q3 2026 (Jul–Sep) | Cohort bump: protobuf 5 → 7; k3s 1.34 → 1.36; Postgres 17 → 18 (test). |
| Q4 2026 (Oct–Dec) | Grafana 12 → 13; Postgres 18 in production; Node.js 24 LTS bump; matrix refresh. Wave 6: notqin InfluxDB v2.7 → v3 Core cutover (see §4.7). |
| Q1 2027 | EMQX 5.8 → migration target evaluation (NanoMQ vs 6.x BSL); Python 3.14 candidate review. |
| Q2 2027 | Final EMQX 5.8 migration before Aug 2027 EOL. |
| Q3 2027 | Iceberg v2 → v3 re-evaluation. |
| Q4 2027 | Cohort bump; matrix refresh. |
6 — How to use this doc
When you start a wave
- Open this doc.
- Pin every new dep against the matrix.
- If a row says “track quarterly” and you want to bump, file an ADR.
When you write a Dockerfile
- Use the Pinned version, not
latest. - Cross-link to the matrix in a comment if the version is unusual.
When a new component is needed (not in the matrix)
- Don’t pin in code first.
- Add a new row to the matrix (PR), with the Why this pin + EOL fields filled.
- Then pin in code.
When this matrix is refreshed
- Re-run the cohort bump in a single PR.
- Update the ADRs for any major-version bumps.
- Tag the PR
tech-stack-refresh-YYYY-MM.
7 — Sources (April 2026 refresh)
- Sparkplug 3.0 specification — Eclipse Foundation
- paho-mqtt 2.1.0 release notes
- protobuf Python package — PyPI
- EMQX EOL policy
- EMQX 5.8 EOL notice
- Mosquitto 2.1.2 release
- InfluxDB endoflife.date
- TimescaleDB releases
- Grafana endoflife.date
- Telegraf releases
- pymodbus on PyPI
- python-snap7 3.0 docs
- asyncua on PyPI
- k3s release notes
- PostgreSQL 18 release announcement
- Python 3.13 release
- Next.js 15 release
- FastAPI releases
- Apache Iceberg V3 status
- MinIO archive notice
- Apache Flink downloads
- cosign 3.0.2 release
- SOPS v3.12.2
- ArgoCD endoflife.date
- DuckDB 1.5.2 announcement