Skip to content

0.4.0

Reference. What this release does, what it does not, and what is simulated.

Released: 2026-10-16, on PyPI 2026-10-17. Previous: 0.3.1, on PyPI 2026-10-15.


What 0.4.0 is

A plant on two boxes keeps counting when one of them dies.

Two copies of one plant, on two addresses, against one PostgreSQL. Pull the plug on the box that is reading the machines and the other one has the job a lease or two later: the dashboard on the survivor keeps answering, the counts carry on from where they were rather than starting over, nobody signs in again, and no reading is booked twice.

It is three pull requests on top of 0.3.1 — #177, #178 and #179 — and it is a minor rather than a patch because a plant's database gains a table and a pack may carry a key it could not carry before.

What a person types to see it

On a box with Docker and nothing else:

git clone https://github.com/factorysemantics/factorysemantics-mes
cd factorysemantics-mes
bash scripts/kill-a-node.sh

It brings up one PostgreSQL, the simulated machines, two nodes of the bottling plant and nginx in front of both on one address; it waits for the counts to move; then it kills a node outright — no shutdown, a power cut — and watches the one address until the other node holds the reader. On this project's own box on 2026-10-16 it printed: the reader was on the second node after 1.13 leases of 6 s, 0 of the requests asked during the failover were refused, all 6 machines booked exactly what their counters moved, and 0 readings were booked twice. The same script runs on every pull request in CI.

docker compose -f docker/docker-compose.redundant.yml up brings the same thing up without the kill, and http://localhost:8000/dashboard (ADMIN / admin) is the plant. The top line of the dashboard says which node is serving the page and which is reading the machines, and it follows a takeover on its own, within five seconds, with no reload.

Two nodes on one box

You do not need the second box to see it. A pack may name the nodes that run on this machine:

[[serve.nodes]]
id       = "a"
api_port = 8010

[redundancy]
enabled       = true
lease_seconds = 6

Then fsmes fleet start, stop, status, list and apply act on every node the pack names — including the one OPC replay both nodes read, which the plant starts before the first node and stops after the last — and fsmes fleet stop <plant> --node b takes one node down on purpose while the other keeps the plant up. fsmes pack check refuses the four ways a node list half-starts a plant before any of it is running. The whole recipe is one plant on two boxes.

A pack that names no nodes is the ordinary plant on one address, and nothing above changes it.

What 0.4.0 does not do

  • The PLC write path is still single. It is not built on one box, so it is not built on two.
  • SQLite is not redundant and is not meant to be. [redundancy] with a SQLite URL refuses in one sentence; the single-box fsmes demo bottling plant is untouched by all of this.
  • The database's own standby is PostgreSQL's job. This release is about not getting in its way; failing a database over is not in it.
  • A conversation on the AI tab starts over after a takeover. The chat's history lives in the node that answered, not in the plant's book.
  • ERP sync and the message publisher are one copy each. They are not leased roles yet: on two nodes, run them on one.
  • The kill is not proven on Windows. A Windows CI job starts, answers, holds and stops two nodes; killing a node there stops it by a different mechanism than a power cut, so the takeover measurements are from Linux only.
  • No real plant has run any of this, as in every release before it.

What is simulated

Everything that produces a number: a deterministic line simulator in src/fsmes/sim/, replayed over a real OPC UA server. Two nodes read the same replay, which is what makes "both of them booked the same counter" a question worth asking.

Upgrading from 0.3.1

pipx upgrade factorysemantics-mes   # or pip install --upgrade
fsmes --version                     # 0.4.0

One migration, b6f0c2a71d58 (0.3.1's head was d4e8a1c93b70): it adds plant_leases and counter_baselines. On a plant that runs on one box there is nothing to do beyond the upgrade — the two tables sit unused until [redundancy] is turned on — and one thing is better even there: a reader that restarts no longer books the counter's whole value as new production. The procedure is on upgrading between versions.