Files
DumiFlex-ComfyUI-Wildcard-P…/scripts/dump_engine_shapes.py
T
DumiFlex 51f6793177 test(validators): engine-parity harness to stop validator drift
The community TS validators re-implement the engine's payload shapes by
hand, and drifted repeatedly (fixed_values entries-vs-values, wildcard
is_null omitted) -- each drift surfaced as a publish failure for a user.
This makes the engine the single source of truth.

- scripts/dump_engine_shapes.py: builds one sample payload per module
  subtype, validates each against that subtype's own engine
  Handler.validate_payload (raises if not engine-valid), and writes the
  engine-row shape to src/validators/fixtures/engine-parity/*.json. Run
  it whenever an engine module shape changes; commit the regenerated
  fixtures.
- src/validators/__tests__/engine-parity.test.ts: asserts the STRICT
  community validator accepts every engine-dumped fixture. 5/5 pass.

Now drift fails CI on either side: a sample that no longer matches its
handler fails the generator; a validator that rejects the real shape
fails the parity test. CLAUDE.md schema section documents the loop.
2026-06-06 10:44:01 +03:00

163 lines
6.1 KiB
Python

"""Generate engine-parity fixtures for the community validator suite.
WHY THIS EXISTS
---------------
The community-web TS validators (`src/validators/`) are a hand-authored
re-implementation of the engine's payload shapes. Re-implementations
drift: fixed_values was published for weeks as `entries/{variable_name}`
when the engine has always used `values/{id,name,value}`; wildcard
options omitted the real `is_null` field; etc. Every drift surfaced as a
publish failure in front of a user.
This script makes the ENGINE the single source of truth. For each module
subtype it builds a sample payload, runs it through that subtype's own
`Handler.validate_payload` (the engine authority -- raises if the sample
is not engine-valid), and dumps the engine-row shape to a committed
fixture. The companion test `src/validators/__tests__/engine-parity.test.ts`
asserts the STRICT community validator accepts every fixture.
Result: if the engine shape changes (a handler gains/renames a field) the
sample stops validating here and this script fails; if the TS validator
drifts from the engine, the parity test fails. Drift is caught at CI time
instead of in front of a publisher.
USAGE
-----
python scripts/dump_engine_shapes.py
Run it whenever an engine module shape changes, then commit the updated
fixtures. Exit 0 = all samples engine-valid + written; exit 1 = a sample
is no longer engine-valid (fix the sample to match the new shape).
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
# Make `engine` importable when run as a bare script (mirrors the
# tests/conftest.py sys.path shim).
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from engine.modules.combine_handler import CombineHandler # noqa: E402
from engine.modules.constraint_handler import ConstraintHandler # noqa: E402
from engine.modules.derivation_handler import DerivationHandler # noqa: E402
from engine.modules.fixed_values_handler import FixedValuesHandler # noqa: E402
from engine.modules.wildcard_handler import WildcardHandler # noqa: E402
# Output dir, relative to repo root (this file lives in scripts/).
_OUT_DIR = (
Path(__file__).resolve().parent.parent
/ "src" / "validators" / "fixtures" / "engine-parity"
)
# Each entry: (handler, type-specific payload). The payload must pass the
# handler's validate_payload -- keep these as the MINIMAL real shape per
# subtype, including fields that the strip-tolerant validator must
# preserve (e.g. wildcard `is_null`).
_SAMPLES: dict[str, tuple[type, dict]] = {
"wildcard": (
WildcardHandler,
{
"var_binding": "mood",
"sub_categories": ["soft", "bold"],
"options": [
{"id": "opt00001", "value": "serene", "weight": 1, "sub_category": "soft"},
# Null option: the is_null contract. Must round-trip
# through the validator or install rebuilds it as an
# invalid empty-value option.
{"id": "opt00002", "value": "", "weight": 1, "sub_category": None, "is_null": True},
],
},
),
"fixed_values": (
FixedValuesHandler,
{"values": [{"id": "val00001", "name": "style", "value": "oil painting"}]},
),
"combine": (
CombineHandler,
{"template": "$mood $subject", "output_var": "scene", "input_vars": ["mood", "subject"]},
),
"derivation": (
DerivationHandler,
{
"rules": [
{
"id": "rule0001",
"branches": [
{
"condition": {
"var": "mood", "op": "equals", "value": "dramatic",
},
"action": {
"target_var": "accent", "mode": "replace",
"value": "cinematic lighting",
},
},
],
"else": {
"action": {
"target_var": "accent", "mode": "replace",
"value": "soft lighting",
},
},
},
],
},
),
"constraint": (
ConstraintHandler,
{
"source_wildcard_id": "src00001",
"target_wildcard_id": "tgt00001",
"exceptions": [],
"matrix": {"soft": {"bold": {"mode": "boost", "factor": 3}}},
},
),
}
def _engine_row(type_id: str, payload: dict) -> dict:
"""Wrap a type-specific payload in the canonical engine-row shape
the publisher ships (see manager/import-export/single-row-publish.ts:
buildModulePublishable)."""
return {
"id": f"parity{type_id[:2]}",
"type": type_id,
"name": f"parity-{type_id}",
"description": "",
"category_id": None,
"tags": [],
"is_favorite": False,
"payload": payload,
}
def main() -> int:
_OUT_DIR.mkdir(parents=True, exist_ok=True)
failures: list[str] = []
for type_id, (handler, payload) in _SAMPLES.items():
try:
# Engine authority: this raises if the sample is not a valid
# payload for the CURRENT engine handler.
handler.validate_payload(payload)
except Exception as exc: # noqa: BLE001 -- report any validation failure
failures.append(f"{type_id}: sample is no longer engine-valid: {exc}")
continue
row = _engine_row(type_id, payload)
out = _OUT_DIR / f"{type_id}.json"
out.write_text(json.dumps(row, indent=2) + "\n", encoding="utf-8")
print(f"[parity] wrote {out.relative_to(_OUT_DIR.parent.parent.parent)}")
if failures:
print("\n[parity] FAILED -- update the sample(s) to match the engine:", file=sys.stderr)
for f in failures:
print(f" - {f}", file=sys.stderr)
return 1
print(f"[parity] {len(_SAMPLES)} engine-valid fixtures written to {_OUT_DIR}")
return 0
if __name__ == "__main__":
raise SystemExit(main())