Files
BlackVortexAIandClaude Fable 5.1 95ace90041 Add BV Text writing assistance, preparation caches and the 1.4.4 hardening
Features: BV Text node with management-gated writing assistance (translate,
improve, undo, prompt profiles); reuse of prepared MODEL/CLIP state for Krea 2
and Anima LLLite consumers; Regional refactoring.

Security boundaries: one wall-clock budget per Remote LLM request enforced by
a socket-closing watchdog across connect, TLS handshake, headers, chunk framing
and body; writing assistance uses the profile's configured timeout; a shared
bounded JSON reader with route-specific limits on every JSON route; endpoint
binding negative tests for the assist path.

Release and packaging: SECURITY.md; bundled-library notices and a readable
bundle header; Registry scan replica as a hard test with a pinned count; a
packaged-file-set test; publish only from a validated push to main with
provenance checks, pinned comfy-cli, an input manifest and a byte-exact
comparison of the stored archive; npm audit and a whole js/ rebuild gate in
Validate; design references, brand sources and a type declaration excluded
from the package. README and changelog for 1.4.4.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 16:51:38 +02:00

295 lines
10 KiB
Python

import ast
import json
from pathlib import Path
import unittest
ROOT = Path(__file__).parents[1]
EXPECTED_NODE_NAMES_BY_CATEGORY = {
"__hidden__": {
"BV Detailer Loop Advance (internal)",
"BV Detailer Loop Result (internal)",
"BV Detailer While End (internal)",
"BV Detailer While Start (internal)",
"BV LUT Loop Advance (Prototype internal)",
"BV LUT Loop Result (Prototype internal)",
"BV LUT While End (Prototype internal)",
"BV LUT While Start (Prototype internal)",
},
"🌀 BV Node Pack/advanced/integrations": {"BV Detector Binding"},
"🌀 BV Node Pack/control": {"BV Control Center"},
"🌀 BV Node Pack/image": {"BV Image Dimensions"},
"🌀 BV Node Pack/latent": {
"BV Empty Latent Random Ratio",
"BV Latent Random Rotate 90",
},
"🌀 BV Node Pack/latent/Deprecated Legacy Nodes": {
"BV Latent Random Aspect Ratio",
},
"🌀 BV Node Pack/pipe": {"BV Smart Pipe", "BV Smart Pipe Merge"},
"🌀 BV Node Pack/pipe/Deprecated Legacy Nodes": {
"BV Pipe",
"BV Pipe Config",
},
"🌀 BV Node Pack/prompting": {
"BV Prompt AST Debug",
"BV Prompt Category Switch",
"BV Prompt Decode",
"BV Prompt Encode",
},
"🌀 BV Node Pack/regional/core": {
"BV Regional Debug",
"BV Regional Deconstructor",
"BV Regional Mask Render",
"BV Regional Prompt",
"BV Regional Prompt Extract",
"BV Regional Select",
},
"🌀 BV Node Pack/regional/detailer": {
"BV Detailer Loop End",
"BV Detailer Loop Job Resolver",
"BV Detailer Loop Start",
"BV Detector Registry",
"BV Regional Detailer Plan",
},
"🌀 BV Node Pack/regional/integrations/Impact Pack": {
"BV Detailer Loop Detect to SEGS (Impact)",
"BV Regional Detailer Mask",
},
"🌀 BV Node Pack/regional/LoRA": {"BV LoRA Registry", "BV Regional LoRA"},
"🌀 BV Node Pack/regional/LoRA/Manual Chains (Optional)": {
"BV LoRA Stack Collector",
"BV Named LoRA Stack",
},
"🌀 BV Node Pack/regional/LUT": {
"BV Apply LUT",
"BV Impact Detector Mask",
"BV LUT Loop End",
"BV LUT Loop Job Resolver",
"BV LUT Loop Start",
"BV LUT Registry",
"BV Regional LUT Plan",
},
"🌀 BV Node Pack/regional/LUT/Manual Chains (Optional)": {
"BV LUT Job",
"BV LUT Loader",
},
"🌀 BV Node Pack/regional/models/Anima": {
"BV Regional Anima Adapter",
"BV Regional Anima Conditioning",
"BV Regional Anima LLLite",
"BV Regional Color Control Image",
},
"🌀 BV Node Pack/regional/models/FLUX.2 Klein 9B": {
"BV Regional FLUX.2 Klein 9B Attention",
},
"🌀 BV Node Pack/regional/models/Generic": {
"BV Regional Native Conditioning",
},
"🌀 BV Node Pack/regional/models/Krea 2": {
"BV Regional Krea 2 Attention",
},
"🌀 BV Node Pack/regional/models/SDXL": {
"BV Regional SDXL Attention",
},
"🌀 BV Node Pack/regional/models/Z-Image": {
"BV Regional Z-Image Attention",
},
"🌀 BV Node Pack/regional/output": {
"BV Regional Image Save",
"BV Regional Image Send",
},
"🌀 BV Node Pack/regional/prompt enhancement": {
"BV Apply Regional Enhancement",
"BV Comfy CLIP LLM Provider",
"BV Regional Prompt Enhancer",
"BV Remote LLM Provider",
},
"🌀 BV Node Pack/subgraph": {
"BV Dynamic Combo",
"BV Subgraph Divider",
"BV Subgraph Heading",
"BV Subgraph Spacer",
},
"🌀 BV Node Pack/utils": {
"BV Hex Color To Int",
"BV Inspect Any",
"BV Seed",
"BV Text",
"BV Text Log Writer",
},
}
def collect_literal_node_mapping_keys() -> set[str]:
names: set[str] = set()
for path in (ROOT / "py" / "nodes").glob("*.py"):
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
for statement in tree.body:
if not isinstance(statement, ast.Assign):
continue
if not any(
isinstance(target, ast.Name)
and target.id == "NODE_CLASS_MAPPINGS"
for target in statement.targets
):
continue
if not isinstance(statement.value, ast.Dict):
continue
for key in statement.value.keys:
if isinstance(key, ast.Constant) and isinstance(key.value, str):
names.add(key.value)
return names
def resolve_string_expression(
expression: ast.expr,
constants: dict[str, str],
) -> str | None:
if isinstance(expression, ast.Constant) and isinstance(expression.value, str):
return expression.value
if isinstance(expression, ast.Name):
return constants.get(expression.id)
if isinstance(expression, ast.JoinedStr):
parts: list[str] = []
for value in expression.values:
if isinstance(value, ast.Constant) and isinstance(value.value, str):
parts.append(value.value)
elif isinstance(value, ast.FormattedValue):
resolved = resolve_string_expression(value.value, constants)
if resolved is None:
return None
parts.append(resolved)
else:
return None
return "".join(parts)
return None
def collect_declared_node_categories(filename: str) -> dict[str, str]:
path = ROOT / "py" / "nodes" / filename
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
constants: dict[str, str] = {}
class_categories: dict[str, str] = {}
node_classes: dict[str, str] = {}
for statement in tree.body:
if isinstance(statement, ast.Assign):
value = resolve_string_expression(statement.value, constants)
if value is not None:
for target in statement.targets:
if isinstance(target, ast.Name):
constants[target.id] = value
if any(
isinstance(target, ast.Name) and target.id == "NODE_CLASS_MAPPINGS"
for target in statement.targets
) and isinstance(statement.value, ast.Dict):
for key, mapped_class in zip(
statement.value.keys,
statement.value.values,
strict=True,
):
if (
isinstance(key, ast.Constant)
and isinstance(key.value, str)
and isinstance(mapped_class, ast.Name)
):
node_classes[key.value] = mapped_class.id
elif isinstance(statement, ast.ClassDef):
for class_statement in statement.body:
if not isinstance(class_statement, ast.Assign):
continue
if not any(
isinstance(target, ast.Name) and target.id == "CATEGORY"
for target in class_statement.targets
):
continue
category = resolve_string_expression(class_statement.value, constants)
if category is not None:
class_categories[statement.name] = category
return {
node_name: class_categories[class_name]
for node_name, class_name in node_classes.items()
if class_name in class_categories
}
class NodeListTests(unittest.TestCase):
def test_node_list_matches_all_declared_node_mapping_keys(self):
node_list = json.loads(
(ROOT / "node_list.json").read_text(encoding="utf-8")
)
self.assertEqual(set(node_list), collect_literal_node_mapping_keys())
def test_node_list_descriptions_are_non_empty_strings(self):
node_list = json.loads(
(ROOT / "node_list.json").read_text(encoding="utf-8")
)
self.assertTrue(node_list)
for node_name, description in node_list.items():
with self.subTest(node=node_name):
self.assertIsInstance(description, str)
self.assertTrue(description.strip())
def test_deprecated_nodes_live_below_their_product_families(self):
from py.nodes.bv_latent_random_ratio import BVLatentRandomAspectRatio
from py.nodes.bv_pipe import BVPipeNode
from py.nodes.bv_pipe_config import BVPipeConfigNode
self.assertEqual(
BVPipeNode.CATEGORY,
"🌀 BV Node Pack/pipe/Deprecated Legacy Nodes",
)
self.assertEqual(
BVPipeConfigNode.CATEGORY,
"🌀 BV Node Pack/pipe/Deprecated Legacy Nodes",
)
self.assertEqual(
BVLatentRandomAspectRatio.CATEGORY,
"🌀 BV Node Pack/latent/Deprecated Legacy Nodes",
)
def test_optional_manual_chains_live_below_their_product_families(self):
categories: dict[str, str] = {}
for path in (ROOT / "py" / "nodes").glob("*.py"):
categories.update(collect_declared_node_categories(path.name))
expected_categories = {
node_name: category
for category, node_names in EXPECTED_NODE_NAMES_BY_CATEGORY.items()
for node_name in node_names
}
self.assertEqual(categories, expected_categories)
manual_chain_names = {
name
for name, category in categories.items()
if category.endswith("/Manual Chains (Optional)")
}
self.assertEqual(
manual_chain_names,
{
"BV Named LoRA Stack",
"BV LoRA Stack Collector",
"BV LUT Job",
"BV LUT Loader",
},
)
def test_m0_feasibility_nodes_are_not_publicly_registered(self):
from fixtures.bv_regional_v3_m0 import (
NODE_CLASS_MAPPINGS,
NODE_DISPLAY_NAME_MAPPINGS,
)
self.assertEqual(NODE_CLASS_MAPPINGS, {})
self.assertEqual(NODE_DISPLAY_NAME_MAPPINGS, {})
node_list = json.loads((ROOT / "node_list.json").read_text(encoding="utf-8"))
self.assertFalse(any(name.startswith("BV M0 Fake ") for name in node_list))
if __name__ == "__main__":
unittest.main()