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

62 lines
4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`.