@@ -74,8 +74,9 @@ type GraphResultNode struct {
7474 // the value it pivots on.
7575 //
7676 // - "match": a value two or more records share on a field an organization declared as
77- // meaningful. Type is the relation's label, Id the shared value. Its edges are the
78- // records carrying it, so a value shared by n records costs n edges rather than n².
77+ // meaningful. Type is the relation's group id, Id the shared value, so relations sharing
78+ // a group converge on one connector whatever each is labelled. Its edges are the records
79+ // carrying it, so a value shared by n records costs n edges rather than n².
7980 //
8081 // - "link": the records hanging off one record through a single data-model link, when
8182 // there are too many of them to pull in. Type is the link's name, Id the value the
@@ -94,12 +95,23 @@ type GraphResultNode struct {
9495 Metadata GraphResultNodeMetadata
9596}
9697
98+ // GraphResultNodeMetadata is what is known about a node beyond its identity: the label to show
99+ // it under, and — for a record — the risk it carries.
97100type GraphResultNodeMetadata struct {
98101 Index int
99- // Label is the record's caption: the value it carries on the field its table declares as
100- // its caption field. It is empty on a connector, which is not a record, and on a record
101- // whose table declares no caption field.
102- Label string
102+
103+ // Label is what to show the node under, and where it comes from depends on what the node is:
104+ //
105+ // - on a record, its caption: the value it carries on the field its table declares as its
106+ // caption field. Empty when its table declares no such field.
107+ // - on a connector, what to call the relationship: the relation group's label for a "match"
108+ // one, the link's name for a "link" one. There it is deliberately not an identity — two
109+ // independent groups may well be labelled the same — so Type still carries the group id,
110+ // and Type/Id is what edges refer to.
111+ Label string
112+
113+ // RiskLevel and Tags come from the records' own scoring, so they are only ever set on a
114+ // record node: a connector is not a record and has nothing to score.
103115 RiskLevel int
104116 Tags []uuid.UUID
105117}
@@ -122,19 +134,22 @@ type GraphWalkOptions struct {
122134 EndTypes []string
123135 Degrees int
124136 SkipSameFieldRelations bool
125- SameFieldRelations []string
137+ SameFieldRelations []uuid. UUID
126138}
127139
128140// GraphRelation declares that equal values of two (record type, field) endpoints connect the
129141// records carrying them, even though no link exists between those records. An organization
130142// defines its own relations against the tables and fields of its own data model.
131143//
132144// Relations are one-to-one: a group of three endpoints that should all count as sharing a
133- // value is expressed as three relations (A<->B, B<->C, C<->A). Relations sharing a Label
134- // converge on the same connector node, so such a group still renders as a single star.
145+ // value is expressed as three relations (A<->B, B<->C, C<->A). Relations sharing a GroupId
146+ // converge on the same connector node, so such a group still renders as a single star. The
147+ // creation path keeps Label consistent across a group, but GroupId — not Label — is what
148+ // identifies it: unlike a label, it survives a rename without splitting or merging groups.
135149type GraphRelation struct {
136150 Id uuid.UUID
137151 OrgId uuid.UUID
152+ GroupId uuid.UUID
138153 Label string
139154 LeftType string
140155 LeftField string
@@ -263,6 +278,7 @@ func GraphIndexedFields(dataModel DataModel, relations []GraphRelation) map[stri
263278// database.
264279type CreateGraphRelation struct {
265280 OrgId uuid.UUID
281+ GroupId uuid.UUID
266282 Label string
267283 LeftType string
268284 LeftField string
0 commit comments