Commit Graph
12 Commits
Author SHA1 Message Date
stroblmeandClaude Opus 5 0e7c0038c0 Compose: keep the portal enrolment on the data volume
CLOUD_CONFIG_FILE defaulted to a path relative to the working directory, so the
enrolment — this installation's credential and the portal key it pinned — sat
inside the container and was lost on every rebuild, silently detaching a
connected installation. It belongs beside the OAuth key and the flow secrets,
for exactly the same reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtBzdDyLsmDaF1W7DLYtYM
2026-08-19 22:13:07 +02:00
Melvin Strobl aa40ccb58b analytics
Signed-off-by: Melvin Strobl <lc3267@kit.edu>
2026-08-18 13:17:07 +02:00
stroblmeandClaude Fable 5 2613fbead4 Allow the standalone Vite origin through CORS
`make dev-frontend` serves the SPA from localhost:5173 and talks to the
stack's backend, which refused it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7LwYgJfpkbLCTeiAf8U4A
2026-08-16 16:46:35 +02:00
stroblmeandClaude Fable 5 82df345bf0 Give the dev stack a broker, a time-series database, and live backend source
The mqtt and influx node types had nothing to talk to locally; mosquitto and
influxdb fill that in, on the compose network only — no host port, since 1883
usually belongs to a real broker on the machine.

The same file's --reload only ever reloaded the source baked into the image:
develop.watch needs `docker compose watch`, and the dev stack starts detached.
A bind mount is what the standard flow actually reads.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7LwYgJfpkbLCTeiAf8U4A
2026-08-16 16:46:35 +02:00
stroblmeandClaude Fable 5 b4d5aa6186 Let two backend CI runs share a runner host
The job kept the fixed container names and the fixed 5432 that compose.ci.yml
resets for every other job, so a second run could not start. Layering that
file leaves the db on a port Docker assigns, which the suite is told about,
and the project name is now per run so one teardown cannot take another run's
database with it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7LwYgJfpkbLCTeiAf8U4A
2026-08-16 16:46:25 +02:00
rootandClaude Fable 5 5462842b8a Supervise the engine's host: deep health, loop watchdog, one worker
The API image ran four uvicorn workers, and each one built a full flow
controller — four sets of MQTT subscriptions, cron ticks and webhooks.
Runs one worker now; scaling out is the worker split, not more processes.

Adds a loop-lag watchdog and a deep /utils/health/ that fails when the
event loop is wedged or Redis is unreachable, the two failure modes a
process-alive check never sees. Autoheal restarts on that signal, behind
a compose profile because it mounts the Docker socket.

The private user-seeding routes now need an explicit opt-in rather than
just ENVIRONMENT=local, so a deployment that kept the default never
exposes them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011LF61rxW1FG5YCD2J9YqjY
2026-08-16 07:14:28 +02:00
Melvin StroblandClaude Fable 5 8d82d6c4ec Let agents drive the flow API over MCP
Playwright Tests / test-playwright (1, 2) (push) Canceled after 0s
Playwright Tests / test-playwright (2, 2) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Test Backend / test-backend (push) Canceled after 0s
Compose Smoke Test / test-compose (push) Canceled after 0s
Playwright Tests / merge-reports (push) Canceled after 0s
The engine now speaks MCP at /mcp, with a built-in OAuth 2.1 authorization
server in front of it: an agent registers itself, sends a human to the browser
to approve it, and exchanges the resulting code for a token. PKCE is required,
codes are single-use and stored only as hashes, the browser is redirected to
the URI that was registered rather than the one asked for, and refresh tokens
rotate so that replaying a spent one revokes the whole line.

Twenty tools cover reading, building, publishing and running flows, and each
one calls the same REST endpoint the dashboard calls, in-process, carrying the
caller's own token. That keeps one description of what a flow is and what may
be done to it — validation, the draft/publish split, the version check — and
means an agent can do nothing a person could not do in the browser.

Agent tokens are RS256 with a keypair of their own rather than the secret that
signs browser sessions, so deleting the key withdraws every agent without
logging anyone out, and deps.decode_token grew the branch that trusting a
second issuer will need when the hosted login arrives.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 00:22:41 +02:00
Melvin StroblandClaude Opus 5 8a221bfe50 Add the Gitea Actions workflows
Ports n3xd's four: pre-commit, backend tests, sharded Playwright and a
compose smoke test. Everything comes from the committed .env.example, so
no repository secrets are needed; Actions still has to be enabled per
repository and a runner registered with the ubuntu-latest label.

container_name, published ports and image tags are all daemon-global, so
two shards on one runner would fight over them. compose.ci.yml resets
them and tags per project. test-backend keeps the fixed names because it
reaches Postgres from the runner host.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkmeRiyeYmVZqJVwuyHq9o
2026-08-15 21:20:05 +02:00
Melvin StroblandClaude Opus 5 11d491dae0 Keep flow state across a restart, and tighten the flow typing
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
2026-08-15 21:19:21 +02:00
Melvin StroblandClaude Fable 5 8c82549cf6 Add the flow editor: canvas, node panel and live values
The browser half of M3. Flows open on a full-bleed canvas with their chrome
floating over it: flow tabs top, dock bottom, node settings in a panel on the
right that leaves the graph visible and running behind it.

- Connections are derived, not stored. A node declares the messages it reads
  and publishes; every matching pair draws an edge, so two producers of one
  message converge on their consumer. Dragging output to input is shorthand
  for pointing that input at the producer's message, and asks before it
  replaces an existing one.
- Values land on the edges as they flow, over a websocket that feeds a store
  outside React, so a value arriving re-renders its own chip and nothing else.
  Clicking an edge shows the last payload and when it arrived.
- Node source is edited in Monaco, loaded only when a panel opens and themed
  from the design tokens.
- Edits autosave; identical documents are skipped server-side, so a quiet
  canvas writes nothing.
- Validation from the API shows on the node it belongs to and is summarised in
  the dock, where each entry pans to its node.
- Works on a phone: touch-connect, 44px dock targets, and the node panel
  becomes a full-screen sheet.

Two new tokens (--status-success, --font-mono) are mirrored in the website repo
and recorded in DESIGN-GUIDELINES.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WzrvW7rjQbynnhF6pxh6i
2026-08-15 18:10:50 +02:00
Melvin StroblandClaude Opus 5 0c3e6081f2 Green the lint gate and drop the local-stack host ports
- 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>
2026-08-09 15:52:57 +02:00
Melvin StroblandClaude Opus 5 529b5f9ed5 Restructure docker into layered compose files, add a Makefile
Move compose.yml/compose.override.yml/compose.traefik.yml into docker/ and
split into the explicit prod -> dev -> local layering; compose.override.yml
had to be renamed because docker compose auto-loads that filename, which
defeats the layering.

- external network traefik-public -> proxy (shared with the website stack)
- frontend host dashboard.${DOMAIN} -> app.${DOMAIN}
- stable container_names, security_opt no-new-privileges on prod services
- adminer bound to 127.0.0.1 in dev instead of all interfaces
- .env.example replaces the committed .env
- pre-commit biome hook ran npm in a bun repo

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 15:21:34 +02:00