app.engine.dsl_validator module¶
Structural validator for strategy ASTs.
Runs synchronously and without I/O. Called on every create/update so a malformed AST never lands in the database, and called again by the simulate service as a cheap idempotent guard - the second call is fast because the AST has already been parsed by Pydantic into plain dict/list/ scalar values.
The validator enforces three things, in order:
Shape: every node has the required keys with the expected types (no surprise dict where a literal was expected, no missing
whenon a rule).Whitelist:
compare.op,arith.op,field.path, andnode.typemust all appear in the corresponding allow-list indsl_ast. The interpreter never invokesgetattron a node type string, but the validator is the first line of defence - by the time the AST reaches the handler table any unknown name has already been rejected withDslValidationError.Limits: a static node count and recursion depth are computed while walking. Both are bounded by
configs.DSL_MAX_NODESandconfigs.DSL_MAX_DEPTHrespectively, so an attacker can’t smuggle in a billion-node tree that would otherwise OOM the API.
Auto-assigned IDs: nodes are allowed to omit id (Blockly will provide
them; hand-written JSON often skips them). The validator assigns a
deterministic "<parent_id>.<type>.<index>" slug so the interpreter
trace and any future error messages have a stable correlation key.
- app.engine.dsl_validator.validate_ast(ast, *, max_depth=None, max_nodes=None)[source]¶
Validate
astin place (mutates only to fill in missingidfields).Returns the same dict reference for convenience so callers can write
ast = validate_ast(ast). RaisesDslValidationErrorwith a descriptivedetailon any structural failure.The limits default to
configs.DSL_*but tests can override them to exercise the rejection paths without sending a megabyte of JSON.- Parameters:
ast (Any)
max_depth (int | None)
max_nodes (int | None)
- Return type:
Dict[str, Any]