fix(cli): dedupe import resolutions before ApplyAsync

PreviewAsync can emit multiple ImportPreviewItem rows for the same
(EntityType, Name) -- one per modified member of a template, for
example. ApplyAsync internally calls .ToDictionary() on the
resolutions list and throws ArgumentException on duplicate keys.

The Central UI's BuildDefaultResolutions already dedupes via a
dictionary assignment (last-write-wins). Mirror that in the CLI
handler so 'bundle import' tolerates the duplicate-rows shape the
preview returns.
This commit is contained in:
Joseph Doherty
2026-05-24 08:20:34 -04:00
parent 438f59e74e
commit 1361a39770

View File

@@ -1838,22 +1838,29 @@ public class ManagementActor : ReceiveActor
$"Bundle has {blockers.Count} blocker(s); import aborted. {details}");
}
// Dedupe by (EntityType, Name) -- the preview can emit multiple rows per
// entity (e.g. one row per modified member of a template), but ApplyAsync
// requires a unique resolution per key. Last-write-wins matches the
// Central UI's TransportImport.BuildDefaultResolutions behavior.
var renameStamp = DateTime.UtcNow.ToString("yyyyMMdd-HHmmss");
var resolutions = preview.Items.Select(item => new ImportResolution(
item.EntityType,
item.Name,
item.Kind switch
var resolutionsMap = new Dictionary<(string, string), ImportResolution>();
foreach (var item in preview.Items)
{
var action = item.Kind switch
{
ConflictKind.New => ResolutionAction.Add,
ConflictKind.Identical => ResolutionAction.Skip,
ConflictKind.Modified => policy,
_ => ResolutionAction.Skip,
},
(item.Kind == ConflictKind.Modified && policy == ResolutionAction.Rename)
};
var renameTo = (item.Kind == ConflictKind.Modified && policy == ResolutionAction.Rename)
? $"{item.Name}-imported-{renameStamp}"
: null)).ToList();
: null;
resolutionsMap[(item.EntityType, item.Name)] = new ImportResolution(
item.EntityType, item.Name, action, renameTo);
}
return await importer.ApplyAsync(session.SessionId, resolutions, username);
return await importer.ApplyAsync(session.SessionId, resolutionsMap.Values.ToList(), username);
}
private static byte[] DecodeBundle(string base64)