1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
use Uuid;
use GraphError;
// Note on the `Uuid` import: only the object-safety smoke tests use
// it as a concrete binding for the `NodeId` associated type. The
// traits themselves are now fully generic over `NodeId`.
/// Trait for entities that can participate in a graph
///
/// Implemented by domain entities like Card, Sprint, Board, etc.
/// Provides a unique identifier for graph node operations.
/// Minimal contract every graph data structure satisfies.
///
/// Direction-agnostic: only methods that make sense for both directed
/// and undirected graphs live here. Directional access (`outgoing` /
/// `incoming`) lives on [`Directed`]; undirected neighbour access lives
/// on [`Undirected`]. A type that needs both must pick the right
/// subtype — there is no way to ask an [`Undirected`] graph for
/// directed semantics and silently get a wrong answer.
///
/// Object-safe with an explicit `NodeId` binding, e.g.
/// `&dyn Graph<NodeId = Uuid>`.
/// Directed-graph vocabulary: distinct outgoing and incoming neighbour
/// sets. Implementations preserve edge direction; calling
/// `outgoing(node)` returns successors, `incoming(node)` returns
/// predecessors.
///
/// Only types that genuinely encode direction implement this. An
/// [`Undirected`] graph cannot accidentally satisfy this trait by
/// returning the same set for both methods.
/// Undirected-graph vocabulary: a single neighbour set per node.
/// Calling `neighbors(node)` returns every node connected to `node` by
/// any edge, ignoring orientation. Implementations that genuinely lack
/// edge direction expose only this surface — directed callers must
/// require [`Directed`] explicitly.
/// Node-keyed cascade operations: archive, unarchive, and hard-remove
/// every edge involving a given node.
///
/// Strictly mutating. Distinct from [`EdgeSet`] (which is read-only)
/// because the two surfaces are independent — a caller that just
/// counts edges doesn't need cascade authority, and a caller doing
/// soft-deletes doesn't need to ask for counts. Splitting them keeps
/// each trait's name accurate to its single purpose.
///
/// Generic over `Graph::NodeId` — no longer pinned to `Uuid`. The
/// kanban domain still uses `Uuid` for every edge today; a future
/// heterogeneous-entity graph can pick another identity (e.g. a
/// discriminated `(EntityKind, Uuid)` newtype) without touching this
/// trait or its callers.
/// Set-like read access over a graph's edges: size, active-only size,
/// and membership.
///
/// Aligns with stdlib `HashSet` / `BTreeSet` vocabulary — `len`,
/// `contains` — so a reader who has seen those once knows the shape
/// of this trait immediately. Strictly read-only; lives separately
/// from [`Cascadable`] so a generic consumer can require only the
/// surface it actually uses, with no implicit mutation authority
/// leaking into inspection code.
///
/// Generic over `Graph::NodeId`, same reasoning as `Cascadable`.
///
/// # Active vs archived
///
/// `is_empty` and `contains` both consult the **active** subset only,
/// matching `Graph::contains_edge` and the principle of least
/// surprise: a caller asking "is this here right now?" wants the
/// current view, not the archive log. `contains_archived` is the
/// explicit escape hatch for callers that need to consult both. The
/// (active + archived) total is still available via `len`.