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);