@@ -298,7 +298,7 @@ export class PccProjectConsumer {
298298 )
299299 return { action : 'SKIPPED' }
300300 }
301- segment = fallback as SegmentRow | null
301+ segment = fallback
302302 }
303303
304304 // Step 3: no match → SKIP (Phase 1: project doesn't exist in CDP yet)
@@ -513,7 +513,7 @@ async function upsertSegment(
513513 SET name = $(name),
514514 status = COALESCE($(status)::"segmentsStatus_type", status),
515515 maturity = $(maturity),
516- description = $( description),
516+ description = COALESCE($(description), description),
517517 "updatedAt" = NOW()
518518 WHERE "sourceId" = $(sourceId) AND "tenantId" = $(tenantId)` ,
519519 {
@@ -560,20 +560,31 @@ async function upsertInsightsProject(
560560
561561 // Slug is intentionally not updated — it is a stable identifier referenced by FK from
562562 // securityInsightsEvaluations and related tables.
563- // logoUrl won't be updated in InsightsProject until we confirm that the format is
564- // compatible with the Insights Squared standard. Do NOT reintroduce it as a
565- // `--`-commented SQL line: pg-promise scans placeholders textually and would still
566- // require the `logoUrl` param, triggering "Property 'logoUrl' doesn't exist".
563+ // description: COALESCE keeps existing when PCC sends null (CM-1131).
564+ // logoUrl: COALESCE("logoUrl", …) never overrides an existing logo; only fills missing ones (CM-1131).
565+ //
566+ // Wrapped in db.tx() so that when called inside an outer transaction (ITask), pg-promise
567+ // creates a SAVEPOINT. A 23505 failure rolls back only the savepoint, leaving the outer
568+ // transaction intact. Without this, a caught PG error still leaves the transaction in
569+ // an aborted state and all subsequent queries on the same tx would fail.
567570 try {
568- await db . none (
569- `UPDATE "insightsProjects"
570- SET name = $(name),
571- description = $(description),
572- "updatedAt" = NOW()
573- WHERE "segmentId" = $(segmentId)
574- AND "deletedAt" IS NULL` ,
575- { segmentId, name : project . name , description : project . description } ,
576- )
571+ await db . tx ( async ( t ) => {
572+ await t . none (
573+ `UPDATE "insightsProjects"
574+ SET name = $(name),
575+ description = COALESCE($(description), description),
576+ "logoUrl" = COALESCE("logoUrl", $(logoUrl)),
577+ "updatedAt" = NOW()
578+ WHERE "segmentId" = $(segmentId)
579+ AND "deletedAt" IS NULL` ,
580+ {
581+ segmentId,
582+ name : project . name ,
583+ description : project . description ,
584+ logoUrl : project . logoUrl ,
585+ } ,
586+ )
587+ } )
577588 } catch ( err ) {
578589 if ( isDuplicateKeyError ( err ) ) return true
579590 throw err
@@ -609,15 +620,28 @@ async function upsertInsightsProject(
609620 )
610621 if ( conflicting ) return true
611622
612- // logoUrl intentionally omitted from the INSERT column list — see note above.
623+ // Same savepoint rationale as the UPDATE path above.
613624 try {
614- await db . none (
615- `INSERT INTO "insightsProjects" (name, slug, description, "segmentId", "isLF")
616- VALUES ($(name), generate_slug('insightsProjects', $(name)), $(description), $(segmentId), TRUE)` ,
617- { name : project . name , description : project . description , segmentId } ,
618- )
625+ await db . tx ( async ( t ) => {
626+ await t . none (
627+ `INSERT INTO "insightsProjects" (name, slug, description, "logoUrl", "segmentId", "isLF")
628+ VALUES ($(name), generate_slug('insightsProjects', $(name)), $(description), $(logoUrl), $(segmentId), TRUE)` ,
629+ {
630+ name : project . name ,
631+ description : project . description ,
632+ logoUrl : project . logoUrl ,
633+ segmentId,
634+ } ,
635+ )
636+ } )
619637 } catch ( err ) {
620- if ( isDuplicateKeyError ( err ) ) return true
638+ if ( isDuplicateKeyError ( err ) ) {
639+ // unique_project_segmentId: another worker already inserted a row for this segment
640+ // concurrently — treat as "already represented", no conflict to record.
641+ const constraintName = ( err as { constraint ?: string } ) . constraint
642+ if ( constraintName === 'unique_project_segmentId' ) return false
643+ return true
644+ }
621645 throw err
622646 }
623647 return false
0 commit comments