From 8f8b4a6d2b104778b434faa490b3d899789edcbb Mon Sep 17 00:00:00 2001 From: Derek Gabriel Date: Sun, 12 Apr 2026 17:56:43 -1000 Subject: [PATCH 1/2] fix: persist contentHash on append-to-existing and normalize response casing AppendAsync's mutation path (ReplaceItemAsync) was writing the merged document without recomputing ContentHash/ContentLength, causing those fields to disappear from Cosmos after any second-or-later append. This broke CompareDocument for any document that had been appended to. All anonymous-object response serializations now use explicit lowercase property names so System.Text.Json emits camelCase matching the BrainDocument model's [JsonPropertyName] attributes. Also adds contentHash to AppendDocument and UpsertDocumentChunked responses. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Services/CosmosDocumentStore.cs | 7 +- src/DevBrain.Functions/Tools/DocumentTools.cs | 72 ++++++++++--------- 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/src/DevBrain.Functions/Services/CosmosDocumentStore.cs b/src/DevBrain.Functions/Services/CosmosDocumentStore.cs index 6d95298..ccfd79a 100644 --- a/src/DevBrain.Functions/Services/CosmosDocumentStore.cs +++ b/src/DevBrain.Functions/Services/CosmosDocumentStore.cs @@ -306,15 +306,18 @@ public async Task AppendAsync( var (existing, etag) = read.Value; + var mergedContent = existing.Content + separator + content; var merged = new BrainDocument { Id = existing.Id, Key = existing.Key, Project = existing.Project, - Content = existing.Content + separator + content, + Content = mergedContent, Tags = UnionTags(existing.Tags, tags), UpdatedAt = DateTimeOffset.UtcNow, - UpdatedBy = updatedBy + UpdatedBy = updatedBy, + ContentHash = ComputeSha256(mergedContent), + ContentLength = mergedContent.Length }; try diff --git a/src/DevBrain.Functions/Tools/DocumentTools.cs b/src/DevBrain.Functions/Tools/DocumentTools.cs index 5cf6453..2ae946f 100644 --- a/src/DevBrain.Functions/Tools/DocumentTools.cs +++ b/src/DevBrain.Functions/Tools/DocumentTools.cs @@ -97,13 +97,13 @@ public async Task GetDocumentMetadata( return JsonSerializer.Serialize(new { - document.Key, - document.Project, - document.Tags, - document.UpdatedAt, - document.UpdatedBy, - document.ContentHash, - document.ContentLength + key = document.Key, + project = document.Project, + tags = document.Tags, + updatedAt = document.UpdatedAt, + updatedBy = document.UpdatedBy, + contentHash = document.ContentHash, + contentLength = document.ContentLength }); } @@ -174,17 +174,17 @@ public async Task ListDocuments( { return JsonSerializer.Serialize(new[] { - new { documents[0].Key, documents[0].Content } + new { key = documents[0].Key, content = documents[0].Content } }); } var projection = documents.Select(d => new { - d.Key, - d.Tags, - d.UpdatedAt, - d.UpdatedBy, - d.Project + key = d.Key, + tags = d.Tags, + updatedAt = d.UpdatedAt, + updatedBy = d.UpdatedBy, + project = d.Project }); return JsonSerializer.Serialize(projection); @@ -258,12 +258,13 @@ public async Task AppendDocument( return JsonSerializer.Serialize(new { - saved.Key, - saved.Project, - saved.Tags, - saved.UpdatedAt, - saved.UpdatedBy, - ContentLength = saved.Content.Length + key = saved.Key, + project = saved.Project, + tags = saved.Tags, + updatedAt = saved.UpdatedAt, + updatedBy = saved.UpdatedBy, + contentHash = saved.ContentHash, + contentLength = saved.Content.Length }); } catch (Exception ex) @@ -329,17 +330,18 @@ public async Task UpsertDocumentChunked( { key, project = resolvedProject, - result.Status, - result.ChunksReceived, - result.TotalChunks, - Document = result.Document is null ? null : new + status = result.Status, + chunksReceived = result.ChunksReceived, + totalChunks = result.TotalChunks, + document = result.Document is null ? null : new { - result.Document.Key, - result.Document.Project, - result.Document.Tags, - result.Document.UpdatedAt, - result.Document.UpdatedBy, - ContentLength = result.Document.Content.Length + key = result.Document.Key, + project = result.Document.Project, + tags = result.Document.Tags, + updatedAt = result.Document.UpdatedAt, + updatedBy = result.Document.UpdatedBy, + contentHash = result.Document.ContentHash, + contentLength = result.Document.Content.Length } }); } @@ -366,17 +368,17 @@ public async Task SearchDocuments( { return JsonSerializer.Serialize(new[] { - new { documents[0].Key, documents[0].Content } + new { key = documents[0].Key, content = documents[0].Content } }); } var projection = documents.Select(d => new { - d.Key, - d.Tags, - d.UpdatedAt, - d.Project, - ContentExcerpt = d.Content.Length > 300 ? d.Content[..300] + "..." : d.Content + key = d.Key, + tags = d.Tags, + updatedAt = d.UpdatedAt, + project = d.Project, + contentExcerpt = d.Content.Length > 300 ? d.Content[..300] + "..." : d.Content }); return JsonSerializer.Serialize(projection); From a83aace6f2d05a41f16fe062134afb910a451462 Mon Sep 17 00:00:00 2001 From: Derek Gabriel Date: Sun, 12 Apr 2026 18:03:16 -1000 Subject: [PATCH 2/2] fix: camelCase CompareDocument's updatedAt/updatedBy in response --- src/DevBrain.Functions/Tools/DocumentTools.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DevBrain.Functions/Tools/DocumentTools.cs b/src/DevBrain.Functions/Tools/DocumentTools.cs index 2ae946f..ac1b03b 100644 --- a/src/DevBrain.Functions/Tools/DocumentTools.cs +++ b/src/DevBrain.Functions/Tools/DocumentTools.cs @@ -154,8 +154,8 @@ public async Task CompareDocument( storedContentHash = document.ContentHash, storedContentLength = document.ContentLength, candidateHash, - document.UpdatedAt, - document.UpdatedBy + updatedAt = document.UpdatedAt, + updatedBy = document.UpdatedBy }); }