Rename planner and document rewrite: rewrite_documents moves qualified names of the renamed flow in every flow (ports, inputs, outputs) and dashboard binding (one key table, WIDGET_BINDINGS, shared with the WidgetDef accessors, legacy pages included), published and draft, as plain JSON with both variants above either version; plan_rename reports documents, counts, fingerprint, warnings and refusals; superuser plan routes for flows and dashboards; key residue classifies state as the mapper does; duplicate retargets inputs and outputs too; queue reports its Redis version and other consumers

This commit is contained in:
2026-11-02 21:06:39 +01:00
parent 81bd7c2724
commit fa2a213631
14 changed files with 1922 additions and 41 deletions
+32
View File
@@ -457,6 +457,17 @@ class WorkQueue(ABC):
"""Release a held list left by a process that stopped mid-rename. For
startup, before anything claims. Returns how many items moved."""
def server_version(self) -> tuple[int, int] | None:
"""The major and minor version of the server the queue lives on,
None for one in this process. A rename rebuilds streams with commands
Redis has had since 7.0."""
return None
def other_consumers(self, within_ms: int) -> list[str]:
"""Consumers but this one that read the queue in the last *within_ms*:
another engine on the same server, which a rename cannot hold."""
return []
@abstractmethod
def rename(
self, rename: Rename, progress: Callable[[str], None] | None = None
@@ -1132,6 +1143,27 @@ class RedisWorkQueue(WorkQueue):
"durable": True,
}
def server_version(self) -> tuple[int, int] | None:
info = cast(dict[str, Any], self._redis.info("server"))
major, minor, *_ = str(info["redis_version"]).split(".")
return int(major), int(minor)
def other_consumers(self, within_ms: int) -> list[str]:
found: set[str] = set()
for stream in (self._stream, self._due_stream):
try:
rows = cast(
list[dict[str, Any]], self._redis.xinfo_consumers(stream, GROUP)
)
except redis.ResponseError:
continue
found |= {
str(row["name"])
for row in rows
if row["name"] != self._consumer and int(row["idle"]) < within_ms
}
return sorted(found)
def dead_letters(self, count: int = 50) -> list[dict[str, Any]]:
"""What was set aside, newest first. The entry id carries the time."""
entries = cast(