fix(cv-qa): preserve reviewed mapping order and verify merge journeys

This commit is contained in:
cesnimda
2026-10-27 00:27:32 +01:00
parent 48b899c8de
commit cf0e5c23de
7 changed files with 683 additions and 3 deletions
@@ -185,7 +185,7 @@ public sealed partial class ProfileCvController
if (mappings.Count > 25 || mappings.Any(m => m is null)) return BadRequest("Invalid or excessive record mappings.");
// Preserve hashes for old requests and already-applied historical reviews.
var hashBytes = mappings.Count == 0 ? JsonSerializer.SerializeToUtf8Bytes(new { request.ExpectedVersion, Decisions = request.Decisions.OrderBy(d => d.Id, StringComparer.Ordinal) })
: JsonSerializer.SerializeToUtf8Bytes(new { request.ExpectedVersion, Decisions = request.Decisions.OrderBy(d => d.Id, StringComparer.Ordinal), Mappings = mappings.OrderBy(m => m.ClientId, StringComparer.Ordinal) });
: JsonSerializer.SerializeToUtf8Bytes(new { request.ExpectedVersion, Decisions = request.Decisions.OrderBy(d => d.Id, StringComparer.Ordinal), Mappings = mappings });
var hash = Convert.ToHexString(SHA256.HashData(hashBytes));
try
{
@@ -197,7 +197,15 @@ public sealed partial class ProfileCvController
if (run.Status == "applied")
{
var prior = JsonSerializer.Deserialize<AppliedReview>(run.ReviewJson ?? "null");
return prior?.Hash == hash ? Ok(new { runId = id, status = "applied", revision = prior.Revision, structuredCv = prior.StructuredCv }) : Conflict("This import has already been applied with different decisions.");
var sameMappings = mappings.Count == 0 || JsonSerializer.Serialize(prior?.Mappings) == JsonSerializer.Serialize(mappings);
var identical = prior?.Hash == hash && sameMappings;
// Old mapping receipts sorted IDs; accept their exact original order only.
if (!identical && prior is not null && mappings.Count > 0 && sameMappings)
{
var legacyHash = Convert.ToHexString(SHA256.HashData(JsonSerializer.SerializeToUtf8Bytes(new { request.ExpectedVersion, Decisions = request.Decisions.OrderBy(d => d.Id, StringComparer.Ordinal), Mappings = mappings.OrderBy(m => m.ClientId, StringComparer.Ordinal) })));
identical = prior.Hash == legacyHash;
}
return identical ? Ok(new { runId = id, status = "applied", revision = prior!.Revision, structuredCv = prior.StructuredCv }) : Conflict("This import has already been applied with different decisions.");
}
if (run.Status != "pending_review" || string.IsNullOrWhiteSpace(run.StructuredProfileJson)) return Conflict("This import is not awaiting review.");
var snapshot = await _careerProfileService.LoadSnapshotAsync(user.Id, HttpContext.RequestAborted);
+1 -1
View File
@@ -77,7 +77,7 @@ public static class CvImportMapping
var result = Node(current);
var metadata = result["metadata"]!["fields"]!.AsObject();
var scalarWrites = new HashSet<string>(StringComparer.Ordinal);
foreach (var mapping in mappings.OrderBy(m => m.ClientId, StringComparer.Ordinal))
foreach (var mapping in mappings)
{
var catalog = Categories.Single(c => c.Category == mapping.Category);
if (mapping.Fields.Count == 0) throw new ArgumentException("A record mapping must contain reviewed fields.");