User-reported bug: a variable marked internal in an upstream Context node could not be read by downstream Combine / Derivation modules — they saw it as missing. The previous design treated the PIPELINE_CONTEXT socket as the public boundary and stripped both engine `__` keys AND user-flagged internal vars at socket emit. Correct semantic: internal = "don't render as standalone $var in the final prompt." It does NOT mean "don't propagate through context." Downstream nodes still need to read internal vars to compose them into other vars (e.g. a Combine builds `$subject` from an internal `$style` + a public `$pose`). Only the final PromptAssembler must skip internal vars at template substitution time. Implementation: - engine/context.py: split the filter into two helpers. `strip_engine_internals` drops only `__`-prefixed engine keys — used at the socket boundary. `strip_internals` keeps the original dual-layer behavior (engine + user-flagged) — used at the prompt- render boundary. - wp_nodes/types.py: socket emit now uses `strip_engine_internals`, so user-flagged internal vars cross node boundaries as regular context keys. `__wp_internal_flags__` is added to `_CROSS_NODE_INTERNAL_KEYS` so downstream PromptAssemblers can still know which vars to filter at render. - wp_nodes/assembler_node.py: before resolving variables, merges the cross-node `__wp_internal_flags__` back into a copy of the socket context and calls `strip_internals` — drops both engine keys and user-flagged internal vars. Net effect on the rendered prompt: unchanged from the user's perspective (internal vars still don't appear as standalone $vars). - tests/test_context.py: regression tests for the new helper + propagation behavior.
58 lines
1.9 KiB
Python
58 lines
1.9 KiB
Python
"""Unit tests for engine.context."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from engine.context import strip_engine_internals, strip_internals
|
|
|
|
|
|
class TestStripEngineInternals:
|
|
def test_drops_double_underscore_keys(self):
|
|
ctx = {"__wp_node_seed__": 42, "name": "A"}
|
|
assert strip_engine_internals(ctx) == {"name": "A"}
|
|
|
|
def test_keeps_user_flagged_internal_vars(self):
|
|
# User-marked-internal vars stay at the socket boundary so
|
|
# downstream modules can read them. Only the prompt-render
|
|
# boundary (PromptAssembler) filters them via strip_internals.
|
|
ctx = {
|
|
"scratch": "internal value",
|
|
"name": "A",
|
|
"__wp_internal_flags__": {"scratch": True},
|
|
}
|
|
result = strip_engine_internals(ctx)
|
|
assert result == {"scratch": "internal value", "name": "A"}
|
|
|
|
def test_returns_new_dict(self):
|
|
ctx = {"name": "A", "__seed": 1}
|
|
result = strip_engine_internals(ctx)
|
|
result["name"] = "B"
|
|
assert ctx["name"] == "A"
|
|
|
|
|
|
class TestStripInternals:
|
|
def test_empty(self):
|
|
assert strip_internals({}) == {}
|
|
|
|
def test_only_user_keys(self):
|
|
ctx = {"name": "A", "count": 3}
|
|
assert strip_internals(ctx) == ctx
|
|
|
|
def test_only_internal_keys(self):
|
|
ctx = {"__wp_node_seed__": 42, "__wp_trace__": []}
|
|
assert strip_internals(ctx) == {}
|
|
|
|
def test_mixed(self):
|
|
ctx = {"name": "A", "__wp_node_seed__": 42, "count": 3}
|
|
assert strip_internals(ctx) == {"name": "A", "count": 3}
|
|
|
|
def test_returns_new_dict(self):
|
|
ctx = {"name": "A", "__seed": 1}
|
|
stripped = strip_internals(ctx)
|
|
stripped["name"] = "B"
|
|
assert ctx["name"] == "A"
|
|
|
|
def test_single_underscore_prefix_not_stripped(self):
|
|
# Only the double-underscore convention is internal.
|
|
ctx = {"_private": 1, "__internal": 2, "normal": 3}
|
|
assert strip_internals(ctx) == {"_private": 1, "normal": 3}
|