history: one Flux script per chart, and rows are json

Two things the installation found that the checks did not.

A script with three `from()` statements in it produces three results all
called `_result`, and InfluxDB refuses that outright — so the measurements go
into one filter and the rows come back tagged with which one they are.

And the answer a database node hands back holds a *list* of rows, which a
record may not: a record is flat scalars. It was declared one, so every chart
failed on the type check the moment a real answer arrived.

The second one is now caught before anything is pushed: the preflight runs
each sample shape past the port that would receive it, which is what turns
"expected record, got dict" from a runtime surprise into a line of output.

Also records the two engine faults this seeding session surfaced.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-22 16:24:38 +02:00
co-authored by Claude Opus 5
parent 8734e51ef1
commit 1b455b5e55
4 changed files with 112 additions and 32 deletions
+23
View File
@@ -12,6 +12,29 @@ Deferring because out of scope is fine, but don't mention deferring than.
### To be sorted
- BUG/FLOW: **a cancelled request can leave `FlowController._lock` held forever.**
Seeding nineteen flows over a client that timed out mid-request left the next
`POST /flows/{name}/start` waiting on the lock indefinitely — ten minutes, until
the container was restarted. A py-spy dump showed *no* thread in the reload path,
so the coroutine that holds it is suspended at an `await` inside `reload()`, most
likely in `_teardown()` awaiting a supervised task's cancellation. Everything else
kept working — health, reads, MQTT — so the engine looked fine and only anything
needing a rebuild hung. Two things worth doing: release the lock on cancellation
(`asyncio.timeout` around the teardown, or a `finally` that cannot be skipped), and
fail a `start` that waits more than a few seconds for the lock rather than hanging.
- BUG/INFRA: **475 zombie `git` processes** in the API container after a seeding
session. `FlowStore._git` uses `subprocess.run`, which reaps its own child — these
are the `git gc --auto` daemons `git commit` spawns, reparented to PID 1 when their
parent exits. PID 1 is the FastAPI process, which never reaps orphans. Harmless at
484 processes against a 34719 limit, but it grows with every save, and a container
that commits per keystroke will get there. Fix: `git -c gc.auto=0 commit`, since a
store that never packs is a separate (and real) concern — 1898 commits had left
5863 loose objects and no packs.
- PERF/API: seeding nineteen flows takes two reloads each — one to publish, one to
stop — so a full rebuild of every flow in the installation runs about forty times
for one seed. It is the slowest thing about standing an installation up, and a
`PUT` that could say "published, stopped" in one call would halve it.
- FEAT/SEC: `locked` is a read-only surface, not a permission — the server accepts a publish
from a panel whose dashboard says locked. Making it real means carrying the flag into
`_panel_may`.