From 566306e4a6ac34372c1d522d377f11ecba6cf603 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Wed, 18 Feb 2026 01:58:24 -0500 Subject: [PATCH] feat: allow 'answer' type in tree drafts, block on publish Co-Authored-By: Claude Sonnet 4.6 --- backend/app/core/tree_validation.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/backend/app/core/tree_validation.py b/backend/app/core/tree_validation.py index de562889..a0c8652d 100644 --- a/backend/app/core/tree_validation.py +++ b/backend/app/core/tree_validation.py @@ -53,6 +53,13 @@ def validate_tree_structure(tree_structure: dict[str, Any]) -> tuple[bool, list[ if "children" in tree_structure: _validate_children(tree_structure["children"], "root.children", errors) + # Block publish if any answer placeholder nodes remain + if _has_answer_nodes(tree_structure): + errors.append({ + "field": "tree_structure", + "message": "Answer placeholders must be resolved to a node type before publishing." + }) + return len(errors) == 0, errors @@ -89,6 +96,10 @@ def _validate_node(node: dict[str, Any], path: str, errors: list[dict[str, str]] "message": "Solution nodes must have a non-empty solution" }) + elif node_type == "answer": + # Answer nodes are draft-only placeholders — no structural validation needed + pass + else: errors.append({ "field": f"{path}.type", @@ -115,6 +126,16 @@ def _validate_children(children: list[dict[str, Any]], path: str, errors: list[d _validate_children(child["children"], f"{child_path}.children", errors) +def _has_answer_nodes(node: dict[str, Any]) -> bool: + """Recursively check if any node in the tree has type 'answer'.""" + if node.get("type") == "answer": + return True + for child in node.get("children", []): + if _has_answer_nodes(child): + return True + return False + + # --- Procedural Tree Validation --- VALID_STEP_TYPES = {"procedure_step", "procedure_end", "section_header"}