@@ -663,45 +663,35 @@ int64_t cbm_gbuf_upsert_node(cbm_gbuf_t *gb, const char *label, const char *name
663663 (strcmp (existing -> label , "Project" ) == 0 || strcmp (existing -> label , "Folder" ) == 0 )) {
664664 return existing -> id ;
665665 }
666- /* Same-QN arrival. Two distinct cases:
667- *
668- * 1) The SAME source entity re-upserted (identity fields match) —
669- * refresh in place, exactly as before.
670- *
671- * 2) A DIFFERENT source entity sharing the QN. In C a struct, a
672- * function and a macro can all carry one name and the QN scheme
673- * does not encode the entity kind, so they collide here. The old
674- * code let the LAST arrival overwrite — under parallel extraction
675- * the merge order varies run to run, so WHICH entity survived
676- * (label/name/lines/props) flickered (xfs: Function-node count
677- * 4998 vs 5015 across two runs), and every order-sensitive
678- * consumer downstream (semantic vectors, LSH, near-threshold
679- * scores) inherited the flicker. Pick the survivor by a canonical
680- * CONTENT rule instead — lexicographically smallest
681- * (label, file_path, start_line, name) — so the outcome is a pure
682- * function of the colliding entities, not of scheduling. Exactly
683- * one entity is dropped, as one always was; kind-disambiguated
684- * QNs (the real cure) are tracked as a follow-up. */
685- bool same_entity = existing -> label && label && strcmp (existing -> label , label ) == 0 &&
686- existing -> file_path && file_path &&
687- strcmp (existing -> file_path , file_path ) == 0 &&
688- existing -> start_line == start_line && existing -> name && name &&
689- strcmp (existing -> name , name ) == 0 ;
690- if (!same_entity ) {
691- int c = strcmp (label ? label : "" , existing -> label ? existing -> label : "" );
692- if (c == 0 ) {
693- c = strcmp (file_path ? file_path : "" ,
694- existing -> file_path ? existing -> file_path : "" );
695- }
696- if (c == 0 ) {
697- c = start_line - existing -> start_line ;
698- }
699- if (c == 0 ) {
700- c = strcmp (name ? name : "" , existing -> name ? existing -> name : "" );
701- }
702- if (c >= 0 ) {
703- return existing -> id ; /* existing entity is the canonical winner */
704- }
666+ /* Same-QN arrival: distinct source entities can share a QN (C: a
667+ * struct, a function and a macro with one name), and the same entity
668+ * can be re-upserted with fresh content. The old code let the LAST
669+ * arrival overwrite — under parallel extraction the merge order
670+ * varies run to run, so WHICH entity survived flickered (xfs:
671+ * Function-node count 4998 vs 5015 across two runs) and every
672+ * order-sensitive consumer downstream inherited it. Pick the
673+ * survivor by a canonical CONTENT rule instead, a pure function of
674+ * the two candidates: smallest file_path, then LARGEST start_line,
675+ * then largest name/label (a mixed-direction composite is still a
676+ * total order, so the pick is commutative and scheduling-free).
677+ * Line-descending within a file keeps the classic upsert contract —
678+ * a later definition in the same file (macro redefinition, refresh
679+ * of the same entity with new content) replaces the earlier one,
680+ * deterministically, because intra-file arrival order is fixed. A
681+ * full tie is the same entity re-upserted → refresh in place.
682+ * Kind-disambiguated QNs (the real cure) remain a follow-up. */
683+ int c = strcmp (file_path ? file_path : "" , existing -> file_path ? existing -> file_path : "" );
684+ if (c == 0 ) {
685+ c = existing -> start_line - start_line ;
686+ }
687+ if (c == 0 ) {
688+ c = strcmp (existing -> name ? existing -> name : "" , name ? name : "" );
689+ }
690+ if (c == 0 ) {
691+ c = strcmp (existing -> label ? existing -> label : "" , label ? label : "" );
692+ }
693+ if (c > 0 ) {
694+ return existing -> id ; /* existing entity is the canonical winner */
705695 }
706696 /* Update in-place. name/properties are strdup'd BEFORE freeing old ones
707697 * (callers may pass existing->name as an argument). label/file_path are
@@ -1206,32 +1196,25 @@ static void merge_update_existing(cbm_gbuf_t *dst, cbm_gbuf_node_t *existing,
12061196 (strcmp (existing -> label , "Project" ) == 0 || strcmp (existing -> label , "Folder" ) == 0 );
12071197 if (!module_on_container ) {
12081198 /* Canonical collision winner (determinism) — mirrors
1209- * cbm_gbuf_upsert_node. Distinct source entities can share a QN (C:
1210- * struct/function/macro with one name); unconditional "src wins" made
1211- * the survivor depend on worker merge order, flickering the node set
1212- * (and every downstream consumer) run to run. Same entity → refresh;
1213- * different entity → keep the lexicographically smallest
1214- * (label, file_path, start_line, name). */
1215- bool same_entity = existing -> label && sn -> label &&
1216- strcmp (existing -> label , sn -> label ) == 0 && existing -> file_path &&
1217- sn -> file_path && strcmp (existing -> file_path , sn -> file_path ) == 0 &&
1218- existing -> start_line == sn -> start_line && existing -> name && sn -> name &&
1219- strcmp (existing -> name , sn -> name ) == 0 ;
1220- bool sn_wins = true;
1221- if (!same_entity ) {
1222- int c = strcmp (sn -> label ? sn -> label : "" , existing -> label ? existing -> label : "" );
1223- if (c == 0 ) {
1224- c = strcmp (sn -> file_path ? sn -> file_path : "" ,
1225- existing -> file_path ? existing -> file_path : "" );
1226- }
1227- if (c == 0 ) {
1228- c = sn -> start_line - existing -> start_line ;
1229- }
1230- if (c == 0 ) {
1231- c = strcmp (sn -> name ? sn -> name : "" , existing -> name ? existing -> name : "" );
1232- }
1233- sn_wins = c < 0 ;
1199+ * cbm_gbuf_upsert_node exactly. Distinct source entities can share a
1200+ * QN (C: struct/function/macro with one name); unconditional "src
1201+ * wins" made the survivor depend on worker merge order, flickering
1202+ * the node set (and every downstream consumer) run to run. Winner =
1203+ * smallest file_path, then LARGEST start_line, then largest
1204+ * name/label — one total order, commutative, scheduling-free; a full
1205+ * tie is the same entity → refresh from src. */
1206+ int c = strcmp (sn -> file_path ? sn -> file_path : "" ,
1207+ existing -> file_path ? existing -> file_path : "" );
1208+ if (c == 0 ) {
1209+ c = existing -> start_line - sn -> start_line ;
1210+ }
1211+ if (c == 0 ) {
1212+ c = strcmp (existing -> name ? existing -> name : "" , sn -> name ? sn -> name : "" );
1213+ }
1214+ if (c == 0 ) {
1215+ c = strcmp (existing -> label ? existing -> label : "" , sn -> label ? sn -> label : "" );
12341216 }
1217+ bool sn_wins = c <= 0 ;
12351218 if (sn_wins ) {
12361219 /* Keep the secondary indexes consistent when the surviving
12371220 * label/name changes (the old code left the node listed under its
0 commit comments