seed: refuse an engine that would ignore what these flows rely on

A node type accepts a setting it does not know and ignores it, which is right
for a node someone is editing and wrong here: seeded against the engine this
installation is running today, every rollershutter would take the trigger's
default minute instead of the twenty-six seconds it actually takes, and every
Victron reading would arrive as an object where a number was declared. It
would look like it had worked.

So the seed asks what the node types know before it writes anything, and says
which rebuild is missing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-22 14:49:54 +02:00
co-authored by Claude Opus 5
parent 0f1d2cc483
commit 3c04c2944d
+51
View File
@@ -383,6 +383,39 @@ def push(api: Api, flows: list[Flow]) -> None:
print(f" dashboard '{name}': {len(widgets)} widgets, published")
#: Settings these flows depend on that a node type gained for this port. A node
#: type accepts an unknown setting and ignores it — which is right for a node
#: someone is editing and wrong here, where it would mean a rollershutter
#: running for the default minute instead of the twenty-six seconds it takes.
NEEDED = {
"trigger": {
"wait_port": "a shutter would run for the default wait, not its own",
"passthrough": "a motor would be commanded with 'true' rather than a direction",
},
"mqtt": {
"json_key": "every Victron reading would arrive as an object, not a number",
},
"http": {
"query": "the weather key would have to sit in the flow file in clear",
},
}
def _engine_too_old(api: Api) -> list[tuple[str, str]]:
"""Settings this installation's node types do not know about yet."""
types = {t["type"]: t for t in api("GET", "/flows/node-types")}
stale = []
for type_name, settings in NEEDED.items():
schema = types.get(type_name, {}).get("params_schema") or {}
known = set(schema.get("properties") or {})
if not known:
continue
for setting, why in settings.items():
if setting not in known:
stale.append((f"{type_name}.{setting}", why))
return stale
def main(argv: list[str]) -> int:
dry = "--dry" in argv
h = house()
@@ -401,6 +434,24 @@ def main(argv: list[str]) -> int:
return 0
api = Api()
stale = _engine_too_old(api)
if stale:
print(
"\nThis installation's engine does not have what these flows need:",
file=sys.stderr,
)
for setting, why in stale:
print(f" {setting}{why}", file=sys.stderr)
print(
"\nAn unknown setting is accepted and ignored rather than refused, so\n"
"seeding now would look like it worked and run every shutter for a\n"
"minute. Rebuild the image first:\n"
' docker compose -p fluksio-app --env-file "$PWD/.env" \\\n'
" -f docker/compose.yml up --build -d backend",
file=sys.stderr,
)
return 1
have = set(api("GET", "/secrets/")["data"])
missing = [name for name in SECRETS if name not in have]
if missing: