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>
8.9 KiB
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 |
Auto-loaded by Claude Code |
| Active rules | .claude/rules/ (7 files) |
Triggered by paths: frontmatter or referenced from CLAUDE.md |
| Platform API contracts | .claude/refactor/CONTRACTS.md |
Before changing generalFunctions exports or any base class |
| Code conventions | .claude/refactor/CONVENTIONS.md |
Before writing or editing any file |
| Per-node concern layout | .claude/refactor/MODULE_SPLIT.md |
When adding files to nodes/<n>/src/ |
| Wiki page templates | .claude/refactor/WIKI_TEMPLATE.md + WIKI_HOME_TEMPLATE.md |
When editing a per-node wiki page |
| Live decisions log | .claude/refactor/OPEN_QUESTIONS.md |
When you spot an ambiguity — append, don't invent |
| Top-level 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/ (15 domain skills, auto-discovered, invokable via Skill tool) |
When you need domain reasoning |
| Spawnable subagents | .claude/agents/ (10 Claude Code subagents) |
When you want to delegate independent work |
| Routing table | .agents/AGENTS.md |
When deciding which specialist to invoke |
| Improvements backlog | .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/ (20 files) |
Historical reference; each has ⚠️ ARCHIVED — Do not update |
| Refactor plan artifacts | .claude/refactor/Archive/ — CONTINUE_HERE.md, TASKS.md |
The May-2026 refactor plan; phases all done |
| Old priority lists | .agents/improvements/Archive/ |
Pre-refactor production priorities |
2. Discovery chain — how a fresh agent finds the rules
CLAUDE.mdauto-loads → points at this file..claude/rules/*.mdauto-load bypaths:frontmatter when editing matching files.nodes/<n>/CLAUDE.mdauto-loads when working under that submodule.- This file (
CONTRACTS.md) is the human-facing map of everything in step 1-3. - Concept lookup: use
grep/findor theExploresubagent — anchor on the canonical sources listed in §1 (commands registry, CONTRACT.md, base classes ingeneralFunctions/).
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 |
nodes/<n>/<n>.js |
| 2 — Adapter | BaseNodeAdapter (from generalFunctions) |
.claude/refactor/CONTRACTS.md §2 |
nodes/<n>/src/nodeClass.js |
| 3 — Domain | BaseDomain (from generalFunctions) |
.claude/refactor/CONTRACTS.md §3 |
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.
4. Output and telemetry contract
Three output ports per node. Source of truth: .claude/refactor/CONTRACTS.md §10 and 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.
5. When a contract changes — the rule
- Update the source file (
src/commands/index.js,src/specificClass.js, orgeneralFunctions/index.js). - Update the per-node
CONTRACT.md(Inputs table is partially autogenerated; the rest is hand-maintained). - Run
npm run wiki:allinside the submodule to regenerate the topic-contract + data-model sections inwiki/. - If the change touched a platform shape (a base class or shared utility), update
.claude/refactor/CONTRACTS.mdandnodes/generalFunctions/CONTRACT.md. - If the change introduced a deprecation, add an alias to
commands/index.jsand a one-line note to the per-nodeCONTRACT.md. - Append unresolved questions to
.claude/refactor/OPEN_QUESTIONS.md. Don't invent answers. - If topic usage in an example flow changed, regenerate or review the per-node
wiki/Reference-Examples.mdand theexamples/*.flow.jsonset.
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.mdupdated for any added / removed / renamed topic, port-0 key, or event.npm run wiki:allre-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.