GitOps for the factory floor. A Kubernetes operator that reconciles industrial PLCs as first-class resources, with per-register remediation policies and a machine-checkable proof of behavior.
apiVersion: setpoint.io/v1
kind: IndustrialPLC
metadata:
name: line-1-printer-plc
spec:
deviceAddress: plc-1.factory.lan
port: 502
registers:
- name: conveyor-speed
address: 4001
desiredValue: 2500
remediation:
strategy: Auto
pollIntervalSecs: 5
- name: print-head-position
address: 4002
desiredValue: 1200
remediation:
strategy: Alert # detected, not auto-corrected
pollIntervalSecs: 5The desired state of every register is git-tracked YAML. The operator
polls the live device, detects drift, and applies the per-register
remediation policy. Auto silently writes the desired value back;
Alert emits a Warning DriftDetected event and bumps a metric
but does not write; Halt marks the resource Failed.
The whole project is built around a single CI-checkable claim:
An operator that auto-corrects an Alert-policy register is broken, even if everything else looks fine.
Reproduce it locally:
make flagship-proof
cat artifacts/latest/proof.json
cat artifacts/latest/report.mdThis boots a kind cluster, deploys the operator + mock PLC, injects
deterministic drift on the Alert-policy register, and writes a
binary verdict: PASS | FAIL plus a human-readable report. The
same flow runs in CI on every PR; see
.github/workflows/e2e-proof.yml.
If kind is not installed, the proof script gracefully degrades
against the current kubectl context.
| Landing page | Evidence section |
![]() |
![]() |
| Control loop | YAML builder |
![]() |
![]() |
| Console | Flagship proof |
![]() |
![]() |
The screenshots above are static captures committed under
docs/screenshots/. For a live view, run the local demo
(see Flagship demo path below).
helm repo add setpoint https://apinzon.github.io/setpoint-operator
helm install setpoint setpoint/setpointkubectl apply -f k8s/crd.yaml
kubectl apply -f k8s/rbac.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f config/samples/industrialplc-line1.yamlThe sample targets an in-cluster setpoint-mock-plc service on
port 5502; deploy k8s/mock-plc.yaml first if you don't have real
hardware. Use k8s/deployment-local.yaml (imagePullPolicy: Never,
tag :latest) for local kind/minikube work.
The repo uses different toolchains depending on which part you are working on:
- Rust and Cargo for the workspace binaries
- Docker for local images and compose services
kubectland Helm for cluster packaging and deploymentjqfor proof artifact generationkindfor local end-to-end cluster runs (optional; the proof script falls back to the currentkubectlcontext whenkindis unavailable)- Node/npm for the
landing/Next.js app
ci-local.sh checks for cargo, docker, helm, and kubectl
up front, and skips some optional checks when trivy or kind
are not installed.
This is the path a reviewer can run end to end. Every command is
discoverable from Makefile, landing/package.json, or
scripts/flagship-proof.sh.
- Install JS deps and verify the frontend builds and typechecks:
npm --prefix landing install cd landing && npm exec -- tsc --noEmit npm --prefix landing run build
- Build and test the Rust workspace:
make fmt make lint make test make build - Lint and render the Helm chart:
make helm-lint make helm-template
- Start the local demo (operator + mock PLC + Axum API gateway on
port 8081 + Next.js dev server on port 3000):
make demo
- Open
http://localhost:3000/for the marketing page andhttp://localhost:3000/consolefor the live console. The console reports whether it is reading the Axum API or the bundled replica. - From a second terminal, exercise each per-policy drift injector:
make demo-drift-conveyor # Auto strategy: operator silently corrects make demo-drift-printhead # Alert strategy: detected, never written make demo-drift-halt # Halt strategy: IndustrialPLC marked Failed
- Tear down:
make demo-cleanup. - Reproduce the CI proof locally:
make flagship-proof. The verdict and artifacts land underartifacts/latest/.
From the repo root:
make build # cargo build --release --workspace
make build-debug # cargo build --workspace
make fmt # cargo fmt --all -- --check
make fmt-fix # cargo fmt --all
make lint # cargo clippy --workspace --all-targets -- -D warnings
make test # cargo test --workspace
make ci-local # local CI bundle
make helm-lint # helm lint charts/setpoint
make helm-template # render chart locally
make k8s-apply # apply raw manifests for local cluster work
make k8s-delete # remove raw manifests
make obs-up # start Prometheus + Grafana
make obs-down # stop Prometheus + Grafana
make demo # start local demo environment
make demo-cleanup # stop demo resources and local processes
make demo-drift-conveyor # inject drift into the Auto conveyor register
make demo-drift-printhead # inject drift into the Alert print-head register
make demo-drift-halt # trigger the Halt safety fault
make flagship-proof # run the proof end to end
make proof-cleanup # clean resources created by the proof
make proof-report # regenerate report from captured artifactsFor the landing app:
npm --prefix landing install
npm --prefix landing run dev
npm --prefix landing run build
npm --prefix landing run start
npm --prefix landing run lint
cd landing && npm exec -- tsc --noEmitFor the API gateway (used by the console demo):
cargo run -p api
curl http://localhost:8081/api/healthThere is no dedicated root-level JavaScript task runner. The TypeScript
typecheck command above is derived from the checked-in landing/tsconfig.json
and local typescript dependency.
The /console route is a typed Next.js client that talks to the
crates/api Axum gateway on http://localhost:8081. When the
gateway is offline, the console surfaces a "Demo data fallback"
banner and reads from a typed bundled replica so the layout is
still exercisable. Audit (/api/audit) and SSE
(/api/stream/events) responses are clearly labeled in the payload
(isMock: true, source: "mock") — they are demo data meant to
render the verification surface end to end while the operator
service is not colocated.
┌──────────┐ watch ┌─────────────┐ poll ┌──────────────┐
│ git / │ ──────▶ │ Setpoint │ ─────▶ │ Modbus PLC │
│ kubectl │ ◀────── │ operator │ ◀───── │ (registers) │
└──────────┘ patch └─────────────┘ write └──────────────┘
│
▼
┌──────────────────┐
│ setpoint_* │
│ Prometheus │
│ metrics │
└──────────────────┘
│
▼
┌────────────────────────┐
│ Warning DriftDetected │
│ / Normal │
│ DriftCorrected events │
└────────────────────────┘
The full architecture, including failure modes and reconciliation
loop, lives in docs/ARCHITECTURE.md.
crates/
operator/ the Kubernetes operator (reconciler + metrics)
setpointctl/ CLI (get-status, watch, sync)
mock-plc/ Modbus TCP server with optional chaos mode
drift-simulator/ overwrites a register on demand for the proof run
api/ Axum gateway used by the console (typed health, audit, events, simulate-policy)
k8s/ raw manifests (CRD, RBAC, deployment, sample, mock)
charts/setpoint/ Helm chart
config/samples/ reference IndustrialPLC resources
docs/ architecture, ADRs, executive summary, proof
artifacts/ proof run output (templates ship here; real run overwrites)
scripts/ flagship-proof.sh, capture-metrics.sh, generate-report.sh, aggregate-proof.sh
landing/
app/ marketing page and /console route
components/ marketing page sections (nav, hero, proof bento, etc.)
Generated or local-output directories that agents should usually leave alone:
target/artifacts/latest/landing/.next/landing/node_modules/
Files with generated or machine-output characteristics that agents should not edit unless the task requires it:
Cargo.locklanding/package-lock.jsonlanding/next-env.d.ts
| ADR | Decision |
|---|---|
| 001 | Use Rust + kube-rs for the operator |
| 002 | Target Modbus TCP first |
| 003 | Rename the project from FabGitOps to Setpoint |
- Executive summary — one-pager, non-technical
- Proof of concept — technical deep-dive on the proof run
- Live demo script — 5-minute demo walkthrough
- Architecture — system design
- Screenshots capture list — what to capture, how
The current CI contract is defined by:
.github/workflows/ci.ymlforfmt,clippy,cargo test, release build, and Helm lint/template checks.github/workflows/e2e-proof.ymlfor the kind-based flagship proof run
If you are making documentation-only changes, the cheapest meaningful verification steps are usually:
make fmt
make lint
make test
make helm-lint
npm --prefix landing run build
cd landing && npm exec -- tsc --noEmitRun make flagship-proof when the task affects the proof flow, manifests,
or behavior claims tied to the end-to-end demo.
- The working directory is
fabgitops, but the current product name and docs areSetpoint. - Some historical metadata still references older repo names. For example,
crates/api/Cargo.tomlpoints tohttps://github.com/AngelP17/fabgitopswhile the rest of the workspace mostly referenceshttps://github.com/apinzon/setpoint-operator. landing/.next/andlanding/node_modules/are currently present in the working tree and should be treated as generated artifacts, not source.- The console demo depends on the Axum gateway on
localhost:8081. When that gateway is offline, the console falls back to a typed bundled replica and labels the data accordingly. The audit ledger and SSE stream are bundled mock payloads in either case; the response payload includesisMock: trueandsource: "mock"so the console can honestly tell the difference. - Drift demo commands (
make demo-drift-conveyor,make demo-drift-printhead,make demo-drift-halt) only work aftermake demohas port-forwarded the in-cluster mock PLC to127.0.0.1:5502.
MIT. See LICENSE.





