Scope Hierarchy
A Scope can declare an ordered list of parent scopes via its parents field. This forms a directed acyclic graph (DAG) that enables requirement inheritance: child scopes override parent definitions while still inheriting anything not explicitly changed.
Declaring parents
from fushinryu_model import Scope
base = Scope(id="base", name="Base", description="Shared requirements.")
extended = Scope(id="extended", name="Extended", description="Extended requirements.", parents=(base,))
The parents tuple is ordered: the first parent takes precedence over subsequent parents when the hierarchy is collapsed.
Collapsing the hierarchy
Scope.collapse() flattens the entire ancestry into a single Scope with no parents. The returned scope contains the merged result of every ancestor's user stories and acceptance criteria.
flat = extended.collapse()
# flat.parents == ()
# flat.user_stories contains inherited and overriding stories merged
Linearisation algorithm
collapse() determines merge order using DFS pre-order traversal with last-occurrence deduplication:
- The root scope is visited first (highest precedence).
- Parents are visited in order; each scope's entire subtree is traversed before moving to the next sibling.
- If a scope appears more than once (shared ancestor — the diamond pattern), only its last occurrence in the traversal is kept.
The result is a list ordered from highest to lowest merge precedence. Scopes are then merged from lowest to highest, so that higher-precedence scopes win.
Diamond example
A = Scope(id="a", name="A", description="Grandparent.")
B = Scope(id="b", name="B", description="Parent B.", parents=(A,))
C = Scope(id="c", name="C", description="Parent C.", parents=(A,))
D = Scope(id="d", name="D", description="Root.", parents=(B, C))
flat = D.collapse()
# Linearisation order (highest → lowest precedence): D, B, C, A
# A appears under both B and C but contributes only once, at the lowest precedence position.
Precedence order: D > B > C > A. B takes precedence over C because it is listed first in D.parents.
Cycle detection
collapse() raises ValueError with a "Cycle detected" message if the parent graph contains a cycle. Cycles are invalid and cannot be collapsed.