KB-1657

IU Core MVP — 05 Parent-Child Multi-Level Model

4 min read Revision 1
dieu44iu-core-mvpparent-childmulti-levelcycle-preventionv0.62026-05-21

IU Core MVP — 05 Parent-Child Multi-Level Model

Date: 2026-05-21 · Authored in 003_parent_child_constraints.sql, 005_* helpers, and cutter_agent/iu_core/tree.py.

Model

  • Primary adjacency: information_unit.parent_or_container_ref uuid (live, unchanged, NOT altered).
  • Durable cache: iu_tree_path — one row per IU with root_unit_id, parent_id, depth, path_ids uuid[], path_addresses text[], sibling_order, path_hash UNIQUE, valid_from/valid_to.
  • Live projection: view v_iu_tree — recursive CTE over the adjacency pointer; usable immediately without iu_tree_path being populated.

Behavior rules (encoded)

  • Root: parent_or_container_ref IS NULLdepth=0, root_unit_id=unit_id, parent_id IS NULL. CHECK iu_tree_path_root_chk enforces (depth=0)=(parent_id IS NULL).
  • Child: parent must exist (FK), must not be retired/deleted, depth>=1.
  • Multi-level: descendants inherit root_unit_id and extend path_ids. Max depth is config-driven, not hardcoded (NEEDS_RULING R6).
  • Sibling ordering: sort_order, then canonical_address, then created_at (v_iu_tree carries sibling_order). sort_order uniqueness deferred (R5).
  • iu_tree_path integrity CHECKs: depth>=0; array_length(path_ids)=depth+1; path_ids and path_addresses equal length; first element = root, last = unit; unit_id <> parent_id.

Cycle prevention

Three layers:

  1. View-level: v_iu_tree recursive CTE has NOT c.id = ANY(w.path_ids) — a corrupt graph cannot make the view loop forever.
  2. SQL helper (005): fn_iu_tree_is_descendant(ancestor,node) + fn_iu_tree_assert_acyclic(unit,new_parent) — raises check_violation when a reparent targets self or a descendant. To be called by the reparent path / a future BEFORE trigger.
  3. Python mirror (tree.py): detect_cycle, is_descendant, assert_acyclic_reparent — pure, used by callers + tests.

Reparent rules

  • Reparent to root (NULL) is always safe.
  • A unit may not be its own parent.
  • New parent may not be a descendant of the unit (would create a cycle).
  • Reparent never overwrites history silently: it emits IU_REPARENTED and creates an iu_relation provenance row + an iu_structure_operation of type reparent_piece.
  • Moving an enacted unit must create a new version/change-set or be explicitly blocked (NEEDS_RULING — defaults to block until ruled).

Parent ↔ child lifecycle impact

  • No hard delete: a parent is deprecated/retired, never DELETEd. Deprecating a parent with active children requires an explicit dependency_action (block_if_children | cascade_deprecate | reparent_children).
  • A child content change emits IU_SQL_LINK_CHANGED / lineage events so the parent's verification can be re-evaluated.
  • Propagation contract: parent events may fan out to children only when the relation/link policy permits inheritance; missing/ambiguous relation fails closed (no workflow mutation).

Rollback / compensation

  • iu_tree_path rows are time-bounded (valid_to); rebuild is always possible from parent_or_container_ref.
  • Reparent compensation = a new reparent_piece operation back to the prior parent; the original op row moves to rolled_back.
  • Rollback file rollback/003_* drops the view freely and refuses to drop a non-empty iu_tree_path.

Helper implemented + tested

cutter_agent/iu_core/tree.py — 8 tree tests in test_iu_core_ddl.py (compute_path, is_descendant, detect_cycle clean/dirty, reparent self/ descendant blocked, reparent-to-root allowed, sibling-subtree allowed).

Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.6-iu-core-mvp-pg-native-parent-child-structure-ops-authoring/05-parent-child-multi-level-model.md