From 3c04c2944d3eab18be4d9a02da7c977193943e9d Mon Sep 17 00:00:00 2001 From: stroblme Date: Sat, 22 Aug 2026 14:49:54 +0200 Subject: [PATCH] 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) --- scripts/tinyhouse/__main__.py | 51 +++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/scripts/tinyhouse/__main__.py b/scripts/tinyhouse/__main__.py index 0a2c8ed..ec9c8f0 100644 --- a/scripts/tinyhouse/__main__.py +++ b/scripts/tinyhouse/__main__.py @@ -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: