Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Operations

Explanation

Environments and promotion

How a Cluster ERP release moves from a workstation through continuous integration and a staging node to a production node, and what enforces each gate.

Cluster ERP has four environments: the development workstation, continuous integration, a staging node, and a production node. A release reaches production only after the same tag ran on staging. This page explains the chain and the code that enforces it in deploy/install.sh, .github/workflows/ci.yml, and src/cluster_erp/server/staging_fixtures.py. Step-by-step installation is in install a Linux node; the CI jobs are in CI pipeline and release.

In short

  • A node is installed with deploy/install.sh --tag <tag> --domain <domain> --role staging|production; the role is mandatory (deploy/install.sh:77-83).
  • A production install refuses a tag that has no annotated promotion tag staging-ok/<tag> on the same commit (deploy/install.sh:101-107).
  • A node records its role at the first install and refuses any other role later (deploy/install.sh:122-128).
  • Continuous integration runs lint, lock, and test jobs on every push, and a deploy job on version tags that waits for approval in the production environment (.github/workflows/ci.yml:25-28, .github/workflows/ci.yml:126-136).
  • The browser battery is not part of continuous integration; it runs against the staging node before the promotion tag is set (.github/workflows/ci.yml:21-22).

How it works

The chain has five gates. The diagram shows them in order.

flowchart LR
  dev["Workstation"] --> ci["CI jobs lint, lock, test"]
  ci --> tag["Version tag v*"]
  tag --> stg["install.sh --role staging"]
  stg --> battery["_run_battery.mjs on staging"]
  battery --> ok["Tag staging-ok/v*"]
  ok --> prod["install.sh --role production"]
  tag --> deploy["CI deploy job, approval"]
  deploy --> prod

The same content in prose:

  1. A developer works on a workstation. On Windows the server runs as one process whatever server_workers says (src/cluster_erp/server/main.py:27-35).
  2. Every push and pull request runs the lint, lock, and test jobs (.github/workflows/ci.yml:37-124).
  3. A version tag v* is pushed. The workflow runs the same gates and then the deploy job (.github/workflows/ci.yml:126-129).
  4. The operator installs the tag on the staging node with --role staging. A staging install needs no promotion record (deploy/install.sh:101, tests/test_install_role_gate.py:145).
  5. The browser battery runs against the staging node. The runner web-client/_run_battery.mjs runs every _e2e_*.mjs suite in sequence and exits non-zero when one suite fails or prints no result (web-client/_run_battery.mjs:1-20). Suites that need a second PostgreSQL server or local processes are left out with --exclude (web-client/_run_battery.mjs:35-38).
  6. When the battery passes, the operator records the promotion as a second annotated tag staging-ok/<tag> on the same commit and pushes it (deploy/install.sh:15-21).
  7. The production install (by hand or by the CI deploy job) resolves both tags on the remote. It stops when the record is missing or points at another commit (deploy/install.sh:85-107).

Node role

At the first install the script writes the role into the file named by ROLE_FILE (deploy/install.sh:58, deploy/install.sh:122-128). A later install with a different role stops with the message that a node never changes role. The staging test accounts are created only on a node whose recorded role is staging (src/cluster_erp/server/staging_fixtures.py:92-105). See demo and staging data.

Approval of the deploy job

The deploy job is bound to the GitHub environment production and to a concurrency group that never cancels a running deploy (.github/workflows/ci.yml:131-136). The workflow header states that the environment is configured with required reviewers, so a person approves each deploy in the Actions screen (.github/workflows/ci.yml:7-12). The reviewer setting itself lives in the repository settings: Not determined from code. The job runs install.sh --role production on the node, so the promotion gate of step 7 applies to it too (.github/workflows/ci.yml:155-156).

Why it is built this way

  • "production runs a tagged release, never a branch tip" (deploy/install.sh:77).
  • "a release reaches production only through staging" (deploy/install.sh:81).
  • "a node keeps the role of its first install; staging data never meets production" (deploy/install.sh:122).
  • "a tag alone never reaches a customer node" (.github/workflows/ci.yml:9-10).

Rules and boundaries

  • The promotion gate is tested by tests/test_install_role_gate.py (seven tests, counted as def test_ lines).
  • An install is reported live only after /ready answers HTTP 200 and the residency check passes (deploy/install.sh:247-259). See security.txt and readiness and data residency check.
  • The production install takes a backup before it migrates (deploy/install.sh:214-226).

Interaction with other parts

Limits and open points

  • Continuous integration waives the live guards with CLUSTER_SKIP_LIVE_TESTS=1 (.github/workflows/ci.yml:18-20, .github/workflows/ci.yml:99).
  • Which hosts form the staging and production nodes is deployment data and is omitted under the security disclosure policy.
  • The list of past promotions is kept outside the wiki and is not reproduced here.

Terms introduced

Term Code identifier Definition Turkish term Source
promotion record staging-ok/<tag> An annotated tag on the release commit that records a passed staging battery. terfi kaydı deploy/install.sh:15-21
node role ROLE_FILE The role, staging or production, written at a node's first install and never changed. düğüm rolü deploy/install.sh:122-128
battery _run_battery.mjs The sequential run of every browser e2e suite against one server. batarya web-client/_run_battery.mjs:1-20

Verified against commit 92f8051 on 2026-11-13.

Verified against commit 92f8051 · 2026-11-13