yau-plant-assistant/CLAUDE.md
Claude 4826255672 Correct the spec against the host as actually observed
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>
2026-08-20 14:29:41 +10:00

4 KiB
Raw Blame History

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

  1. No instructions for safety-critical actions. Procedural questions get document identity, revision, effective date and verbatim prerequisites — never synthesised steps.
  2. 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.
  3. 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 proxy network, 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 authelia from a Caddyfile block — it silently makes a service public.
  • ~/authelia/configuration.yml is 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-ai and cube are pinned — do not add them to Watchtower's update list.
  • openplc-runtime is 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 its 10.0.0.17 binding to 0.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 on imh.
  • No secrets in Git, compose files, logs or error messages. 0600 env 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. imh is pending — inspect it, or ask. Fixtures are marked as fixtures.
  • Store UTC. Convert to SITE_TIMEZONE exactly 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.jsonl before 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 (07000 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.