A toggle with a Value writes it to its variable while on and nothing while off; its Negative goes with the variable. $name.bool reads the switch (true/false) across nodes, in text and in conditions, through __wp_toggle_bools__. Payloads reading $name.bool stamp schema 12; bool is reserved as a tag group.
112 lines
5.2 KiB
Python
112 lines
5.2 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 = 12
|
|
|
|
# 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
|
|
# pool filter variables: a `$name` term in a use's `category_filter` or a
|
|
# ref's `@{uuid:…}` filter
|
|
FILTER_VARIABLES_SCHEMA_VERSION = 11
|
|
# WP Toggles booleans: text or a condition reading `$name.bool`
|
|
BOOL_ACCESSOR_SCHEMA_VERSION = 12
|
|
|
|
_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}
|