The engine fell back to MemoryState because there was no Redis to talk
to, so every value was lost on restart. Adds a redis service with
append-only persistence, reachable only from the backend, and points
REDIS_HOST at it.
state.py and pipeline.py leave the strict-mypy exclusion. redis-py types
its calls as sync/async unions; the casts narrow that at the points of
use rather than switching to redis.asyncio, which would make a
deliberately synchronous backend asynchronous. nodes.py stays excluded.
The .env.example CORS entry belongs with the CI work that follows.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkmeRiyeYmVZqJVwuyHq9o
Makes the flow engine reachable from the API, which is what M3 needs before
any of it can reach the browser.
- app/flow is a package now; the prototype's watch-dir scripts and the
matplotlib/networkx visualiser are gone with their dependencies.
- Messages carry a serializable dtype instead of a live Python type, and a
port name, so the graph can speak qualified names while node functions keep
local arguments. Redis state is JSON, not pickle.
- Message names are namespaced per flow ("heating.temp"); a bare name resolves
to its own flow, a dotted one crosses flows.
- Several nodes may provide the same message: producers are a list, so fan-in
is a real edge instead of a silently dropped one.
- Flows are stored as flow.json plus node sources in a git repository, one
commit per save, with identical saves skipped so autosave stays quiet.
- Node failures are isolated and reported per node; validate() returns cycles
and unconnected inputs instead of raising deep in a run.
- Credentials live in an encrypted store and are referenced as {"$secret": …}.
- Engine events reach websocket clients through a bus, so values, node status
and execution show up live.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WzrvW7rjQbynnhF6pxh6i
- exclude app/flow from ruff and mypy: it is not importable as a package and
nothing in the API reaches it, so 405 of the 412 findings were about code
scheduled for restructuring (see NOTEPAD.md)
- fix the 7 real findings outside it: col(...) around created_at for the
.desc() ordering, and a type: ignore that is no longer needed
- compose.local.yml resets the host ports compose.dev.yml publishes; the
integrated stack is served entirely through Traefik, and a port already
taken on the host used to fail the whole stack
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The submodule collapse was only half applied: .gitmodules was deleted but
backend/ and frontend/ were still recorded as gitlinks, so none of their
files were tracked. Replace the gitlinks with the real trees.
Also untrack .env (it carried placeholder secrets) in favour of a tracked
.env.example, drop the committed __pycache__, and narrow the blanket *.png
ignore that would have swallowed design assets.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>