Superproject: - CLAUDE.md: legacy-drift table loses the dashboardAPI row (migrated); drift section notes the type-id-preservation strategy for the remaining mgc / vgc renames. - CONTRACTS.md: canonical-unit rule explicitly carves out reactor as an approved ASM-textbook exception with the conversion boundary. Submodules: - nodes/valve @ 167b102: CONTRACT documents valve's lack of an FSM maintenance state (schema mode enum accepts `maintenance` but no enter/exit sequences exist). Limits made explicit instead of being hidden as a wiki TODO. - nodes/reactor @ 75d0413: CONTRACT now declares the approved ASM-unit divergence (mg/L, m³/d, °C, 1/h) with the conversion boundary spelled out. Closes the canonical-unit drift surfaced by the wiki audit. - nodes/dashboardAPI @ ......: file rename (preserves type id). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
149 lines
8.9 KiB
Markdown
149 lines
8.9 KiB
Markdown
# EVOLV — Contracts, Rules, and Standards
|
|
|
|
> **Front door for humans and agents working in this repo.**
|
|
> If you only read one file before touching code, read this one. It maps every
|
|
> contract, rule, and standard in the EVOLV stack and tells you where each
|
|
> lives. Everything else here is a link to a more specific file.
|
|
|
|
EVOLV is a Node-RED node library for wastewater treatment plant automation,
|
|
built by Waterschap Brabantse Delta R&D. All work happens on the `development`
|
|
branch across 12 submodules; promotion to `main` is gated by Docker E2E.
|
|
|
|
---
|
|
|
|
## 1. Where everything lives
|
|
|
|
### Platform-wide (EVOLV root)
|
|
|
|
| What | Where | Read when |
|
|
|---|---|---|
|
|
| **This map** | `CONTRACTS.md` (this file) | First time, or when orienting |
|
|
| **Agent entry-point instructions** | [`CLAUDE.md`](./CLAUDE.md) | Auto-loaded by Claude Code |
|
|
| **Active rules** | [`.claude/rules/`](./.claude/rules/) (7 files) | Triggered by `paths:` frontmatter or referenced from `CLAUDE.md` |
|
|
| **Platform API contracts** | [`.claude/refactor/CONTRACTS.md`](./.claude/refactor/CONTRACTS.md) | Before changing `generalFunctions` exports or any base class |
|
|
| **Code conventions** | [`.claude/refactor/CONVENTIONS.md`](./.claude/refactor/CONVENTIONS.md) | Before writing or editing any file |
|
|
| **Per-node concern layout** | [`.claude/refactor/MODULE_SPLIT.md`](./.claude/refactor/MODULE_SPLIT.md) | When adding files to `nodes/<n>/src/` |
|
|
| **Wiki page templates** | [`.claude/refactor/WIKI_TEMPLATE.md`](./.claude/refactor/WIKI_TEMPLATE.md) + [`WIKI_HOME_TEMPLATE.md`](./.claude/refactor/WIKI_HOME_TEMPLATE.md) | When editing a per-node wiki page |
|
|
| **Live decisions log** | [`.claude/refactor/OPEN_QUESTIONS.md`](./.claude/refactor/OPEN_QUESTIONS.md) | When you spot an ambiguity — append, don't invent |
|
|
| **Top-level wiki** | [`wiki/`](./wiki/) (Home, Architecture, Getting-Started, Telemetry, Topology-Patterns, Topic-Conventions, Glossary, Functional-Overview) | When you need a process-level or architecture-level view |
|
|
| **Agent skills** | [`.claude/skills/`](./.claude/skills/) (15 domain skills, auto-discovered, invokable via `Skill` tool) | When you need domain reasoning |
|
|
| **Spawnable subagents** | [`.claude/agents/`](./.claude/agents/) (10 Claude Code subagents) | When you want to delegate independent work |
|
|
| **Routing table** | [`.agents/AGENTS.md`](./.agents/AGENTS.md) | When deciding which specialist to invoke |
|
|
| **Improvements backlog** | [`.agents/improvements/IMPROVEMENTS_BACKLOG.md`](./.agents/improvements/IMPROVEMENTS_BACKLOG.md) | When deferring functional work |
|
|
|
|
### Per-node (`nodes/<nodeName>/`)
|
|
|
|
| What | Where | Read when |
|
|
|---|---|---|
|
|
| Node entry instructions for agents | `nodes/<n>/CLAUDE.md` | Auto-loaded when touching files in that subtree |
|
|
| **Node API contract** | `nodes/<n>/CONTRACT.md` | Before changing `msg.topic` inputs/outputs/events |
|
|
| **Command registry (source of truth)** | `nodes/<n>/src/commands/index.js` | When adding/removing an accepted topic |
|
|
| **Domain logic** | `nodes/<n>/src/specificClass.js` | Pure JS; no `RED.*` allowed |
|
|
| **Node-RED adapter** | `nodes/<n>/src/nodeClass.js` | Bridge to runtime; ≤ 25 lines, extends `BaseNodeAdapter` |
|
|
| **Per-node wiki** | `nodes/<n>/wiki/` — `Home.md`, `Reference-{Architecture,Contracts,Limitations,Examples}.md` | Topic-contract + data-model sections autogen via `npm run wiki:all` |
|
|
| **Tests** | `nodes/<n>/test/{basic,integration,edge}/` | Required for every change |
|
|
| **Example flows** | `nodes/<n>/examples/{basic,integration,edge}.flow.json` | Required artifact per node |
|
|
|
|
### Shared library (`nodes/generalFunctions/`)
|
|
|
|
| What | Where |
|
|
|---|---|
|
|
| **Library API contract** | `nodes/generalFunctions/CONTRACT.md` |
|
|
| **Public exports** | `nodes/generalFunctions/index.js` (barrel) |
|
|
| **Source** | `nodes/generalFunctions/src/{domain,nodered,measurements,convert,configs,…}/` |
|
|
|
|
### Archives (don't take as authoritative)
|
|
|
|
| What | Where | Why kept |
|
|
|---|---|---|
|
|
| Pre-refactor wiki pages | [`wiki/Archive/`](./wiki/Archive/) (20 files) | Historical reference; each has `⚠️ ARCHIVED — Do not update` |
|
|
| Refactor plan artifacts | [`.claude/refactor/Archive/`](./.claude/refactor/Archive/) — `CONTINUE_HERE.md`, `TASKS.md` | The May-2026 refactor plan; phases all done |
|
|
| Old priority lists | [`.agents/improvements/Archive/`](./.agents/improvements/Archive/) | Pre-refactor production priorities |
|
|
|
|
---
|
|
|
|
## 2. Discovery chain — how a fresh agent finds the rules
|
|
|
|
1. `CLAUDE.md` auto-loads → points at this file.
|
|
2. `.claude/rules/*.md` auto-load by `paths:` frontmatter when editing matching files.
|
|
3. `nodes/<n>/CLAUDE.md` auto-loads when working under that submodule.
|
|
4. This file (`CONTRACTS.md`) is the human-facing map of everything in step 1-3.
|
|
5. **Concept lookup**: use `grep` / `find` or the `Explore` subagent — anchor on the canonical sources listed in §1 (commands registry, CONTRACT.md, base classes in `generalFunctions/`).
|
|
|
|
---
|
|
|
|
## 3. The three contracts every node honours
|
|
|
|
Every EVOLV node is a three-tier sandwich. Each tier has a contract:
|
|
|
|
| Tier | Class | Contract source | Per-node implementation |
|
|
|---|---|---|---|
|
|
| 1 — Entry | `RED.nodes.registerType` | [`.claude/rules/node-architecture.md`](./.claude/rules/node-architecture.md) | `nodes/<n>/<n>.js` |
|
|
| 2 — Adapter | `BaseNodeAdapter` (from `generalFunctions`) | [`.claude/refactor/CONTRACTS.md §2`](./.claude/refactor/CONTRACTS.md) | `nodes/<n>/src/nodeClass.js` |
|
|
| 3 — Domain | `BaseDomain` (from `generalFunctions`) | [`.claude/refactor/CONTRACTS.md §3`](./.claude/refactor/CONTRACTS.md) | `nodes/<n>/src/specificClass.js` |
|
|
|
|
Plus the **commands registry** (`nodes/<n>/src/commands/index.js`) declares
|
|
the `msg.topic` inputs; `BaseNodeAdapter` dispatches by topic lookup. See
|
|
[`.claude/refactor/CONTRACTS.md §4`](./.claude/refactor/CONTRACTS.md).
|
|
|
|
---
|
|
|
|
## 4. Output and telemetry contract
|
|
|
|
Three output ports per node. Source of truth: [`.claude/refactor/CONTRACTS.md §10`](./.claude/refactor/CONTRACTS.md) and [`wiki/Telemetry.md`](./wiki/Telemetry.md).
|
|
|
|
| Port | Carries | Formatter |
|
|
|---|---|---|
|
|
| 0 | Process data (delta-compressed) | `outputUtils.formatMsg(..., 'process')` |
|
|
| 1 | InfluxDB line protocol | `outputUtils.formatMsg(..., 'influxdb')` |
|
|
| 2 | Registration / control plumbing | hand-shaped on the parent-child handshake |
|
|
|
|
Output-coverage testing (manifest + populated + degraded states) is **mandatory**
|
|
for any change touching Port 0/1/2 keys, function-node fan-outs, or dashboard widgets.
|
|
See [`.claude/rules/output-coverage.md`](./.claude/rules/output-coverage.md).
|
|
|
|
---
|
|
|
|
## 5. When a contract changes — the rule
|
|
|
|
1. Update the source file (`src/commands/index.js`, `src/specificClass.js`, or `generalFunctions/index.js`).
|
|
2. Update the per-node `CONTRACT.md` (Inputs table is partially autogenerated; the rest is hand-maintained).
|
|
3. Run `npm run wiki:all` inside the submodule to regenerate the topic-contract + data-model sections in `wiki/`.
|
|
4. If the change touched a platform shape (a base class or shared utility), update [`.claude/refactor/CONTRACTS.md`](./.claude/refactor/CONTRACTS.md) and `nodes/generalFunctions/CONTRACT.md`.
|
|
5. If the change introduced a deprecation, add an alias to `commands/index.js` and a one-line note to the per-node `CONTRACT.md`.
|
|
6. Append unresolved questions to [`.claude/refactor/OPEN_QUESTIONS.md`](./.claude/refactor/OPEN_QUESTIONS.md). Don't invent answers.
|
|
7. If topic usage in an example flow changed, regenerate or review the per-node `wiki/Reference-Examples.md` and the `examples/*.flow.json` set.
|
|
|
|
---
|
|
|
|
## 6. Conventions in one paragraph (the rest is in `CONVENTIONS.md`)
|
|
|
|
Files ≤ 200 lines (300 hard cap); functions ≤ 30 lines (60 hard cap). Default
|
|
to no comments — add one only when *why* is non-obvious. `specificClass`
|
|
**never** imports `RED.*`. Logger from `generalFunctions`, never `console.log`.
|
|
S88 colour scheme is mandatory in diagrams. Topic prefixes: `set.<noun>` for
|
|
idempotent setters, `cmd.<verb>` for triggers, `evt.<noun>` for events.
|
|
Tests live in `test/{basic,integration,edge}/`. Submodule commits go in the
|
|
submodule first, then the superproject bumps the pin.
|
|
|
|
**Canonical units** (Pa / m³/s / W / K) apply to every node **except
|
|
`reactor`**, which deliberately uses ASM-kinetics literature units
|
|
(mg/L, m³/d, °C, 1/h) — documented in `nodes/reactor/CONTRACT.md`.
|
|
Conversions happen at the parent/child boundary via `UnitPolicy`.
|
|
|
|
---
|
|
|
|
## 7. Verification checklist before merge
|
|
|
|
- [ ] Per-node tests green (`cd nodes/<n> && node --test test/basic test/integration test/edge`).
|
|
- [ ] `CONTRACT.md` updated for any added / removed / renamed topic, port-0 key, or event.
|
|
- [ ] `npm run wiki:all` re-run in the touched submodule(s).
|
|
- [ ] Output-coverage manifest + tests updated if any output shape changed.
|
|
- [ ] Submodule pin bumped in the superproject.
|
|
- [ ] Commit message captures *why* — load-bearing decisions go in the commit body and PR description.
|
|
|
|
---
|
|
|
|
*Last reviewed: 2026-05-19. If something in this map is wrong, fix this file
|
|
in the same PR as the change that made it wrong.*
|