Files
DumiFlex-ComfyUI-Wildcard-P…/engine/migrations/__init__.py
T
Dumitru Picalau 657dc07831 feat(ai): assistant overhaul: tool registry, server-kept chats, search index, security, page tools, web search, saved chats, images, permissions, derivation NOT (schema 10), OpenAI Responses API (#64)
Phases 2 to 8 of the AI assistant overhaul plus fixes from real-model chats: one tool registry with native tool calls, server-kept chat logs with prompt caching, an FTS5 library index with opt-in meaning search, keys bound to their address and a guard on every AI route, page tools and opt-in web search, saved chats with streaming, Try again, a context meter and compaction, chat images, delete/category/tag permissions with Undo, constraint and test cards, card fixes and previews, questions with answers to click, the derivation NOT toggle (schema v10, community catalog 0021), request fixes for the newest Claude models, and OpenAI's Responses API. Review fixes: Try again clears the old turn's fixes and answers, category Undo removes only what it made, a failed delete rolls back in order, not_matches patterns are checked, busy chats refuse a second request, and chat search ignores stored JSON.
2026-10-14 14:56:12 +02:00

107 lines
4.9 KiB
Python

"""Versioned payload migration chain. Mirror of TypeScript
`src/manager/import-export/migrations.ts`.
Used by `engine/importer.py` on server-side parse paths (re-validate
on commit). Client-side migration is the source of truth for what
the user saw in the picker; server re-runs as defense in depth.
"""
from __future__ import annotations
from typing import Any
from engine.migrations.v0_to_v1 import migrate_v0_to_v1
from engine.migrations.v1_to_v2 import migrate_v1_to_v2
CURRENT_SCHEMA_VERSION = 2
# Highest schema version this runtime can natively READ + WRITE — distinct
# from CURRENT_SCHEMA_VERSION (the migration-chain head, which stays 2 because
# v2->v3 through v6->v7 are no-ops). v3 is the SP2b text-grammar bump (shape-
# identical to v2), v4 is the additive `target_select` constraint reach, v5
# is the additive `accepts` tag-axis kind, v6 the constraint `only` rule and
# v7 derivation AND/OR condition groups plus the `is_empty`/`is_not_empty` ops,
# v8 send-to-negative (`negative` text + the derivation `negative` mode),
# v9 the wildcard fallback option and v10 the derivation `not_contains` /
# `not_matches` tests; all are handled natively, so a
# payload at CURRENT < v <= MAX_KNOWN installs AS-IS with no migration.
# Mirror of TS `MAX_KNOWN_SCHEMA_VERSION` in
# `src/manager/import-export/migrations.ts`.
#
# MAINTENANCE CONTRACT: bump this whenever the TS `schemaVersionForPayload()`
# learns to stamp a new (higher) version — otherwise this commit-side
# re-validate will reject the very shapes the runtime just learned to produce.
MAX_KNOWN_SCHEMA_VERSION = 10
# Feature stamps (mirror of the TS constants beside MAX_KNOWN). Each is the
# catalog version a payload needs once it uses that feature; see
# `engine/migrations/stamping.py:schema_version_for_payload`.
SP2B_SCHEMA_VERSION = 3 # range count / `~` flag in a `{N$$…}` multi-pick
SP3_REACH_SCHEMA_VERSION = 4 # non-default constraint `target_select`
TAG_AXES_SCHEMA_VERSION = 5 # wildcard `tag_group_kinds` with an `accepts` group
CONSTRAINT_ONLY_SCHEMA_VERSION = 6 # constraint rule with mode `only`
# derivation condition group (AND / OR) or an `is_empty` / `is_not_empty` test
DERIVATION_CONDITIONS_SCHEMA_VERSION = 7
# send-to-negative: a non-empty `negative` on a wildcard option, fixed value or
# combine, or a derivation action with mode `negative`
NEGATIVES_SCHEMA_VERSION = 8
# wildcard fallback option: an option flagged `fallback: true`
FALLBACK_SCHEMA_VERSION = 9
# derivation NOT toggle: a test using `not_contains` or `not_matches`
NEGATED_TESTS_SCHEMA_VERSION = 10
_CHAIN = {
0: migrate_v0_to_v1,
1: migrate_v1_to_v2,
}
def migrate_payload(payload: dict[str, Any]) -> dict[str, Any]:
"""Walk the migration chain from `payload.schema_version` up to current.
Returns one of:
- {"ok": True, "migrated": <dict>, "migrated_entity_count": int}
where migrated_entity_count is the cumulative entity count across
all migration steps (total bundles+wildcards+fixed_values+combines+
derivations+constraints+categories that passed through any step).
Matches TS `migratedEntityCount`.
- {"ok": False, "reason": str}
"""
version = payload.get("schema_version")
if not isinstance(version, int):
return {"ok": False, "reason": "payload missing schema_version field"}
# Reject threshold reads MAX_KNOWN (highest natively-supported version),
# NOT CURRENT (the migration-chain head). A payload at
# CURRENT < v <= MAX_KNOWN is shape-compatible and handled natively, so it
# passes through AS-IS — the while-loop below is bound by CURRENT, so there
# is nothing to migrate and schema_version is preserved. Only > MAX_KNOWN
# rejects. Mirror of TS `migrateImportEnvelope`.
if version > MAX_KNOWN_SCHEMA_VERSION:
return {
"ok": False,
"reason": f"future schema version {version} (max known: {MAX_KNOWN_SCHEMA_VERSION})",
}
current = {
"schema_version": version,
"bundles": payload.get("bundles", []),
"wildcards": payload.get("wildcards", []),
"fixed_values": payload.get("fixed_values", []),
"combines": payload.get("combines", []),
"derivations": payload.get("derivations", []),
"constraints": payload.get("constraints", []),
"categories": payload.get("categories", []),
}
migrated_entity_count = 0
while current["schema_version"] < CURRENT_SCHEMA_VERSION:
fn = _CHAIN.get(current["schema_version"])
if fn is None:
ver = current["schema_version"]
return {"ok": False, "reason": f"no migration registered for v{ver}"}
before = sum(
len(current[k])
for k in ("bundles", "wildcards", "fixed_values", "combines",
"derivations", "constraints", "categories")
)
current = fn(current)
migrated_entity_count += before
return {"ok": True, "migrated": current, "migrated_entity_count": migrated_entity_count}