Verified on lin001 over SSH on 2026-08-20, read-only: - Port 502 is bound to 10.0.0.17, not 0.0.0.0, so unauthenticated Modbus is not internet-reachable at the Docker level. This was an open risk in §3 and an unchecked item in §15; it is now a confirmation. openplc-runtime also publishes 8443 (the Runtime web UI) on the same private address, which the spec did not mention. - openplc-runtime is not the only published port on the host: caddy, wireguard, mosquitto and chirpstack-gateway-bridge all publish on 0.0.0.0. The no-published-ports rule still applies in full to what we build, but the "one deliberate exception" framing was wrong and invited over-reading. - Port 22 is open to the internet. Added to §15 as an open item for the same NSG review. - 21 containers running, not 22; no stopped containers. - /datadisk is 46% used with InfluxDB at 55 GB, up from 43%/52 GB. Recorded the growth rate so it can be budgeted for. CLAUDE.md restates two of these rules and is updated to match. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4 KiB
CLAUDE.md — rules to keep front of mind
Plant Operations Assistant for the Waterloo Road Pump Station (WRPS), deployed onto an
existing, live, shared Docker host. Full detail: BUILD-AI-CONTAINERS.md (the build spec) and
YAU_Linux_Host_Onboarding.md (the host brief). Where the two conflict, the host brief wins.
The three lines this system does not cross
- No instructions for safety-critical actions. Procedural questions get document identity, revision, effective date and verbatim prerequisites — never synthesised steps.
- No recommended setpoints or operating parameters. Advisory questions get evidence, ranges, outcomes, documented limits, then an explicit deferral. Never a number presented as the answer.
- No answers outside the evidence. Zero rows means "no records found", never a plausible figure.
These are code paths, not prompt instructions. A safety rule living only in a prompt is not implemented. The classifier runs first; each class has a Pydantic contract validated in Python after generation and before returning. Contract failure -> regenerate once -> error. Never return.
When class is uncertain, choose the more restrictive class. Procedural beats Reference. Advisory beats Historical. Partly-advisory is advisory.
Host rules — inherited, non-negotiable
- Growing data goes on
/datadisk, never/. Root is 62 GB and has hit 100% before. - No published host ports for anything we build. Join the external
proxynetwork, reach it through Caddy. Some existing containers do publish (caddy,wireguard,mosquitto,chirpstack-gateway-bridge,openplc-runtime) — they carry non-HTTP protocols. We do not. - Never omit
import autheliafrom a Caddyfile block — it silently makes a service public. ~/authelia/configuration.ymlis root-owned:sudo, back up as.bak-<purpose>-<date>. Restarting Authelia logs out every active user — announce it first.- AD group membership must be DIRECT. Nested membership silently fails.
pg-aiandcubeare pinned — do not add them to Watchtower's update list.openplc-runtimeis live control for this demo. Never restart, update or reconfigure it as a side effect of AI work. Do not copy its published-port pattern, and never change its10.0.0.17binding to0.0.0.0— that binding is what keeps unauthenticated Modbus off the internet.- Never install on, write to or restart
cicore1. Never exceed read-only onimh. - No secrets in Git, compose files, logs or error messages.
0600env files under~/ai/.
Verification
docker ps showing "Up" is not proof. curl -sI the public URL, expect 302 to the auth
portal, and read the container logs. Prove each layer without the LLM first: prove Cube returns
the right number by hand, prove retrieval finds the right procedure by hand, then wire the agent.
Working conventions
- Prefer additive changes. Snapshot config before editing.
- Do not invent schema.
imhis pending — inspect it, or ask. Fixtures are marked as fixtures. - Store UTC. Convert to
SITE_TIMEZONEexactly once, in Cube. Never do timezone maths in a prompt. - Fix failures in the classifier, Cube or ingestion — not by adding instructions to the prompt.
- When something fails, add the failing case to
eval/testset.jsonlbefore fixing it. - Small commits, one concern each. If a change alters an accepted phase, re-run that phase's gate.
The plant
Waterloo Road Pump Station: a three-pump wastewater station. Wet well WW-101 (0–7000 mm,
120 m³/m), duty/assist/assist pumps PU-301/302/303 on a common VSD speed reference, discharging
through manifold MAN-301 against 22 m static lift. Spill weir at 6000 mm, LSHH-102 at 5500 mm.
Control runs on openplc-runtime; Yokogawa CI Server on cicore1 polls it over Modbus TCP and
historises the result. Source of truth for tags: db/seed/tags.csv, derived from
WRPS/04-plc/register-map.csv and WRPS/05-scada/modbus/scada-points.csv.