Repository: dotnet/arcade-services
Pull request #4790: Do not store assets work items
Author: login: adamzip
Created: 2025-05-05T10:01:14Z | Merged: 2025-05-06T11:05:38Z

List of assets can be too large to store in the Message Queue, therefore they should not be defined inside work items. To fix this, builds are queried at the beginning of the PullRequestUpdater flow, and the assets are taken from there.

https://github.com/dotnet/arcade-services/issues/4745#issue-3019865179


Diff:
diff --git a/src/Maestro/Maestro.DataProviders/SqlBarClient.cs b/src/Maestro/Maestro.DataProviders/SqlBarClient.cs
index 11ae706b0e..0252a08d17 100644
--- a/src/Maestro/Maestro.DataProviders/SqlBarClient.cs
+++ b/src/Maestro/Maestro.DataProviders/SqlBarClient.cs
@@ -294,7 +294,7 @@ private Subscription ToClientModelSubscription(Data.Models.Subscription other)
         };
     }
 
-    private Build ToClientModelBuild(Data.Models.Build other)
+    public static Build ToClientModelBuild(Data.Models.Build other)
     {
         var channels = other.BuildChannels?
             .Select(bc => ToClientModelChannel(bc.Channel))
@@ -336,7 +336,7 @@ private Build ToClientModelBuild(Data.Models.Build other)
         };
     }
 
-    private BuildRef ToClientModelBuildDependency(Data.Models.BuildDependency other)
+    private static BuildRef ToClientModelBuildDependency(Data.Models.BuildDependency other)
         => new(other.BuildId, other.IsProduct, other.TimeToInclusionInMinutes);
 
     private static SubscriptionPolicy ToClientModelSubscriptionPolicy(Data.Models.SubscriptionPolicy other)
@@ -374,6 +374,7 @@ public async Task<Build> GetBuildAsync(int buildId)
             .Include(b => b.BuildChannels)
             .ThenInclude(b => b.Channel)
             .Include(b => b.Assets)
+            .ThenInclude(b => b.Locations)
             .FirstOrDefaultAsync();
 
         if (build != null)
diff --git a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/IPullRequestUpdater.cs b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/IPullRequestUpdater.cs
index a9f1c1f527..263fb8c353 100644
--- a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/IPullRequestUpdater.cs
+++ b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/IPullRequestUpdater.cs
@@ -3,6 +3,7 @@
 
 using ProductConstructionService.DependencyFlow.Model;
 using ProductConstructionService.DependencyFlow.WorkItems;
+using Microsoft.DotNet.ProductConstructionService.Client.Models;
 
 namespace ProductConstructionService.DependencyFlow;
 
@@ -13,15 +14,13 @@ Task<bool> CheckPullRequestAsync(
 
     Task ProcessPendingUpdatesAsync(
         SubscriptionUpdateWorkItem update,
-        bool forceApply);
+        bool forceApply,
+        Build build);
 
     Task UpdateAssetsAsync(
         Guid subscriptionId,
         SubscriptionType type,
         int buildId,
-        string sourceRepo,
-        string sourceSha,
-        List<Asset> assets,
         bool forceApply);
 
     PullRequestUpdaterId Id { get; }
diff --git a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestConflictNotifier.cs b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestConflictNotifier.cs
index acacfea159..75444aede5 100644
--- a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestConflictNotifier.cs
+++ b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestConflictNotifier.cs
@@ -35,7 +35,8 @@ Task NotifyAboutMergeConflictAsync(
         InProgressPullRequest pr,
         SubscriptionUpdateWorkItem update,
         Subscription subscription,
-        IReadOnlyCollection<UnixPath> conflictedFiles);
+        IReadOnlyCollection<UnixPath> conflictedFiles,
+        Build build);
 }
 
 internal class PullRequestConflictNotifier : IPullRequestConflictNotifier
@@ -91,7 +92,8 @@ public async Task NotifyAboutMergeConflictAsync(
         InProgressPullRequest pr,
         SubscriptionUpdateWorkItem update,
         Subscription subscription,
-        IReadOnlyCollection<UnixPath> conflictedFiles)
+        IReadOnlyCollection<UnixPath> conflictedFiles,
+        Build build)
     {
         string metadataFile, contentType, correctContent;
 
@@ -128,7 +130,7 @@ public async Task NotifyAboutMergeConflictAsync(
                     subscription.TargetDirectory,
                     update.SourceRepo,
                     update.SourceSha,
-                    update.Assets.FirstOrDefault()?.Version,
+                    build.Assets.FirstOrDefault()?.Version,
                     update.BuildId),
                 new JsonSerializerOptions()
                 {
diff --git a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs
index 9ef129b363..47d95d4711 100644
--- a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs
+++ b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs
@@ -2,7 +2,6 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Collections.Immutable;
-using Maestro.Data;
 using Maestro.Data.Models;
 using Maestro.DataProviders;
 using Maestro.MergePolicies;
@@ -19,9 +18,9 @@
 using ProductConstructionService.DependencyFlow.Model;
 using ProductConstructionService.DependencyFlow.WorkItems;
 using ProductConstructionService.WorkItems;
-using Asset = ProductConstructionService.DependencyFlow.Model.Asset;
 using AssetData = Microsoft.DotNet.ProductConstructionService.Client.Models.AssetData;
 using SubscriptionDTO = Microsoft.DotNet.ProductConstructionService.Client.Models.Subscription;
+using BuildDTO = Microsoft.DotNet.ProductConstructionService.Client.Models.Build;
 
 namespace ProductConstructionService.DependencyFlow;
 
@@ -122,11 +121,11 @@ public async Task UpdateAssetsAsync(
         Guid subscriptionId,
         SubscriptionType type,
         int buildId,
-        string sourceRepo,
-        string sourceSha,
-        List<Asset> assets,
         bool forceApply)
     {
+        var build = await _sqlClient.GetBuildAsync(buildId)
+            ?? throw new InvalidOperationException($"Build with buildId {buildId} not found in the DB.");
+
         await ProcessPendingUpdatesAsync(
             new()
             {
@@ -134,22 +133,19 @@ await ProcessPendingUpdatesAsync(
                 SubscriptionId = subscriptionId,
                 SubscriptionType = type,
                 BuildId = buildId,
-                SourceSha = sourceSha,
-                SourceRepo = sourceRepo,
-                Assets = assets,
+                SourceSha = build.Commit,
+                SourceRepo = build.GetRepository(),
                 IsCoherencyUpdate = false,
             },
-            forceApply);
+            forceApply,
+            build);
     }
 
     /// <summary>
     ///     Process any pending pull request updates.
     /// </summary>
     /// <param name="forceApply">If false, we will check if this build is the latest one we have queued. If it's not we will skip this update.</param>
-    /// <returns>
-    ///     True if updates have been applied; <see langword="false" /> otherwise.
-    /// </returns>
-    public async Task ProcessPendingUpdatesAsync(SubscriptionUpdateWorkItem update, bool forceApply)
+    public async Task ProcessPendingUpdatesAsync(SubscriptionUpdateWorkItem update, bool forceApply, BuildDTO build)
     {
         _logger.LogInformation("Processing pending updates for subscription {subscriptionId}", update.SubscriptionId);
         bool isCodeFlow = update.SubscriptionType == SubscriptionType.DependenciesAndSources;
@@ -197,28 +193,29 @@ public async Task ProcessPendingUpdatesAsync(SubscriptionUpdateWorkItem update,
 
         if (isCodeFlow)
         {
-            await ProcessCodeFlowUpdateAsync(update, pr, prInfo);
+            await ProcessCodeFlowUpdateAsync(update, pr, prInfo, build);
         }
         else 
         {
-            await ProcessDependencyUpdateAsync(update, pr, prInfo);
+            await ProcessDependencyUpdateAsync(update, pr, prInfo, build);
         }
     }
 
     private async Task ProcessDependencyUpdateAsync(
         SubscriptionUpdateWorkItem update, 
         InProgressPullRequest? pr,
-        PullRequest? prInfo)
+        PullRequest? prInfo,
+        BuildDTO build)
     {
         if (pr != null && prInfo != null)
         {
-            await UpdatePullRequestAsync(update, pr, prInfo);
+            await UpdatePullRequestAsync(update, pr, prInfo, build);
             await _pullRequestUpdateReminders.UnsetReminderAsync(isCodeFlow: false);
             return;
         }
 
         // Create a new (regular) dependency update PR
-        var prUrl = await CreatePullRequestAsync(update);
+        var prUrl = await CreatePullRequestAsync(update, build);
         if (prUrl == null)
         {
             _logger.LogInformation("No changes required for subscription {subscriptionId}, no pull request created", update.SubscriptionId);
@@ -514,7 +511,7 @@ private async Task AddDependencyFlowEventsAsync(
     ///     Creates a pull request from the given updates.
     /// </summary>
     /// <returns>The pull request url when a pr was created; <see langref="null" /> if no PR is necessary</returns>
-    private async Task<string?> CreatePullRequestAsync(SubscriptionUpdateWorkItem update)
+    private async Task<string?> CreatePullRequestAsync(SubscriptionUpdateWorkItem update, BuildDTO build)
     {
         (var targetRepository, var targetBranch) = await GetTargetAsync();
         bool isCodeFlow = update.SubscriptionType == SubscriptionType.DependenciesAndSources;
@@ -522,7 +519,7 @@ private async Task AddDependencyFlowEventsAsync(
         IRemote darcRemote = await _remoteFactory.CreateRemoteAsync(targetRepository);
 
         TargetRepoDependencyUpdate repoDependencyUpdate =
-            await GetRequiredUpdates(update, targetRepository, prBranch: null, targetBranch: targetBranch);
+            await GetRequiredUpdates(update, targetRepository, build, prBranch: null, targetBranch: targetBranch);
 
         if (repoDependencyUpdate.CoherencyCheckSuccessful && repoDependencyUpdate.RequiredUpdates.Count < 1)
         {
@@ -619,7 +616,11 @@ await AddDependencyFlowEventsAsync(
         }
     }
 
-    private async Task UpdatePullRequestAsync(SubscriptionUpdateWorkItem update, InProgressPullRequest pr, PullRequest prInfo)
+    private async Task UpdatePullRequestAsync(
+        SubscriptionUpdateWorkItem update,
+        InProgressPullRequest pr,
+        PullRequest prInfo,
+        BuildDTO build)
     {
         (var targetRepository, var targetBranch) = await GetTargetAsync();
 
@@ -628,7 +629,7 @@ private async Task UpdatePullRequestAsync(SubscriptionUpdateWorkItem update, InP
         IRemote darcRemote = await _remoteFactory.CreateRemoteAsync(targetRepository);
 
         TargetRepoDependencyUpdate targetRepositoryUpdates =
-            await GetRequiredUpdates(update, targetRepository, prInfo.HeadBranch, targetBranch);
+            await GetRequiredUpdates(update, targetRepository, build, prInfo.HeadBranch, targetBranch);
 
         if (targetRepositoryUpdates.CoherencyCheckSuccessful && targetRepositoryUpdates.RequiredUpdates.Count < 1)
         {
@@ -756,6 +757,7 @@ private class TargetRepoDependencyUpdate
     private async Task<TargetRepoDependencyUpdate> GetRequiredUpdates(
         SubscriptionUpdateWorkItem update,
         string targetRepository,
+        BuildDTO build,
         string? prBranch,
         string targetBranch)
     {
@@ -768,7 +770,7 @@ private async Task<TargetRepoDependencyUpdate> GetRequiredUpdates(
         // Existing details 
         var existingDependencies = (await darc.GetDependenciesAsync(targetRepository, prBranch ?? targetBranch)).ToList();
 
-        IEnumerable<AssetData> assetData = update.Assets.Select(
+        IEnumerable<AssetData> assetData = build.Assets.Select(
             a => new AssetData(false)
             {
                 Name = a.Name,
@@ -978,7 +980,8 @@ private static PullRequestUpdateSummary CreatePrSummaryFromInProgressPr(
     private async Task ProcessCodeFlowUpdateAsync(
         SubscriptionUpdateWorkItem update,
         InProgressPullRequest? pr,
-        PullRequest? prInfo)
+        PullRequest? prInfo,
+        BuildDTO build)
     {
         if (update.SourceSha == pr?.SourceSha)
         {
@@ -993,7 +996,6 @@ private async Task ProcessCodeFlowUpdateAsync(
         }
 
         var subscription = await _sqlClient.GetSubscriptionAsync(update.SubscriptionId);
-        var build = await _sqlClient.GetBuildAsync(update.BuildId);
         var isForwardFlow = !string.IsNullOrEmpty(subscription.TargetDirectory);
         string prHeadBranch = pr?.HeadBranch ?? GetNewBranchName(subscription.TargetBranch);
 
@@ -1089,7 +1091,8 @@ await _pullRequestConflictNotifier.NotifyAboutMergeConflictAsync(
                 pr,
                 update,
                 subscription,
-                codeFlowRes.ConflictedFiles);
+                codeFlowRes.ConflictedFiles,
+                build);
         }
     }
 
diff --git a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/SubscriptionTriggerer.cs b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/SubscriptionTriggerer.cs
index 8da58eef72..819ad79f29 100644
--- a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/SubscriptionTriggerer.cs
+++ b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/SubscriptionTriggerer.cs
@@ -115,12 +115,6 @@ await AddDependencyFlowEventAsync(
             "PR",
             null);
 
-        _logger.LogInformation("Looking up build {buildId}", buildId);
-
-        Build build = await _context.Builds.Include(b => b.Assets)
-            .ThenInclude(a => a.Locations)
-            .FirstAsync(b => b.Id == buildId);
-
         IPullRequestUpdater pullRequestUpdater;
 
         if (subscription.PolicyObject.Batchable)
@@ -141,15 +135,6 @@ await AddDependencyFlowEventAsync(
                 new NonBatchedPullRequestUpdaterId(_subscriptionId));
         }
 
-        var assets = build.Assets
-            .Select(a => new Asset
-            {
-                Name = a.Name,
-                Version = a.Version
-            })
-            .ToList();
-
-
         IAsyncDisposable? @lock;
         var mutexKey = pullRequestUpdater.Id.ToString();
         do
@@ -169,10 +154,7 @@ await pullRequestUpdater.UpdateAssetsAsync(
                     subscription.SourceEnabled
                         ? SubscriptionType.DependenciesAndSources
                         : SubscriptionType.Dependencies,
-                    build.Id,
-                    build.GetRepository(),
-                    build.Commit,
-                    assets,
+                    buildId,
                     forceApply: true);
 
                 _logger.LogInformation("Asset update complete for {subscriptionId}", _subscriptionId);
diff --git a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/WorkItemProcessors/SubscriptionUpdateProcessor.cs b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/WorkItemProcessors/SubscriptionUpdateProcessor.cs
index 12bab3275b..855bbd0b69 100644
--- a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/WorkItemProcessors/SubscriptionUpdateProcessor.cs
+++ b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/WorkItemProcessors/SubscriptionUpdateProcessor.cs
@@ -1,21 +1,34 @@
 ﻿// Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using Maestro.DataProviders;
 using ProductConstructionService.DependencyFlow.WorkItems;
+using Microsoft.Extensions.Logging;
 
 namespace ProductConstructionService.DependencyFlow.WorkItemProcessors;
 
-public class SubscriptionUpdateProcessor(IPullRequestUpdaterFactory updaterFactory)
-    : DependencyFlowUpdateProcessor<SubscriptionUpdateWorkItem>
+public class SubscriptionUpdateProcessor(
+    IPullRequestUpdaterFactory updaterFactory,
+    ISqlBarClient sqlClient,
+    ILogger logger)
+: DependencyFlowUpdateProcessor<SubscriptionUpdateWorkItem>
 {
     private readonly IPullRequestUpdaterFactory _updaterFactory = updaterFactory;
+    private readonly ISqlBarClient _sqlClient = sqlClient;
+    private readonly ILogger _logger = logger;
 
     public override async Task<bool> ProcessWorkItemAsync(
         SubscriptionUpdateWorkItem workItem,
         CancellationToken cancellationToken)
     {
+        var build = await _sqlClient.GetBuildAsync(workItem.BuildId);
+        if (build == null)
+        {
+            _logger.LogError("Build with buildId {BuildId} not found in the DB.", workItem.BuildId);
+            return false;
+        }
         var updater = _updaterFactory.CreatePullRequestUpdater(PullRequestUpdaterId.Parse(workItem.UpdaterId));
-        await updater.ProcessPendingUpdatesAsync(workItem, forceApply: false);
+        await updater.ProcessPendingUpdatesAsync(workItem, forceApply: false, build);
         return true;
     }
 
diff --git a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/WorkItems/SubscriptionUpdateWorkItem.cs b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/WorkItems/SubscriptionUpdateWorkItem.cs
index 64976f2c0d..0af3071d12 100644
--- a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/WorkItems/SubscriptionUpdateWorkItem.cs
+++ b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/WorkItems/SubscriptionUpdateWorkItem.cs
@@ -25,9 +25,6 @@ public class SubscriptionUpdateWorkItem : DependencyFlowWorkItem
     [DataMember]
     public string SourceRepo { get; init; }
 
-    [DataMember]
-    public List<Asset> Assets { get; init; }
-
     /// <summary>
     ///     If true, this is a coherency update and not driven by specific
     ///     subscription ids (e.g. could be multiple if driven by a batched subscription)
diff --git a/test/ProductConstructionService.DependencyFlow.Tests/PendingUpdatePullRequestUpdaterTests.cs b/test/ProductConstructionService.DependencyFlow.Tests/PendingUpdatePullRequestUpdaterTests.cs
index 2f008deb7f..16812d6a22 100644
--- a/test/ProductConstructionService.DependencyFlow.Tests/PendingUpdatePullRequestUpdaterTests.cs
+++ b/test/ProductConstructionService.DependencyFlow.Tests/PendingUpdatePullRequestUpdaterTests.cs
@@ -2,7 +2,9 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using Maestro.Data.Models;
+using Maestro.DataProviders;
 using ProductConstructionService.DependencyFlow.WorkItems;
+using BuildDTO = Microsoft.DotNet.ProductConstructionService.Client.Models.Build;
 
 namespace ProductConstructionService.DependencyFlow.Tests;
 
@@ -16,8 +18,9 @@ protected async Task WhenProcessPendingUpdatesAsyncIsCalled(
         await Execute(
             async context =>
             {
+                BuildDTO buildDTO = SqlBarClient.ToClientModelBuild(forBuild);
                 IPullRequestUpdater updater = CreatePullRequestActor(context);
-                await updater.ProcessPendingUpdatesAsync(CreateSubscriptionUpdate(forBuild, isCodeFlow), forceApply);
+                await updater.ProcessPendingUpdatesAsync(CreateSubscriptionUpdate(forBuild, isCodeFlow), forceApply, buildDTO);
             });
     }
 
diff --git a/test/ProductConstructionService.DependencyFlow.Tests/PullRequestUpdaterTests.cs b/test/ProductConstructionService.DependencyFlow.Tests/PullRequestUpdaterTests.cs
index 1fdc927593..6acaa2b1f3 100644
--- a/test/ProductConstructionService.DependencyFlow.Tests/PullRequestUpdaterTests.cs
+++ b/test/ProductConstructionService.DependencyFlow.Tests/PullRequestUpdaterTests.cs
@@ -2,7 +2,6 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using FluentAssertions;
-using Kusto.Cloud.Platform.Utils;
 using Maestro.Data;
 using Maestro.Data.Models;
 using Maestro.DataProviders;
@@ -19,7 +18,6 @@
 using Moq;
 using NUnit.Framework;
 using ProductConstructionService.DependencyFlow.WorkItems;
-using Asset = ProductConstructionService.DependencyFlow.Model.Asset;
 using AssetData = Microsoft.DotNet.ProductConstructionService.Client.Models.AssetData;
 using Microsoft.DotNet.DarcLib.Models.VirtualMonoRepo;
 using System.Collections.Immutable;
@@ -602,13 +600,6 @@ protected SubscriptionUpdateWorkItem CreateSubscriptionUpdate(Build forBuild, bo
             BuildId = forBuild.Id,
             SourceSha = forBuild.Commit,
             SourceRepo = forBuild.GitHubRepository ?? forBuild.AzureDevOpsRepository,
-            Assets = forBuild.Assets
-                .Select(a => new Asset
-                {
-                    Name = a.Name,
-                    Version = a.Version
-                })
-                .ToList(),
             IsCoherencyUpdate = false,
         };
 
diff --git a/test/ProductConstructionService.DependencyFlow.Tests/SubscriptionUpdaterTests.cs b/test/ProductConstructionService.DependencyFlow.Tests/SubscriptionUpdaterTests.cs
index 287be3d64b..2467480262 100644
--- a/test/ProductConstructionService.DependencyFlow.Tests/SubscriptionUpdaterTests.cs
+++ b/test/ProductConstructionService.DependencyFlow.Tests/SubscriptionUpdaterTests.cs
@@ -61,22 +61,7 @@ private void ThenUpdateAssetsAsyncShouldHaveBeenCalled(UpdaterId forUpdater, Bui
                     Subscription.Id,
                     SubscriptionType.Dependencies,
                     withBuild.Id,
-                    SourceRepo,
-                    NewCommit,
-                    Capture.In(updatedAssets),
                     true));
-
-        updatedAssets.Should().BeEquivalentTo(
-            new List<List<Asset>>
-            {
-                withBuild.Assets
-                    .Select(a => new Asset
-                    {
-                        Name = a.Name,
-                        Version = a.Version
-                    })
-                    .ToList()
-            });
     }
 
     [Test]
diff --git a/test/ProductConstructionService.DependencyFlow.Tests/UpdateAssetsPullRequestUpdaterTests.cs b/test/ProductConstructionService.DependencyFlow.Tests/UpdateAssetsPullRequestUpdaterTests.cs
index 72dea03222..b39cc27594 100644
--- a/test/ProductConstructionService.DependencyFlow.Tests/UpdateAssetsPullRequestUpdaterTests.cs
+++ b/test/ProductConstructionService.DependencyFlow.Tests/UpdateAssetsPullRequestUpdaterTests.cs
@@ -18,15 +18,6 @@ await updater.UpdateAssetsAsync(
                     Subscription.Id,
                     Subscription.SourceEnabled ? SubscriptionType.DependenciesAndSources : SubscriptionType.Dependencies,
                     forBuild.Id,
-                    SourceRepo,
-                    forBuild.Commit,
-                    forBuild.Assets
-                        .Select(a => new Asset
-                        {
-                            Name = a.Name,
-                            Version = a.Version
-                        })
-                        .ToList(),
                     forceApply: true);
             });
     }


Reviews:
user: login: adamzip
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T11:46:37Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR refactors the asset handling logic so that asset data is no longer stored in work items but instead is retrieved from builds. The changes remove asset parameters from work item and test signatures and update methods to retrieve build information as needed.  
- Removed asset extraction and related assertions from tests.  
- Updated Pull Request updater methods and interface to accept a Build parameter and retrieve asset info accordingly.  
- Adjusted build queries to include location details.

### Reviewed Changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated no comments.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| test/ProductConstructionService.DependencyFlow.Tests/UpdateAssetsPullRequestUpdaterTests.cs | Removed asset transformation in test setup. |
| test/ProductConstructionService.DependencyFlow.Tests/SubscriptionUpdaterTests.cs | Updated expected asset data handling in tests. |
| test/ProductConstructionService.DependencyFlow.Tests/PullRequestUpdaterTests.cs | Removed asset parameters from the update work item in tests. |
| test/ProductConstructionService.DependencyFlow.Tests/PendingUpdatePullRequestUpdaterTests.cs | Added a null build parameter to the ProcessPendingUpdatesAsync call. |
| src/ProductConstructionService/ProductConstructionService.DependencyFlow/WorkItemProcessors/SubscriptionUpdateProcessor.cs | Updated call to ProcessPendingUpdatesAsync with a null build argument. |
| src/ProductConstructionService/ProductConstructionService.DependencyFlow/SubscriptionTriggerer.cs | Removed direct build lookup and asset extraction before calling update methods. |
| src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs | Refactored UpdateAssetsAsync and ProcessPendingUpdatesAsync to retrieve build details and pass them to downstream methods. |
| src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestConflictNotifier.cs | Updated NotifyAboutMergeConflictAsync signature to include build information. |
| src/ProductConstructionService/ProductConstructionService.DependencyFlow/IPullRequestUpdater.cs | Updated interface signatures to reflect the new build parameter. |
| src/Maestro/Maestro.DataProviders/SqlBarClient.cs | Enhanced build query to include asset location details. |
</details>



<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**src/ProductConstructionService/ProductConstructionService.DependencyFlow/IPullRequestUpdater.cs:16**
* Update the method documentation to reflect the new 'build' parameter in ProcessPendingUpdatesAsync, explaining how it is used to retrieve asset details.
```
Task ProcessPendingUpdatesAsync(SubscriptionUpdateWorkItem update, bool forceApply, Build? build);
```
**test/ProductConstructionService.DependencyFlow.Tests/PendingUpdatePullRequestUpdaterTests.cs:20**
* Consider adding tests to verify that when a null build is passed, the build lookup logic within ProcessPendingUpdatesAsync is correctly triggered and handled.
```
await updater.ProcessPendingUpdatesAsync(CreateSubscriptionUpdate(forBuild, isCodeFlow), forceApply, null);
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T12:06:31Z
user: login: premun
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-05T12:16:34Z
user: login: dkurepa
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-05T14:39:00Z
user: login: premun
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T09:06:28Z
user: login: adamzip
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T09:26:11Z
user: login: premun
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-06T10:57:28Z
user: login: premun
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T10:57:37Z

Review comments:
diff_hunk: @@ -61,22 +61,7 @@ private void ThenUpdateAssetsAsyncShouldHaveBeenCalled(UpdaterId forUpdater, Bui
                     Subscription.Id,
                     SubscriptionType.Dependencies,
                     withBuild.Id,
-                    SourceRepo,
-                    NewCommit,
-                    Capture.In(updatedAssets),
                     true));
-
-        updatedAssets.Should().BeEquivalentTo(
path: test/ProductConstructionService.DependencyFlow.Tests/SubscriptionUpdaterTests.cs
user: login: adamzip
body: I believe this doesn't serve a purpose anymore since we now don't explicitly pass a list of assets to the method above
created_at: 2025-05-05T11:46:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -22,6 +22,8 @@
 using Asset = ProductConstructionService.DependencyFlow.Model.Asset;
 using AssetData = Microsoft.DotNet.ProductConstructionService.Client.Models.AssetData;
 using SubscriptionDTO = Microsoft.DotNet.ProductConstructionService.Client.Models.Subscription;
+using BuildDTO = Microsoft.DotNet.ProductConstructionService.Client.Models.Build;
+using static Microsoft.EntityFrameworkCore.DbLoggerCategory;
path: src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs
user: login: premun
body: ```suggestion
```
created_at: 2025-05-05T12:08:02Z
author_association: MEMBER
diff_hunk: @@ -122,24 +124,24 @@ public async Task UpdateAssetsAsync(
         Guid subscriptionId,
         SubscriptionType type,
         int buildId,
-        string sourceRepo,
-        string sourceSha,
-        List<Asset> assets,
         bool forceApply)
     {
+        _logger.LogInformation("Looking up build {buildId}", buildId);
path: src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs
user: login: premun
body: I think we agreed we don't want to log outside of methods that do something about what they are going to do but instead we would log inside.
I think SQL queries are already logged (with a higher verbosity though) so maybe these "looking up build" statements are extraneous.
created_at: 2025-05-05T12:13:20Z
author_association: MEMBER
diff_hunk: @@ -149,13 +151,19 @@ await ProcessPendingUpdatesAsync(
     /// <returns>
     ///     True if updates have been applied; <see langword="false" /> otherwise.
     /// </returns>
-    public async Task ProcessPendingUpdatesAsync(SubscriptionUpdateWorkItem update, bool forceApply)
+    public async Task ProcessPendingUpdatesAsync(SubscriptionUpdateWorkItem update, bool forceApply, BuildDTO? build)
     {
         _logger.LogInformation("Processing pending updates for subscription {subscriptionId}", update.SubscriptionId);
         bool isCodeFlow = update.SubscriptionType == SubscriptionType.DependenciesAndSources;
         InProgressPullRequest? pr = await _pullRequestState.TryGetStateAsync();
         PullRequest? prInfo;
 
+        if (build == null)
+        {
+            _logger.LogInformation("Looking up build {buildId}", update.BuildId);
+            build = await _sqlClient.GetBuildAsync(update.BuildId);
+        }
path: src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs
user: login: premun
body: Same here, I'd remove the logging:

```suggestion
        build ??= await _sqlClient.GetBuildAsync(update.BuildId);
```
created_at: 2025-05-05T12:13:53Z
author_association: MEMBER
diff_hunk: @@ -15,7 +15,7 @@ public override async Task<bool> ProcessWorkItemAsync(
         CancellationToken cancellationToken)
     {
         var updater = _updaterFactory.CreatePullRequestUpdater(PullRequestUpdaterId.Parse(workItem.UpdaterId));
-        await updater.ProcessPendingUpdatesAsync(workItem, forceApply: false);
+        await updater.ProcessPendingUpdatesAsync(workItem, forceApply: false, build: null);
path: src/ProductConstructionService/ProductConstructionService.DependencyFlow/WorkItemProcessors/SubscriptionUpdateProcessor.cs
user: login: premun
body: This is the only place where we pass `null` as `build` if I am correct.
Why not just make the parameter required and query it here based on `workItem.BuildId`?
I think it will make the API simpler.
created_at: 2025-05-05T12:15:41Z
author_association: MEMBER
diff_hunk: @@ -122,24 +124,24 @@ public async Task UpdateAssetsAsync(
         Guid subscriptionId,
         SubscriptionType type,
         int buildId,
-        string sourceRepo,
-        string sourceSha,
-        List<Asset> assets,
         bool forceApply)
     {
+        _logger.LogInformation("Looking up build {buildId}", buildId);
path: src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs
user: login: dkurepa
body: I think they're currently not included in the AI, we might have disabled them in the settings.json. But even if we had them, those are quite hard to read, and just give you a translated SQL query, maybe even without the parameters, I'd prefer if we had these simple traces
created_at: 2025-05-05T14:39:00Z
author_association: MEMBER
diff_hunk: @@ -1,21 +1,25 @@
 ﻿// Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using Maestro.DataProviders;
 using ProductConstructionService.DependencyFlow.WorkItems;
 
 namespace ProductConstructionService.DependencyFlow.WorkItemProcessors;
 
-public class SubscriptionUpdateProcessor(IPullRequestUpdaterFactory updaterFactory)
+public class SubscriptionUpdateProcessor(IPullRequestUpdaterFactory updaterFactory, ISqlBarClient sqlClient)
     : DependencyFlowUpdateProcessor<SubscriptionUpdateWorkItem>
 {
     private readonly IPullRequestUpdaterFactory _updaterFactory = updaterFactory;
+    private readonly ISqlBarClient _sqlClient = sqlClient;
 
     public override async Task<bool> ProcessWorkItemAsync(
         SubscriptionUpdateWorkItem workItem,
         CancellationToken cancellationToken)
     {
+        var build = await _sqlClient.GetBuildAsync(workItem.BuildId)
+            ?? throw new InvalidOperationException($"Build with buildId {workItem.BuildId} not found in the DB.");
path: src/ProductConstructionService/ProductConstructionService.DependencyFlow/WorkItemProcessors/SubscriptionUpdateProcessor.cs
user: login: premun
body: Throwing here will mean the work item will get processed 3 times and fail each time.
I think we should instead log + return false.
created_at: 2025-05-06T09:06:11Z
author_association: MEMBER
diff_hunk: @@ -1,21 +1,25 @@
 ﻿// Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using Maestro.DataProviders;
 using ProductConstructionService.DependencyFlow.WorkItems;
 
 namespace ProductConstructionService.DependencyFlow.WorkItemProcessors;
 
-public class SubscriptionUpdateProcessor(IPullRequestUpdaterFactory updaterFactory)
+public class SubscriptionUpdateProcessor(IPullRequestUpdaterFactory updaterFactory, ISqlBarClient sqlClient)
     : DependencyFlowUpdateProcessor<SubscriptionUpdateWorkItem>
 {
     private readonly IPullRequestUpdaterFactory _updaterFactory = updaterFactory;
+    private readonly ISqlBarClient _sqlClient = sqlClient;
 
     public override async Task<bool> ProcessWorkItemAsync(
         SubscriptionUpdateWorkItem workItem,
         CancellationToken cancellationToken)
     {
+        var build = await _sqlClient.GetBuildAsync(workItem.BuildId)
+            ?? throw new InvalidOperationException($"Build with buildId {workItem.BuildId} not found in the DB.");
path: src/ProductConstructionService/ProductConstructionService.DependencyFlow/WorkItemProcessors/SubscriptionUpdateProcessor.cs
user: login: adamzip
body: True, we query builds and subscriptions like this in PRUpdater. I think it's fine not to be too defensive with those because this should basically never happen.
But anyway, I'm changing this one to what you said
created_at: 2025-05-06T09:26:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,21 +1,25 @@
 ﻿// Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using Maestro.DataProviders;
 using ProductConstructionService.DependencyFlow.WorkItems;
 
 namespace ProductConstructionService.DependencyFlow.WorkItemProcessors;
 
-public class SubscriptionUpdateProcessor(IPullRequestUpdaterFactory updaterFactory)
+public class SubscriptionUpdateProcessor(IPullRequestUpdaterFactory updaterFactory, ISqlBarClient sqlClient)
     : DependencyFlowUpdateProcessor<SubscriptionUpdateWorkItem>
 {
     private readonly IPullRequestUpdaterFactory _updaterFactory = updaterFactory;
+    private readonly ISqlBarClient _sqlClient = sqlClient;
 
     public override async Task<bool> ProcessWorkItemAsync(
         SubscriptionUpdateWorkItem workItem,
         CancellationToken cancellationToken)
     {
+        var build = await _sqlClient.GetBuildAsync(workItem.BuildId)
+            ?? throw new InvalidOperationException($"Build with buildId {workItem.BuildId} not found in the DB.");
path: src/ProductConstructionService/ProductConstructionService.DependencyFlow/WorkItemProcessors/SubscriptionUpdateProcessor.cs
user: login: premun
body: When a subscription is missing, it does happen every time we have an ongoing PR and we delete the subscription which can happen in real world as well as each time we run scenario tests and then clean up after ourselves.

For builds, those should only go missing during scenario tests (not sure we delete them from BAR or not).

So I guess it depends what we query first during processing - subscription or build - and how we want it to behave. 
It also depends if someone can queue a subscription trigger with a non-existent build ID or whether darc/API verifies this first.
created_at: 2025-05-06T10:57:37Z
author_association: MEMBER

PR comments:
user: login: adamzip
created_at: 2025-05-05T12:17:50Z
body: > **src/ProductConstructionService/ProductConstructionService.DependencyFlow/IPullRequestUpdater.cs:16**
> 
> * Update the method documentation to reflect the new 'build' parameter in ProcessPendingUpdatesAsync, explaining how it is used to retrieve asset details.

Doesn't seem appropriate or necessary to document this in the interface.

> ```
> Task ProcessPendingUpdatesAsync(SubscriptionUpdateWorkItem update, bool forceApply, Build? build);
> ```
> 
> **test/ProductConstructionService.DependencyFlow.Tests/PendingUpdatePullRequestUpdaterTests.cs:20**
> 
> * Consider adding tests to verify that when a null build is passed, the build lookup logic within ProcessPendingUpdatesAsync is correctly triggered and handled.

Don't think it's necessary - it's already a nullable `Build?` in the method arguments. It's clear that the object can be null and that the null case must be handled in code


author_association: CONTRIBUTOR
user: login: premun
created_at: 2025-05-05T13:47:50Z
body: > Don't think it's necessary - it's already a nullable `Build?` in the method arguments. It's clear that the object can be null and that the null case must be handled in code

What I think it's saying is that we never test if passing `null` actually triggers querying it and correctly looks it up. But I don't think we will need it if it's not optional anymore.
author_association: MEMBER
user: login: dkurepa
created_at: 2025-05-05T15:10:08Z
body: How is this PR actually solving the issue of us storing assets in the queue reminder? I'd expect to see some change in `SubscriptionUpdateWorkItem`
author_association: MEMBER
user: login: premun
created_at: 2025-05-05T15:11:34Z
body: Ah, good point, `SubscriptionUpdateWorkItem` needs to lose some members
author_association: MEMBER
user: login: premun
created_at: 2025-05-05T15:38:57Z
body: @adamzip can you prioritize merging this one as it's causing an active problem?
author_association: MEMBER
user: login: adamzip
created_at: 2025-05-06T09:01:03Z
body: > How is this PR actually solving the issue of us storing assets in the queue reminder? I'd expect to see some change in `SubscriptionUpdateWorkItem`

I've removed the places where we write the assets into the work item. The last commit now also removes the property from the class 👍 
author_association: CONTRIBUTOR