Wildcard + fixed_values surfaces are binding PRODUCERS, not consumers
— they don't read upstream \$var. Combine + derivation + assembler do
(template interpolation is their reason to exist).
Adds VarOutOfSurfaceError to engine.syntax.types + extends SurfaceKind
literal with "fixed_values". _resolve_var checks surface membership
in _VAR_SURFACES_ALLOWED frozenset; out-of-surface refs warn + render
literal \$name in lenient mode (existing default), raise in strict
mode (used by tests + dev mode).
Lenient strategy means workflows saved before this cycle keep
resolving — the warning surfaces in toast / conflict scanner without
crashing the queue.
Test fixture adjustments document the new contract:
- tests/syntax/test_resolve.py default surface flips wildcard -> combine
(these tests cover VAR resolution mechanics, not surface gating)
- tests/syntax/test_determinism.py corpus single_var + var_in_text
cases use surface=combine
- tests/engine/modules/test_wildcard_handler.py:
* test_resolve_var_substitution_in_option renamed to
test_resolve_var_in_option_renders_literal_warns + asserts new
lenient path
* test_resolve_pinned_mode_resolves_inline_syntax swaps \$color for
a {red|red} alternation that DOES resolve on wildcard surface
Surface gating tests live in tests/engine/syntax/test_resolve_var_surface.py
covering all 5 surfaces + lenient vs strict modes + internal-key skip.
85 lines
2.7 KiB
Python
85 lines
2.7 KiB
Python
"""Surface gating for $var token.
|
|
|
|
Combine, derivation, and assembler surfaces support $var reads.
|
|
Wildcard and fixed_values surfaces are binding PRODUCERS, not consumers
|
|
— $var reads in those surfaces warn + render literal in lenient mode,
|
|
raise VarOutOfSurfaceError in strict mode.
|
|
"""
|
|
import random
|
|
|
|
import pytest
|
|
|
|
from engine.syntax import resolve_text
|
|
from engine.syntax.types import VarOutOfSurfaceError
|
|
|
|
|
|
class _FakeCtx:
|
|
"""Minimal ResolveContext-shaped object for surface gating tests."""
|
|
|
|
def __init__(self, surface, strict=False, vars_=None):
|
|
self.surface = surface
|
|
self.strict = strict
|
|
self.developer_mode = False
|
|
self.max_ref_depth = 8
|
|
self.warnings = []
|
|
self._vars = vars_ or {}
|
|
self.rng = random.Random(0)
|
|
|
|
def get_var(self, name):
|
|
return self._vars.get(name)
|
|
|
|
def get_module(self, uuid):
|
|
return None
|
|
|
|
|
|
def test_var_resolves_on_combine_surface():
|
|
ctx = _FakeCtx("combine", vars_={"name": "world"})
|
|
assert resolve_text("hello $name", ctx) == "hello world"
|
|
|
|
|
|
def test_var_resolves_on_derivation_surface():
|
|
ctx = _FakeCtx("derivation", vars_={"name": "world"})
|
|
assert resolve_text("hello $name", ctx) == "hello world"
|
|
|
|
|
|
def test_var_resolves_on_assembler_surface():
|
|
ctx = _FakeCtx("assembler", vars_={"name": "world"})
|
|
assert resolve_text("hello $name", ctx) == "hello world"
|
|
|
|
|
|
def test_var_warns_on_wildcard_surface_lenient():
|
|
"""Wildcard surface: lenient strategy renders $name literal +
|
|
pushes a var_out_of_surface warning."""
|
|
ctx = _FakeCtx("wildcard", strict=False, vars_={"name": "world"})
|
|
result = resolve_text("hello $name", ctx)
|
|
assert result == "hello $name"
|
|
assert any(w.get("type") == "var_out_of_surface" for w in ctx.warnings)
|
|
|
|
|
|
def test_var_warns_on_fixed_values_surface_lenient():
|
|
ctx = _FakeCtx("fixed_values", strict=False, vars_={"name": "world"})
|
|
result = resolve_text("hello $name", ctx)
|
|
assert result == "hello $name"
|
|
assert any(w.get("type") == "var_out_of_surface" for w in ctx.warnings)
|
|
|
|
|
|
def test_var_raises_on_wildcard_surface_strict():
|
|
ctx = _FakeCtx("wildcard", strict=True)
|
|
with pytest.raises(VarOutOfSurfaceError):
|
|
resolve_text("hello $name", ctx)
|
|
|
|
|
|
def test_var_raises_on_fixed_values_surface_strict():
|
|
ctx = _FakeCtx("fixed_values", strict=True)
|
|
with pytest.raises(VarOutOfSurfaceError):
|
|
resolve_text("hello $name", ctx)
|
|
|
|
|
|
def test_internal_dunder_var_never_warns():
|
|
"""__-prefixed vars are engine bookkeeping; never warn even on
|
|
surfaces that disallow VAR."""
|
|
ctx = _FakeCtx("wildcard", strict=False)
|
|
result = resolve_text("$__wp_internal__", ctx)
|
|
assert result == ""
|
|
assert not any(w.get("type") == "var_out_of_surface" for w in ctx.warnings)
|