Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/design/package-house.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,23 @@ borrowed operation authority as House ownership.
Browser/Wasm and query adapters supply their host-created clients through the
same lease contract.

Standalone API coordinate matching is a compile-realization adopter. The
host-neutral query issues one exact `Realize` request per distinct endpoint
with `Surface` demand and consumes the House-issued compile selection when
creating each Package Root. Coordinate matching observes the complete selected
API population, its declarations, assembly identities, references, and type
forwarders; it does not execute or inspect method bodies, and a package without
a separate reference surface already uses its `lib/` assets as that surface.
The destination host execution retains the source acquisition's typed producer
identity. The host supplies store, transfer, source-authorization, and
operation-lifetime policy; the query neither requests a complete payload nor
reruns package asset selection. The CLI's operation-local store does not
persist ranged entries: distinct endpoints are each realized once, and an
equal destination reuses the source Root without another acquisition. The
settlement-owned ranged content therefore remains the one retained copy of
selected entry bytes. `ApiCoordinateMatchInspectionTests` gates the existing Avalonia
forwarder result, exact House request shape, and destination producer affinity.

This is the adopted PackageHouse operation-ownership step 17b under
[#6544](https://github.com/richlander/dotnet-inspect/issues/6544). Library
ownership, Workspace admission, and host adoption remain separate focused
Expand Down
6 changes: 3 additions & 3 deletions src/DotnetInspect.Cli/Commands/ApiCoordinateMatchCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ internal static async Task<int> ExecuteAsync(
ApiCoordinateMatchOptionsParser.Success options,
CancellationToken cancellationToken)
{
await using var provider =
new ConfiguredPackageRootPayloadProvider(
await using var executor =
new ConfiguredApiCoordinateMatchHouseExecutor(
HttpClientFactory.Shared.Timeout,
options.SourceOptions);
try
{
InspectionEnvelope<ApiCoordinateMatchContent> envelope =
await ApiCoordinateMatchInspection.ExecuteAsync(
options.Request,
provider,
executor.Execution,
cancellationToken).ConfigureAwait(false);
return ApiCoordinateMatchOutput.Write(envelope, options);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using DotnetInspector.Packages;
using DotnetInspector.Queries;
using NuGetFetch;

namespace DotnetInspect.Cli.Commands;

internal sealed class ConfiguredApiCoordinateMatchHouseExecutor
: IApiCoordinateMatchHouseExecutor, IAsyncDisposable
{
readonly DesktopPackageSourceComposition _composition;
readonly NuGetSourceOptions? _sourceOptions;
readonly Dictionary<ConfiguredPackageAuthority, IPackageStore> _stores =
new(ReferenceEqualityComparer.Instance);
readonly PackagePayloadAcquisitionPlan _payloadAcquisition;

internal ConfiguredApiCoordinateMatchHouseExecutor(
TimeSpan requestTimeout,
NuGetSourceOptions? sourceOptions)
{
_composition =
new DesktopPackageSourceComposition(requestTimeout);
_sourceOptions = sourceOptions;
_payloadAcquisition = new(
GetStore,
PackagePayloadLimits.Default,
access: PackagePayloadAccess.Ranged);
Execution = new(
_composition.CreateHouseOperation(
PackageHouseOperationProfile.Realize),
this);
}

internal ApiCoordinateMatchHouseExecution Execution { get; }

public async Task<PackageHouseSettlement> ExecuteAsync(
PackageHouseRequest request,
PackageProducerIdentity? requiredProducer,
CancellationToken cancellationToken = default)
{
PackageHouse house = _composition.CreateRealizationHouse(
_payloadAcquisition,
_sourceOptions,
requiredProducerKey: requiredProducer?.PortableKey);
PackageSourceOperationLease? operation =
_composition.IssueSettlementOperation(cancellationToken);
try
{
Task<PackageHouseSettlement> pending =
house.ExecuteAsync(request, operation);
operation = null;
return await pending.ConfigureAwait(false);
}
finally
{
operation?.Dispose();
}
}

public ValueTask DisposeAsync() => _composition.DisposeAsync();

IPackageStore GetStore(
ConfiguredPackageAuthority authority,
PackageProducerIdentity producer)
{
if (!_stores.TryGetValue(authority, out IPackageStore? store))
{
store = new OperationLocalPackageStore();
_stores.Add(authority, store);
}
return store;
}

private sealed class OperationLocalPackageStore : IPackageStore
{
readonly InMemoryPackageStore _inner = new();

public IPackageContent? TryGetCached(
string packageName,
string version,
IReadOnlyList<string>? allowedSourceKeys,
Action<string>? log = null) =>
_inner.TryGetCached(
packageName,
version,
allowedSourceKeys,
log);

public IEnumerable<IPackageContent> EnumerateCached(
string packageName,
string version,
IReadOnlyList<string>? allowedSourceKeys,
Action<string>? log = null) =>
_inner.EnumerateCached(
packageName,
version,
allowedSourceKeys,
log);

public ValueTask<IPackageContent> CommitAsync(
string packageName,
string version,
string sourceKey,
Stream nupkg,
CancellationToken cancellationToken = default) =>
_inner.CommitAsync(
packageName,
version,
sourceKey,
nupkg,
cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,31 @@ public PackageHouse CreateDependencyContentQueryHouse(
public PackageHouse CreateRealizationHouse(
PackagePayloadAcquisitionPlan payloadAcquisition,
NuGetSourceOptions? sourceOptions = null,
Action<string>? log = null)
Action<string>? log = null,
string? requiredProducerKey = null)
{
ArgumentNullException.ThrowIfNull(payloadAcquisition);
return new PackageHouse(
new CompositionAuthorization(this, sourceOptions),
new CompositionAuthorization(
this,
sourceOptions,
requiredProducerKey),
payloadAcquisition,
log,
_versionSettlement);
}

/// <summary>
/// Creates a House operation declaration with this composition's exact
/// request and operation deadlines.
/// </summary>
public PackageHouseOperation CreateHouseOperation(
PackageHouseOperationProfile profile) =>
PackageHouseOperation.Create(
profile,
_options.RequestTimeout,
_options.OperationTimeout);

/// <summary>Issues the source-owned operation consumed by a shared inspection.</summary>
public PackageSourceOperationLease IssueSettlementOperation(
CancellationToken cancellationToken = default) =>
Expand Down Expand Up @@ -611,13 +626,20 @@ public PackageSourceAuthorization AuthorizeSourcesFor(

private sealed class CompositionAuthorization(
DesktopPackageSourceComposition composition,
NuGetSourceOptions? sourceOptions)
NuGetSourceOptions? sourceOptions,
string? requiredProducerKey)
: IPackageSourceAuthorization
{
public PackageSourceAuthorization AuthorizeSourcesFor(
string packageId) =>
composition.AuthorizeSourcesFor(
packageId,
sourceOptions);
requiredProducerKey is null
? composition.AuthorizeSourcesFor(
packageId,
sourceOptions)
: composition.AuthorizeHouseSources(
packageId,
sourceOptions,
requiredProducerKey)
.AuthorizeSourcesFor(packageId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ public static class ApiCoordinateMatchInspection
{
public static async Task<InspectionEnvelope<ApiCoordinateMatchContent>> ExecuteAsync(
ApiCoordinateMatchRequest request,
IPackageRootPayloadProvider provider,
ApiCoordinateMatchHouseExecution execution,
CancellationToken cancellationToken = default) =>
Complete(Project(await ApiCoordinateMatchQuery.ExecuteAsync(
request, provider, cancellationToken).ConfigureAwait(false)));
request, execution, cancellationToken).ConfigureAwait(false)));

public static async Task<InspectionEnvelope<ApiCoordinateMatchContent>> ExecuteAsync(
InspectionWorkspace workspace,
Expand Down
Loading