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
  2. Pinning policy
  3. The matrix
  4. Strategic decisions log
  5. Upgrade calendar

1 — How to read this doc

Each row in the matrix has:

ColumnMeaning
ComponentWhat it is, with the role in our stack
PinnedThe version we ship with today
Latest stableWhat the upstream project considers stable as of refresh date
Why this pinReason we are not on latest (stability, EOL window, transitive risk)
EOL / runwayWhen this pin is no longer supported upstream
Migration pathWhat 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

  1. Pin everything that ships in production. No latest tags in any Dockerfile, no unpinned PyPI deps in any requirements.txt, no version-floating in any Helm chart.
  2. 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.
  3. 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.
  4. 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.
  5. 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)

ComponentPinnedLatest stableWhy this pinEOL / runwayMigration path
Sparkplug B spec3.0 (ISO/IEC 20237)3.0 (ratified)The protocol every domain agrees on. v4 in dev but not stabilized.No EOL — international standardWatch v4.0 in 2027; do not adopt until reference implementations exist
MQTT3.1.1 + 5.0 dual mode5.0Sparkplug B 3.0 mandates MQTT 3.1.1 baseline; we accept 5.0 featuresn/aStay
OPC UA1.05 client side1.05Industry standard; backwards-compatiblen/aWatch 1.06
ModbusTCP + RTUn/aSpec is ancient and stablen/an/a
InfluxDB line protocolv2v2What InfluxDB v2 + Telegraf usen/aIf we move to TimescaleDB, line protocol becomes irrelevant — INSERT SQL

3.2 — Brokers

ComponentPinnedLatest stableWhy this pinEOL / runwayMigration path
Mosquitto (edge)2.1.22.1.2Latest patch; edge box uses single-node Mosquitto for buffer + persistence2.x line, ~3 years runwayStay; 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.15.8.x EOL Aug 27, 2027Either accept BSL on 6.x or migrate to NanoMQ / HiveMQ Community by mid-2027

3.3 — Languages & runtimes

ComponentPinnedLatest stableWhy this pinEOL / runwayMigration path
Python (cloud services)3.13.x3.13.xMature; full support through Oct 2028; PEP 703 free-threading + JIT in experimentalEOL Oct 2028Plan 3.14 in Q4 2027
Python (Edge box)3.12.x3.13.x3.13 has ~20 % higher RAM at idle — Edge has tight RAM budgetEOL Oct 2028Re-evaluate when 3.14 ships with RAM optimizations
Go1.25.x1.25.xk3s 1.35 builds with 1.25; aligns with driver SDK1.x line stableTrack quarterly
Node.js22 LTS24 (current)Next.js 15 + React 19 supported; LTS = production-safeApr 2027Bump to 24 LTS (Oct 2026) when entering active LTS

3.4 — Drivers (Python — Wave 2-4)

ComponentPinnedLatest stableWhy this pinEOL / runwayMigration path
paho-mqtt2.1.02.1.0Already pinned; supports MQTT 5.0 + new callback APIActiveStay
protobuf (Python runtime)5.28.37.34.15.28.3 works; 7.x is a major-version jump that broke MessageFactory.GetPrototype (we have a cross-version helper)ActiveQ3 2026 — bump to 7.x in cohort with minor refactor
pymodbus3.13.03.13.0Wave 2ActiveTrack quarterly
python-snap73.0.03.0.0Wave 3 — pure-Python rewrite; eliminates the C snap7 library from our Docker imageActiveStay; massive Docker simplification win
asyncua1.1.81.1.8Wave 4; OPC UA clientActiveTrack quarterly
libplctag (C, Allen-Bradley)1.5.x (Phase 2)1.5.xPhase-2 backlog driver per PLC Driver Coverage MatrixActiveStay when adopted

3.5 — Storage

ComponentPinnedLatest stableWhy this pinEOL / runwayMigration path
InfluxDB v3 Core (default for new work)3.x3.xDefault 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.ActiveTrack quarterly
InfluxDB v2 (notqin Wave 0 — legacy)2.7.x3.xnotqin 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 directionWave 6 migration target — see §4.7
TimescaleDB (DigiTPME prod)2.26.02.26.0Per PRD — SIBD-1 Industrial Data Fabric — chosen for Postgres compatibility + RLS-driven multi-tenancyActiveTrack quarterly
PostgreSQL (TimescaleDB host + app DB)17.x18.318 has 3× I/O improvements but is too new (Feb 2026 release) — prod waits 6 months on Postgres majors17 EOL Nov 2029Q4 2026 — bump to 18 in cohort
Apache Icebergv2 specv3 spec (ratified)v3 not yet supported by all consumed engines (Trino, Athena, Snowflake all preview-only) — see §4.3v2 not deprecatedQ3 2027 — re-evaluate v3
MinIO (object store)Out — replaced by SeaweedFSRELEASE.2026-04-11 (AIStor enterprise only)MinIO archived community binaries Apr 25, 2026 — see §4.2Source-only on AGPLv3SeaweedFS is our default S3-compatible object store from Wave 6 onward
SeaweedFS3.x3.xReplaces MinIO for our open-architecture pitchActiveTrack
DuckDB (ad-hoc analytics)1.5.21.5.2Embedded analytics on Iceberg; stable production statusActiveTrack quarterly

3.6 — Time-series ingest & visualization

ComponentPinnedLatest stableWhy this pinEOL / runwayMigration path
Telegraf (Wave 0 fallback)1.32.x1.38.xnotqin Wave 0 path; not the primary ingest in Wave 1+ (we own the Sparkplug B decoder)n/aWill deprecate when fully migrated to UNS
Grafana12.4.x13.0.113 just shipped at GrafanaCON Apr 2026 — give 6 months in production. 12.4 still gets security + critical patches12.x active until ~Q4 2026Q4 2026 — bump to 13 LTS once stabilized

3.7 — Edge / Cloud orchestration

ComponentPinnedLatest stableWhy this pinEOL / runwayMigration path
k3s (edge)v1.34.xv1.36.xk8s 1.34 EOL Oct 2026 — gives us a 6-month migration windowEOL Oct 2026Q3 2026 — bump to 1.36 in cohort
Kubernetes (cloud)1.34.x1.36.xSame logic as k3sEOL Oct 2026Q3 2026 — bump to 1.36
Docker27.x28.xCompose v2 syntax; engines stableActiveTrack quarterly
ArgoCD3.2.103.2.10Rolling release; only top-3 minors get patchesn/aTrack quarterly

3.8 — Backend / API / web

ComponentPinnedLatest stableWhy this pinEOL / runwayMigration path
FastAPI0.136.10.136.1Pin specific version per FastAPI prod guidanceActiveTrack quarterly
uvicorn / gunicornuvicorn 0.320.32Production ASGIActiveTrack
Next.js15.x15.xReact 19 stable, App Router, Turbopack stable for devActive LTSWatch 16 release for late 2026
React19.x19.xLatest stable; Next.js 15 native supportActiveTrack
Auth.js (NextAuth.js)5.x5.xWave 6 multi-tenant authActiveTrack
ComponentPinnedLatest stableWhy this pinEOL / runwayMigration path
Apache Flink1.20.x (notqin existing)2.2.0Bearing-anomaly job runs on 1.20 in notqin; 2.x is a major rewrite1.20 LTS through 2026notqin Q4 2026 — bump cohort with the rest of the matrix
Java (Flink runtime)17 LTS21 LTSFlink 1.20 supports bothActiveTrack with Flink

3.10 — Security supply chain

ComponentPinnedLatest stableWhy this pinEOL / runwayMigration path
cosign3.0.23.0.2Production-ready bundle formatActiveTrack
SOPS3.12.23.12.2Secrets in Git, age + KMS-friendlyActiveTrack
Syft (SBOM)latest in CIlatestSBOM gen on every releaseActiveTrack
Trivy / Grype (vuln scan)latest in CIlatestCI gate on critical CVEsActiveTrack
age (encryption)1.2.x1.2.xSOPS dependencyActiveTrack

3.11 — Observability

ComponentPinnedLatest stableWhy this pinEOL / runwayMigration path
Prometheus3.x3.xStandardActiveTrack
Loki3.x3.xStandardActiveTrack
OpenTelemetry SDK (Python)1.x1.xIf/when we add tracingActiveTrack

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.

OptionProsCons
A. Pin EMQX 5.8 LTSTrue open source (Apache 2.0); 18-month runwayForced migration before Aug 2027
B. Adopt EMQX 6.x BSLLatest features; future Apache transitionBSL contradicts the pitch; customers may push back
C. Migrate to NanoMQLightweight, fully open, edge-friendlyLess proven at cloud cluster scale
D. HiveMQ Community EditionApache 2.0, matureNo 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_v2 parser 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.

OptionProsCons
A. InfluxDB v3 Core (OSS)Latest stable; lifts cardinality ceiling; native SQL; same line-protocol writes (decoder unchanged); Telegraf swap is config-onlyNo HA in OSS (Enterprise only); we’d still need TimescaleDB for DigiTPME prod — two engines
B. TimescaleDB straight awayOne time-series engine across notqin + DigiTPME; battle-tested; Postgres tooling; RLS for multi-tenancyBigger migration than v3 (decoder rewrite from line protocol → SQL); learning curve for the SIBD team
C. Run both side-by-side in Wave 6Compare; let evidence decideOperational overhead; slows Wave 6

Decision (locked). Option A — Wave 6 cuts notqin over to InfluxDB v3 Core. Reasoning:

  1. The decoder already speaks line protocol; the cutover is a Telegraf + Grafana datasource flip, no code change.
  2. v3 Core gives the team v3 hands-on before DigiTPME prod hits scale.
  3. TimescaleDB remains the DigiTPME production engine for multi-tenancy + RLS reasons; running both is intentional dual-engine experience.
  4. 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.

QuarterAction
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 2027EMQX 5.8 → migration target evaluation (NanoMQ vs 6.x BSL); Python 3.14 candidate review.
Q2 2027Final EMQX 5.8 migration before Aug 2027 EOL.
Q3 2027Iceberg v2 → v3 re-evaluation.
Q4 2027Cohort 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)