using Launcher.Next.Core; namespace Launcher.Next.Application; public sealed class PackageResolutionUseCase { private readonly IPackageCatalogPackageAcquirer catalog; private readonly IPackageSynchronizer synchronizer; private readonly IPackageRequirementCacheBinder binder; private readonly ILastKnownGoodPackageResolver cache; private readonly ILauncherTelemetry telemetry; private readonly IPackageMaterializer? materializer; public PackageResolutionUseCase(IPackageCatalogPackageAcquirer c, IPackageSynchronizer s, IPackageRequirementCacheBinder b, ILastKnownGoodPackageResolver l, ILauncherTelemetry t) { catalog=c; synchronizer=s; binder=b; cache=l; telemetry=t; } public PackageResolutionUseCase(IPackageCatalogPackageAcquirer c, IPackageSynchronizer s, IPackageRequirementCacheBinder b, ILastKnownGoodPackageResolver l, IPackageMaterializer m, ILauncherTelemetry t) : this(c,s,b,l,t) { materializer=m; } public async Task ResolveAsync(PackageRequirement requirement, CancellationToken cancellationToken) { var evidence = new List { new("PACKAGE_REQUIREMENT_RECEIVED", requirement.PackageId) }; telemetry.Write("PackageResolutionStarted", new Dictionary { ["packageId"] = requirement.PackageId, ["product"] = requirement.Product, ["channel"] = requirement.Channel, ["offlineAllowed"] = requirement.AllowOfflineFallback }); try { var online = await catalog.AcquireAsync(requirement, cancellationToken); if (online is not null && Matches(requirement, online.Manifest)) { if (materializer is not null) { var committed = await materializer.MaterializeAsync(online.Manifest, online.SourceRoot, cancellationToken); await binder.BindAsync(requirement, committed.Version, cancellationToken); evidence.Add(new("ONLINE_PACKAGE_MATERIALIZED_NOT_ACTIVATED", committed.Version)); return new(new CachedPackageResolution(committed.PackageId, committed.Version, committed.LocalPackageRoot, false, "MATERIALIZED_NOT_ACTIVATED"), true, evidence); } await synchronizer.SynchronizeAsync(online.Manifest, online.SourceRoot, cancellationToken); await binder.BindAsync(requirement, online.Manifest.Version, cancellationToken); var selected = await cache.ResolveAsync(requirement, cancellationToken); if (selected is not null && !selected.IsOfflineFallback) { evidence.Add(new("ONLINE_PACKAGE_SELECTED", online.Manifest.Version)); telemetry.Write("PackageResolutionSelected", new Dictionary { ["code"] = "ONLINE_PACKAGE_SELECTED", ["packageId"] = selected.PackageId, ["version"] = selected.Version }); return new(selected, true, evidence); } evidence.Add(new("ONLINE_PACKAGE_REJECTED", "LOCAL_REVALIDATION_FAILED")); } else evidence.Add(new("CATALOG_UNAVAILABLE", online is null ? "NO_CATALOG_PACKAGE" : "REQUIREMENT_MISMATCH")); } catch (Exception ex) when (ex is not OperationCanceledException) { evidence.Add(new("CATALOG_UNAVAILABLE", "ACQUISITION_OR_STAGING_FAILED")); } if (!requirement.AllowOfflineFallback) { evidence.Add(new("OFFLINE_FALLBACK_DISABLED", "POLICY")); evidence.Add(new("NO_SAFE_PACKAGE_AVAILABLE", "BLOCKED")); telemetry.Write("PackageResolutionBlocked", new Dictionary { ["code"] = "OFFLINE_FALLBACK_DISABLED", ["packageId"] = requirement.PackageId }); return new(null, false, evidence); } var cached = await cache.ResolveAsync(requirement, cancellationToken); if (cached is null) { evidence.Add(new("NO_SAFE_PACKAGE_AVAILABLE", "CACHE_REJECTED")); telemetry.Write("PackageResolutionBlocked", new Dictionary { ["code"] = "NO_SAFE_PACKAGE_AVAILABLE", ["packageId"] = requirement.PackageId }); return new(null, false, evidence); } var code = cached.IsOfflineFallback ? "PREVIOUS_LKG_SELECTED" : "VALID_CURRENT_CACHE_SELECTED"; evidence.Add(new(code, cached.Version)); telemetry.Write("PackageResolutionSelected", new Dictionary { ["code"] = code, ["packageId"] = cached.PackageId, ["version"] = cached.Version }); return new(cached, false, evidence); } private static bool Matches(PackageRequirement r, PackageManifest m) => string.Equals(r.PackageId, m.PackageId, StringComparison.Ordinal) && string.Equals(r.Product, m.Product, StringComparison.Ordinal) && r.ProductYear == m.ProductYear && string.Equals(r.Architecture, m.Architecture, StringComparison.OrdinalIgnoreCase) && string.Equals(r.Channel, m.RolloutChannel, StringComparison.OrdinalIgnoreCase) && string.Equals(r.RequiredVersion, m.Version, StringComparison.Ordinal); }