high priority medium complexity infrastructure pending backend specialist Tier 2

Acceptance Criteria

validateParentChildEdge(parentId, childId) returns a ValidationResult.failure with a localised error message if adding the edge would create a cycle in the hierarchy tree
validateUserDepthLimit(userId, targetUnitId) returns failure with message 'Maximum 5 chapter assignments reached' when the user already holds 5 chapter-level assignments
validateLevelTypeConsistency(unitId, assignedLevelType) returns failure when the level type does not match the node's depth position (e.g., a 'region' node cannot be placed as a child of another 'region')
All validation methods are pure functions with no side effects — they read from the provided tree snapshot and return results synchronously
User-facing error messages are in Norwegian Bokmål and English (determined by app locale), are concise (under 80 characters), and actionable
The validator is invoked before any Supabase write in the hierarchy editor; no invalid state is ever persisted
validateParentChildEdge uses an iterative ancestor-traversal algorithm (not recursive) to avoid stack overflows on deep trees
Unit tests cover: valid edge, direct cycle (A→B→A), indirect cycle (A→B→C→A), depth limit at exactly 5, depth limit at 6, valid level type, invalid level type at each depth level

Technical Requirements

frameworks
Flutter
Dart (core)
apis
Supabase REST (read-only ancestor queries for cycle detection)
data models
HierarchyNode
LevelType (enum: national, region, county, local, chapter)
ValidationResult
UserUnitAssignment
performance requirements
Cycle detection on a tree of 1,400 nodes must complete in under 200ms
Validator operates on an in-memory tree snapshot passed by the caller — no live DB queries during validation
Depth limit check is O(1) using precomputed assignment count from assignment repository
security requirements
Validation runs client-side for UX only; authoritative validation is enforced by Supabase RLS and DB constraints — client validator is a first line of defence, not the last
Validator must not expose internal node IDs or tree structure in error messages displayed to end users

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Implement as a stateless service class HierarchyStructureValidator with only static or instance methods that accept a HierarchyTreeSnapshot value object. For cycle detection, use iterative DFS: maintain a visited set and traverse ancestors of the proposed parent; if the childId appears in the ancestor chain the edge creates a cycle. Store LevelType ordering as a const List depthOrder = [national, region, county, local, chapter] and validate a node's type by comparing its depth index against this list. Define a sealed ValidationResult type: ValidationResult.success() and ValidationResult.failure(String userMessage, String?

debugDetail). The validator should accept the full tree snapshot as a parameter rather than querying Supabase directly — callers are responsible for providing a fresh snapshot, which keeps the validator fast, testable, and free of async complexity.

Testing Requirements

Unit tests (flutter_test) are the primary testing vehicle. Test suite must include: (1) cycle detection — at least 6 cases covering no cycle, direct cycle, indirect multi-hop cycle, and self-reference; (2) depth limit — boundary tests at 4, 5 (boundary pass), and 6 (boundary fail); (3) level type consistency — one valid and one invalid case per LevelType enum value; (4) empty tree edge case. All tests must use pure in-memory tree fixtures, no Supabase calls. Aim for 100% statement coverage on HierarchyStructureValidator.

Integration test (optional): wire validator into a mock hierarchy editor flow and assert that a cycle-creation attempt is blocked at the UI layer.

Component
Hierarchy Node Editor Screen
ui medium
Epic Risks (3)
high impact medium prob technical

Recursive aggregation queries across four hierarchy levels (national → region → local) with 1,400 leaf nodes may be too slow for real-time dashboard requests, exceeding the 200ms target and causing spinner timeouts.

Mitigation & Contingency

Mitigation: Implement aggregation as a Supabase RPC using a single recursive CTE rather than multiple round-trip queries. Pre-compute aggregations nightly via a scheduled Edge Function and cache results. For real-time needs, aggregate only the immediate subtree on demand.

Contingency: Surface a 'Refreshing...' indicator and serve stale cached aggregations immediately. Queue an async recalculation and push updated data via Supabase Realtime when ready, avoiding blocking the admin dashboard.

medium impact medium prob scope

The 5-chapter limit and primary-assignment constraint are NHF-specific. Applying these rules globally may break HLF and Blindeforbundet configurations where different limits apply, requiring per-organization configuration that was not initially scoped.

Mitigation & Contingency

Mitigation: Make the maximum assignment count a configurable value stored in the organization's feature-flag or settings table rather than a hardcoded constant. Design the assignment service to read this limit at runtime per organization.

Contingency: Default the limit to a high value (e.g., 100) for organizations other than NHF, effectively making it non-restrictive, while keeping the enforcement logic intact for when per-org configuration is fully implemented.

medium impact low prob technical

The searchable parent dropdown in HierarchyNodeEditor must search across up to 1,400 units efficiently. Client-side filtering of the full hierarchy may be slow; server-side search adds complexity and latency.

Mitigation & Contingency

Mitigation: Use the in-memory hierarchy cache as the search corpus — since the cache already holds the flat unit list, client-side filtering with a debounced input is sufficient and avoids extra Supabase calls. Pre-build a search index on cache load.

Contingency: Cap the dropdown to showing the 50 most recently accessed units by default, with a 'search all' option that triggers a server-side full-text query. This keeps the common case fast while supporting edge cases.