fix: KB tree builder — post-build repair, validation gate, and root ID handling

- Add _repair_tree() post-build pass that fixes structural issues caused by
  placed-set race conditions: demotes decisions with <2 children to actions,
  hoists orphaned children as siblings, converts dead-end actions to solutions
- Add validation gate at commit endpoint — rejects trees that fail validation
  with a 422 and descriptive error messages instead of saving invalid trees
- Update test fixtures to create valid multi-node tree structures
- Frontend: initialize currentNodeId from tree's actual root ID instead of
  hardcoded 'root', fixing "Invalid tree structure" for KB-generated trees

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-03-11 02:53:55 -04:00
parent c1fb2f180c
commit 2f23141daf
2 changed files with 171 additions and 9 deletions

View File

@@ -293,14 +293,42 @@ class TestCommit:
kb_import.status = "ready"
kb_import.source_metadata = {"_conversion": {"title": "Test Flow", "description": "Test"}}
node = KBImportNode(
kb_import_id=kb_import.id,
node_order=0,
node_type="question",
content={"original_id": "root", "question": "Test question?", "options": []},
confidence_score=0.9,
)
test_db.add(node)
# Build a valid tree: root decision with 2 branches leading to solutions
nodes_data = [
KBImportNode(
kb_import_id=kb_import.id, node_order=0, node_type="question",
content={
"original_id": "root", "question": "What is the issue?",
"options": [
{"id": "opt-root-0", "label": "Option A", "next_node_id": "action-a"},
{"id": "opt-root-1", "label": "Option B", "next_node_id": "action-b"},
],
},
confidence_score=0.9,
),
KBImportNode(
kb_import_id=kb_import.id, node_order=1, node_type="action",
content={"original_id": "action-a", "question": "Try fix A", "description": "Do thing A", "next_node_id": "solution-a"},
confidence_score=0.9,
),
KBImportNode(
kb_import_id=kb_import.id, node_order=2, node_type="action",
content={"original_id": "action-b", "question": "Try fix B", "description": "Do thing B", "next_node_id": "solution-b"},
confidence_score=0.9,
),
KBImportNode(
kb_import_id=kb_import.id, node_order=3, node_type="resolution",
content={"original_id": "solution-a", "question": "Resolved via A", "description": "Issue fixed by A"},
confidence_score=0.9,
),
KBImportNode(
kb_import_id=kb_import.id, node_order=4, node_type="resolution",
content={"original_id": "solution-b", "question": "Resolved via B", "description": "Issue fixed by B"},
confidence_score=0.9,
),
]
for n in nodes_data:
test_db.add(n)
await test_db.commit()
# Commit