diff --git a/Directory.Packages.props b/Directory.Packages.props
index e67d992..4977bbd 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -87,6 +87,13 @@
+
+
+
diff --git a/DodoSSH.slnx b/DodoSSH.slnx
index 5211eb3..d685d4c 100644
--- a/DodoSSH.slnx
+++ b/DodoSSH.slnx
@@ -19,6 +19,7 @@
+
diff --git a/src/DodoSSH.Api/appsettings.json b/src/DodoSSH.Api/appsettings.json
index b0e8796..a40458d 100644
--- a/src/DodoSSH.Api/appsettings.json
+++ b/src/DodoSSH.Api/appsettings.json
@@ -7,14 +7,10 @@
}
},
"AllowedHosts": "*",
- "ConnectionStrings": {
- "Postgres": ""
- },
"Server": {
"PublicBaseUrl": "http://localhost:5233"
},
"Oidc": {
- "Authority": "",
"Audience": "dodossh-api",
"ClientId": "dodossh-desktop",
"LoopbackRedirectPattern": "http://127.0.0.1:*/callback",
diff --git a/tests/DodoSSH.Api.Tests/ApiFixture.cs b/tests/DodoSSH.Api.Tests/ApiFixture.cs
new file mode 100644
index 0000000..ccf1944
--- /dev/null
+++ b/tests/DodoSSH.Api.Tests/ApiFixture.cs
@@ -0,0 +1,112 @@
+using System.Net.Http.Headers;
+using DodoSSH.Infrastructure;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Testcontainers.PostgreSql;
+using Xunit;
+
+namespace DodoSSH.Api.Tests;
+
+///
+/// Hosts the API in-process against a real PostgreSQL container and a stubbed identity provider.
+///
+///
+/// One container and one host per assembly. Tests therefore use distinct users and vaults rather
+/// than assuming an empty database.
+///
+public sealed class ApiFixture : WebApplicationFactory, IAsyncLifetime
+{
+ private readonly PostgreSqlContainer container = new PostgreSqlBuilder("postgres:18-alpine")
+ .WithDatabase("dodossh")
+ .WithUsername("postgres")
+ .WithPassword("test")
+ .Build();
+
+ /// The stubbed identity provider.
+ public StubIdentityProvider IdentityProvider { get; } = new();
+
+ ///
+ public async ValueTask InitializeAsync()
+ {
+ await container.StartAsync();
+
+ await using var scope = Services.CreateAsyncScope();
+ var database = scope.ServiceProvider.GetRequiredService();
+ await database.Database.MigrateAsync();
+ }
+
+ ///
+ public override async ValueTask DisposeAsync()
+ {
+ await base.DisposeAsync();
+ await container.DisposeAsync();
+ IdentityProvider.Dispose();
+ }
+
+ ///
+ protected override void ConfigureWebHost(IWebHostBuilder builder)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ builder.UseEnvironment("Testing");
+
+ // Environment variables rather than ConfigureAppConfiguration. Under minimal hosting the
+ // application's own configuration sources are added inside Program.cs after this callback
+ // runs, so an appsettings.json value would win over anything registered here. Program.cs
+ // adds AddEnvironmentVariables("DODOSSH_") last, which makes these authoritative.
+ var settings = new Dictionary(StringComparer.Ordinal)
+ {
+ ["DODOSSH_ConnectionStrings__Postgres"] = container.GetConnectionString(),
+ ["DODOSSH_Server__PublicBaseUrl"] = "http://localhost",
+ ["DODOSSH_Oidc__Authority"] = IdentityProvider.Authority,
+ ["DODOSSH_Oidc__Audience"] = StubIdentityProvider.Audience,
+ ["DODOSSH_Oidc__ClientId"] = "dodossh-desktop",
+
+ // The stub serves plaintext HTTP on a loopback port.
+ ["DODOSSH_Oidc__RequireHttpsMetadata"] = "false",
+
+ ["DODOSSH_Relay__Enabled"] = "false",
+
+ // Fixed so cursors stay valid for the lifetime of the test host.
+ ["DODOSSH_Sync__CursorSigningKey"] = Convert.ToBase64String(new byte[32]),
+ };
+
+ foreach (var (name, value) in settings)
+ {
+ Environment.SetEnvironmentVariable(name, value);
+ }
+ }
+
+ /// Creates a client carrying a valid token for the given subject.
+ public HttpClient CreateClientFor(string subject, string? email = null)
+ {
+ var client = CreateClient();
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
+ "Bearer",
+ IdentityProvider.MintToken(subject, email));
+
+ return client;
+ }
+
+ /// Creates a client carrying the supplied raw token.
+ public HttpClient CreateClientWithToken(string token)
+ {
+ var client = CreateClient();
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
+ return client;
+ }
+
+ /// Opens a database scope for arranging state and asserting on it.
+ public AsyncServiceScope CreateScope() => Services.CreateAsyncScope();
+}
+
+/// Shares one host and container across every test class in the assembly.
+[CollectionDefinition(Name)]
+public sealed class ApiCollection : ICollectionFixture
+{
+ /// Collection name.
+ public const string Name = "api";
+}
diff --git a/tests/DodoSSH.Api.Tests/DodoSSH.Api.Tests.csproj b/tests/DodoSSH.Api.Tests/DodoSSH.Api.Tests.csproj
new file mode 100644
index 0000000..7a26fd2
--- /dev/null
+++ b/tests/DodoSSH.Api.Tests/DodoSSH.Api.Tests.csproj
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/DodoSSH.Api.Tests/StubIdentityProvider.cs b/tests/DodoSSH.Api.Tests/StubIdentityProvider.cs
new file mode 100644
index 0000000..04ef1b6
--- /dev/null
+++ b/tests/DodoSSH.Api.Tests/StubIdentityProvider.cs
@@ -0,0 +1,171 @@
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Cryptography;
+using System.Text.Json;
+using Microsoft.IdentityModel.Tokens;
+using WireMock.RequestBuilders;
+using WireMock.ResponseBuilders;
+using WireMock.Server;
+
+namespace DodoSSH.Api.Tests;
+
+///
+/// A stand-in identity provider: OIDC discovery, JWKS, and token minting.
+///
+///
+/// Deliberately not a TestAuthHandler that short-circuits authentication. Tokens are signed
+/// with a real RSA key and validated by the application's own JwtBearer pipeline, so issuer,
+/// audience, signature, lifetime and claim handling are all genuinely exercised. Bypassing that
+/// would hide precisely the claim-mapping mistakes that cause real authorization holes.
+///
+public sealed class StubIdentityProvider : IDisposable
+{
+ private const string KeyId = "dodossh-test-key";
+
+ private readonly WireMockServer server;
+ private readonly RsaSecurityKey signingKey;
+
+ public StubIdentityProvider()
+ {
+ var rsa = RSA.Create(2048);
+ signingKey = new RsaSecurityKey(rsa) { KeyId = KeyId };
+
+ server = WireMockServer.Start();
+ Authority = server.Url!.TrimEnd('/');
+
+ StubDiscovery();
+ StubJwks();
+ }
+
+ /// Issuer URL, matching what the tokens claim.
+ public string Authority { get; }
+
+ /// Audience the API is configured to expect.
+ public static string Audience => "dodossh-api";
+
+ ///
+ /// Mints a signed access token.
+ ///
+ /// The sub claim — the stable user identifier.
+ /// Optional email claim.
+ /// Optional display name claim.
+ /// Override the audience, to test rejection.
+ /// Override the issuer, to test rejection.
+ /// Override expiry, to test rejection.
+ public string MintToken(
+ string subject,
+ string? email = null,
+ string? name = null,
+ string? audience = null,
+ string? issuer = null,
+ DateTime? expires = null)
+ {
+ var now = TimeProvider.System.GetUtcNow().UtcDateTime;
+
+ var claims = new List
+ {
+ new("sub", subject),
+ };
+
+ if (email is not null)
+ {
+ claims.Add(new System.Security.Claims.Claim("email", email));
+ }
+
+ if (name is not null)
+ {
+ claims.Add(new System.Security.Claims.Claim("name", name));
+ }
+
+ var expiry = expires ?? now.AddMinutes(15);
+
+ // The earlier of now-1min and exp-1min. A fixed now-1min would sit after the expiry of a
+ // deliberately-expired test token (rejected at construction), while exp-1min alone would
+ // put nbf in the future for a normal token and make every valid token unauthorized.
+ var notBefore = expiry < now ? expiry.AddMinutes(-1) : now.AddMinutes(-1);
+
+ var token = new JwtSecurityToken(
+ issuer: issuer ?? Authority,
+ audience: audience ?? Audience,
+ claims: claims,
+ notBefore: notBefore,
+ expires: expiry,
+ signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.RsaSha256));
+
+ return new JwtSecurityTokenHandler().WriteToken(token);
+ }
+
+ /// Mints a token signed by a different key, which must be rejected.
+ public string MintTokenWithForeignKey(string subject)
+ {
+ var foreign = new RsaSecurityKey(RSA.Create(2048)) { KeyId = KeyId };
+ var now = TimeProvider.System.GetUtcNow().UtcDateTime;
+
+ var token = new JwtSecurityToken(
+ issuer: Authority,
+ audience: Audience,
+ claims: [new System.Security.Claims.Claim("sub", subject)],
+ notBefore: now.AddMinutes(-1),
+ expires: now.AddMinutes(15),
+ signingCredentials: new SigningCredentials(foreign, SecurityAlgorithms.RsaSha256));
+
+ return new JwtSecurityTokenHandler().WriteToken(token);
+ }
+
+ private void StubDiscovery()
+ {
+ var document = new Dictionary(StringComparer.Ordinal)
+ {
+ ["issuer"] = Authority,
+ ["jwks_uri"] = $"{Authority}/.well-known/jwks.json",
+ ["authorization_endpoint"] = $"{Authority}/connect/authorize",
+ ["token_endpoint"] = $"{Authority}/connect/token",
+ ["response_types_supported"] = new[] { "code" },
+ ["subject_types_supported"] = new[] { "public" },
+ ["id_token_signing_alg_values_supported"] = new[] { "RS256" },
+ ["code_challenge_methods_supported"] = new[] { "S256" },
+ };
+
+ server
+ .Given(Request.Create().WithPath("/.well-known/openid-configuration").UsingGet())
+ .RespondWith(Response.Create()
+ .WithStatusCode(200)
+ .WithHeader("Content-Type", "application/json")
+ .WithBody(JsonSerializer.Serialize(document)));
+ }
+
+ private void StubJwks()
+ {
+ var parameters = signingKey.Rsa.ExportParameters(includePrivateParameters: false);
+
+ var jwks = new
+ {
+ keys = new[]
+ {
+ new
+ {
+ kty = "RSA",
+ use = "sig",
+ kid = KeyId,
+ alg = "RS256",
+ n = Base64UrlEncoder.Encode(parameters.Modulus),
+ e = Base64UrlEncoder.Encode(parameters.Exponent),
+ },
+ },
+ };
+
+ server
+ .Given(Request.Create().WithPath("/.well-known/jwks.json").UsingGet())
+ .RespondWith(Response.Create()
+ .WithStatusCode(200)
+ .WithHeader("Content-Type", "application/json")
+ .WithBody(JsonSerializer.Serialize(jwks)));
+ }
+
+ ///
+ public void Dispose()
+ {
+ server.Stop();
+ server.Dispose();
+ signingKey.Rsa.Dispose();
+ }
+}
diff --git a/tests/DodoSSH.Api.Tests/SyncEndpointTests.cs b/tests/DodoSSH.Api.Tests/SyncEndpointTests.cs
new file mode 100644
index 0000000..837b221
--- /dev/null
+++ b/tests/DodoSSH.Api.Tests/SyncEndpointTests.cs
@@ -0,0 +1,716 @@
+using System.Net;
+using System.Net.Http.Json;
+using DodoSSH.Contracts;
+using DodoSSH.Domain;
+using DodoSSH.Infrastructure;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace DodoSSH.Api.Tests;
+
+///
+/// End-to-end sync behaviour over HTTP, and the authorization denials.
+///
+///
+/// The denial tests are the most important thing here. Every one of them asserts that a caller who
+/// should not reach a vault does not, through the real authentication pipeline rather than a
+/// bypassed one.
+///
+[Collection(ApiCollection.Name)]
+public sealed class SyncEndpointTests(ApiFixture fixture)
+{
+ private static readonly DateTimeOffset Now = new(2026, 7, 28, 12, 0, 0, TimeSpan.Zero);
+
+ // ---- Authentication ----
+
+ [Fact]
+ public async Task Pull_WithoutAToken_Is401()
+ {
+ var client = fixture.CreateClient();
+
+ var response = await client.PostAsJsonAsync(
+ PullUrl(Guid.CreateVersion7()),
+ new SyncPullRequest(null, null, null));
+
+ response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized);
+ }
+
+ [Fact]
+ public async Task Push_WithoutAToken_Is401()
+ {
+ var client = fixture.CreateClient();
+
+ var response = await client.PostAsJsonAsync(
+ PushUrl(Guid.CreateVersion7()),
+ new SyncPushRequest([]));
+
+ response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized);
+ }
+
+ [Fact]
+ public async Task ATokenSignedByAnotherKey_Is401()
+ {
+ // Proves signature validation is genuinely running, not stubbed out.
+ var client = fixture.CreateClientWithToken(
+ fixture.IdentityProvider.MintTokenWithForeignKey(NewSubject()));
+
+ var response = await client.PostAsJsonAsync(
+ PullUrl(Guid.CreateVersion7()),
+ new SyncPullRequest(null, null, null));
+
+ response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized);
+ }
+
+ [Fact]
+ public async Task ATokenForAnotherAudience_Is401()
+ {
+ var client = fixture.CreateClientWithToken(
+ fixture.IdentityProvider.MintToken(NewSubject(), audience: "some-other-api"));
+
+ var response = await client.PostAsJsonAsync(
+ PullUrl(Guid.CreateVersion7()),
+ new SyncPullRequest(null, null, null));
+
+ response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized);
+ }
+
+ [Fact]
+ public async Task ATokenFromAnotherIssuer_Is401()
+ {
+ var client = fixture.CreateClientWithToken(
+ fixture.IdentityProvider.MintToken(NewSubject(), issuer: "https://evil.example"));
+
+ var response = await client.PostAsJsonAsync(
+ PullUrl(Guid.CreateVersion7()),
+ new SyncPullRequest(null, null, null));
+
+ response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized);
+ }
+
+ [Fact]
+ public async Task AnExpiredToken_Is401()
+ {
+ var client = fixture.CreateClientWithToken(fixture.IdentityProvider.MintToken(
+ NewSubject(),
+ expires: TimeProvider.System.GetUtcNow().UtcDateTime.AddMinutes(-10)));
+
+ var response = await client.PostAsJsonAsync(
+ PullUrl(Guid.CreateVersion7()),
+ new SyncPullRequest(null, null, null));
+
+ response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized);
+ }
+
+ // ---- Authorization: the wrong user must be denied ----
+
+ [Fact]
+ public async Task Pull_AnotherUsersVault_Is404()
+ {
+ // 404 rather than 403: a distinct "exists but forbidden" answer would let a caller
+ // enumerate other tenants' vault ids.
+ var (_, vaultId) = await SeedUserWithVaultAsync();
+ var intruder = fixture.CreateClientFor(NewSubject());
+
+ var response = await intruder.PostAsJsonAsync(
+ PullUrl(vaultId),
+ new SyncPullRequest(null, null, null));
+
+ response.StatusCode.ShouldBe(HttpStatusCode.NotFound);
+ }
+
+ [Fact]
+ public async Task Push_AnotherUsersVault_Is404()
+ {
+ var (_, vaultId) = await SeedUserWithVaultAsync();
+ var intruder = fixture.CreateClientFor(NewSubject());
+
+ var response = await intruder.PostAsJsonAsync(PushUrl(vaultId), NewCreateBatch());
+
+ response.StatusCode.ShouldBe(HttpStatusCode.NotFound);
+ }
+
+ [Fact]
+ public async Task Push_AnotherUsersVault_WritesNothing()
+ {
+ // A denial that still mutated state would be worse than no check at all.
+ var (_, vaultId) = await SeedUserWithVaultAsync();
+ var intruder = fixture.CreateClientFor(NewSubject());
+ var batch = NewCreateBatch();
+
+ await intruder.PostAsJsonAsync(PushUrl(vaultId), batch);
+
+ await using var scope = fixture.CreateScope();
+ var database = scope.ServiceProvider.GetRequiredService();
+
+ (await database.Hosts.AnyAsync(h => h.VaultId == vaultId)).ShouldBeFalse();
+ (await database.VaultChanges.AnyAsync(c => c.VaultId == vaultId)).ShouldBeFalse();
+ }
+
+ [Fact]
+ public async Task Pull_ANonexistentVault_Is404()
+ {
+ var client = fixture.CreateClientFor(NewSubject());
+
+ var response = await client.PostAsJsonAsync(
+ PullUrl(Guid.CreateVersion7()),
+ new SyncPullRequest(null, null, null));
+
+ response.StatusCode.ShouldBe(HttpStatusCode.NotFound);
+ }
+
+ [Fact]
+ public async Task ATeamVault_IsDeniedUntilTeamsShip()
+ {
+ // Failing closed on an unimplemented path, rather than falling through to a default.
+ var vaultId = await SeedTeamVaultAsync();
+ var client = fixture.CreateClientFor(NewSubject());
+
+ var response = await client.PostAsJsonAsync(
+ PullUrl(vaultId),
+ new SyncPullRequest(null, null, null));
+
+ response.StatusCode.ShouldBe(HttpStatusCode.NotFound);
+ }
+
+ // ---- Round trip ----
+
+ [Fact]
+ public async Task Push_ThenPull_ReturnsTheItem()
+ {
+ var (subject, vaultId) = await SeedUserWithVaultAsync();
+ var client = fixture.CreateClientFor(subject);
+
+ var batch = NewCreateBatch();
+ var push = await client.PostAsJsonAsync(PushUrl(vaultId), batch);
+ push.EnsureSuccessStatusCode();
+
+ var pushed = await push.Content.ReadFromJsonAsync();
+ pushed.ShouldNotBeNull();
+ pushed.Results.Count.ShouldBe(1);
+ pushed.Results[0].Status.ShouldBe(SyncOperationStatus.Applied);
+ pushed.Results[0].Version.ShouldBe(1);
+
+ var pull = await client.PostAsJsonAsync(
+ PullUrl(vaultId),
+ new SyncPullRequest(null, null, null));
+ pull.EnsureSuccessStatusCode();
+
+ var pulled = await pull.Content.ReadFromJsonAsync();
+ pulled.ShouldNotBeNull();
+ pulled.Changes.Count.ShouldBe(1);
+
+ var change = pulled.Changes[0];
+ change.EntityId.ShouldBe(batch.Operations[0].EntityId);
+ change.Operation.ShouldBe(SyncOperation.Upsert);
+ change.Payload.ShouldNotBeNull();
+ change.Payload.Envelope.ShouldBe(batch.Operations[0].Payload!.Envelope);
+ }
+
+ [Fact]
+ public async Task Pull_WithACursor_ReturnsOnlyNewerChanges()
+ {
+ var (subject, vaultId) = await SeedUserWithVaultAsync();
+ var client = fixture.CreateClientFor(subject);
+
+ await client.PostAsJsonAsync(PushUrl(vaultId), NewCreateBatch());
+
+ var first = await (await client.PostAsJsonAsync(
+ PullUrl(vaultId),
+ new SyncPullRequest(null, null, null))).Content.ReadFromJsonAsync();
+ first.ShouldNotBeNull();
+
+ // Nothing new since that cursor.
+ var empty = await (await client.PostAsJsonAsync(
+ PullUrl(vaultId),
+ new SyncPullRequest(first.NextCursor, null, null)))
+ .Content.ReadFromJsonAsync();
+ empty.ShouldNotBeNull();
+ empty.Changes.ShouldBeEmpty();
+
+ // The cursor must not have rewound, or the next poll would replay history.
+ empty.NextCursor.ShouldBe(first.NextCursor);
+
+ await client.PostAsJsonAsync(PushUrl(vaultId), NewCreateBatch());
+
+ var second = await (await client.PostAsJsonAsync(
+ PullUrl(vaultId),
+ new SyncPullRequest(first.NextCursor, null, null)))
+ .Content.ReadFromJsonAsync();
+ second.ShouldNotBeNull();
+ second.Changes.Count.ShouldBe(1);
+ }
+
+ [Fact]
+ public async Task Pull_WithACursorFromAnotherVault_Is400()
+ {
+ var (subject, firstVault) = await SeedUserWithVaultAsync();
+ var (_, otherVault) = await SeedUserWithVaultAsync();
+ var client = fixture.CreateClientFor(subject);
+
+ var pull = await client.PostAsJsonAsync(
+ PullUrl(firstVault),
+ new SyncPullRequest(null, null, null));
+ var cursor = (await pull.Content.ReadFromJsonAsync())!.NextCursor;
+
+ // Correctly signed, but issued for a different vault.
+ var response = await client.PostAsJsonAsync(
+ PullUrl(otherVault),
+ new SyncPullRequest(cursor, null, null));
+
+ // 404 first, because this caller cannot see the other vault at all.
+ response.StatusCode.ShouldBe(HttpStatusCode.NotFound);
+ }
+
+ [Fact]
+ public async Task Pull_WithATamperedCursor_Is400()
+ {
+ var (subject, vaultId) = await SeedUserWithVaultAsync();
+ var client = fixture.CreateClientFor(subject);
+
+ var response = await client.PostAsJsonAsync(
+ PullUrl(vaultId),
+ new SyncPullRequest("bm90LWEtcmVhbC1jdXJzb3I", null, null));
+
+ response.StatusCode.ShouldBe(HttpStatusCode.BadRequest);
+
+ var problem = await response.Content.ReadFromJsonAsync();
+ problem.ShouldNotBeNull();
+ problem.Code.ShouldBe(ProblemCodes.InvalidCursor);
+ }
+
+ // ---- Conflict and idempotency ----
+
+ [Fact]
+ public async Task Push_WithAStaleVersion_ReportsConflictAndReturnsServerState()
+ {
+ var (subject, vaultId) = await SeedUserWithVaultAsync();
+ var client = fixture.CreateClientFor(subject);
+
+ var create = NewCreateBatch();
+ var entityId = create.Operations[0].EntityId;
+ await client.PostAsJsonAsync(PushUrl(vaultId), create);
+
+ // Update to version 2.
+ await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest(
+ [
+ NewOperation(entityId, expectedVersion: 1, envelope: [9, 9, 9]),
+ ]));
+
+ // A second client still believes it is on version 1.
+ var stale = await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest(
+ [
+ NewOperation(entityId, expectedVersion: 1, envelope: [7, 7, 7]),
+ ]));
+
+ stale.EnsureSuccessStatusCode();
+
+ var body = await stale.Content.ReadFromJsonAsync();
+ body.ShouldNotBeNull();
+ body.Results[0].Status.ShouldBe(SyncOperationStatus.Conflict);
+ body.Results[0].Version.ShouldBe(2);
+
+ // The server's current state comes back so the client can merge rather than guess.
+ body.Results[0].ServerEntity.ShouldNotBeNull();
+ body.Results[0].ServerEntity!.Payload!.Envelope.ShouldBe([9, 9, 9]);
+ }
+
+ [Fact]
+ public async Task Push_WithAConflict_DoesNotOverwrite()
+ {
+ var (subject, vaultId) = await SeedUserWithVaultAsync();
+ var client = fixture.CreateClientFor(subject);
+
+ var create = NewCreateBatch();
+ var entityId = create.Operations[0].EntityId;
+ await client.PostAsJsonAsync(PushUrl(vaultId), create);
+ await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest(
+ [
+ NewOperation(entityId, expectedVersion: 1, envelope: [9, 9, 9]),
+ ]));
+
+ await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest(
+ [
+ NewOperation(entityId, expectedVersion: 1, envelope: [7, 7, 7]),
+ ]));
+
+ await using var scope = fixture.CreateScope();
+ var database = scope.ServiceProvider.GetRequiredService();
+ var stored = await database.Hosts.SingleAsync(h => h.Id == entityId);
+
+ // Never last-writer-wins.
+ stored.Payload.ShouldBe([9, 9, 9]);
+ stored.Version.ShouldBe(2);
+ }
+
+ [Fact]
+ public async Task Push_ReplayingAnOperationId_IsReportedDuplicateAndAppliedOnce()
+ {
+ var (subject, vaultId) = await SeedUserWithVaultAsync();
+ var client = fixture.CreateClientFor(subject);
+
+ var batch = NewCreateBatch();
+
+ var first = await client.PostAsJsonAsync(PushUrl(vaultId), batch);
+ first.EnsureSuccessStatusCode();
+
+ // Exactly the same batch again, as a retry after a timeout would be.
+ var replay = await client.PostAsJsonAsync(PushUrl(vaultId), batch);
+ replay.EnsureSuccessStatusCode();
+
+ var body = await replay.Content.ReadFromJsonAsync();
+ body.ShouldNotBeNull();
+ body.Results[0].Status.ShouldBe(SyncOperationStatus.Duplicate);
+
+ await using var scope = fixture.CreateScope();
+ var database = scope.ServiceProvider.GetRequiredService();
+
+ var stored = await database.Hosts.SingleAsync(h => h.Id == batch.Operations[0].EntityId);
+ stored.Version.ShouldBe(1);
+
+ (await database.VaultChanges.CountAsync(c => c.EntityId == stored.Id)).ShouldBe(1);
+ }
+
+ [Fact]
+ public async Task Push_AMixedBatch_AppliesTheGoodAndReportsTheBad()
+ {
+ // One stale item must not block everything else a client queued while offline.
+ var (subject, vaultId) = await SeedUserWithVaultAsync();
+ var client = fixture.CreateClientFor(subject);
+
+ var existing = NewCreateBatch();
+ await client.PostAsJsonAsync(PushUrl(vaultId), existing);
+
+ var goodId = Guid.CreateVersion7();
+ var mixed = new SyncPushRequest(
+ [
+ NewOperation(existing.Operations[0].EntityId, expectedVersion: 99, envelope: [1]),
+ NewOperation(goodId, expectedVersion: null, envelope: [2, 2]),
+ ]);
+
+ var response = await client.PostAsJsonAsync(PushUrl(vaultId), mixed);
+
+ // 200 despite a failed operation: per-operation status carries the detail.
+ response.StatusCode.ShouldBe(HttpStatusCode.OK);
+
+ var body = await response.Content.ReadFromJsonAsync();
+ body.ShouldNotBeNull();
+ body.Results[0].Status.ShouldBe(SyncOperationStatus.Conflict);
+ body.Results[1].Status.ShouldBe(SyncOperationStatus.Applied);
+
+ await using var scope = fixture.CreateScope();
+ var database = scope.ServiceProvider.GetRequiredService();
+ (await database.Hosts.AnyAsync(h => h.Id == goodId)).ShouldBeTrue();
+ }
+
+ // ---- Relay field enforcement, ADR 0004 ----
+
+ [Fact]
+ public async Task Push_ARelayAddressWithoutEnablingRelay_IsRejected()
+ {
+ // Prevents the server quietly learning an address the user never opted into exposing.
+ var (subject, vaultId) = await SeedUserWithVaultAsync();
+ var client = fixture.CreateClientFor(subject);
+
+ var response = await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest(
+ [
+ new SyncPushOperation(
+ Guid.CreateVersion7(),
+ SyncEntityType.Host,
+ Guid.CreateVersion7(),
+ SyncOperation.Upsert,
+ null,
+ new EncryptedPayload([1, 2, 3], 1, 1),
+ new SyncPlaintextFields(RelayEnabled: false, Hostname: "secret.internal", Port: 22)),
+ ]));
+
+ response.EnsureSuccessStatusCode();
+
+ var body = await response.Content.ReadFromJsonAsync();
+ body!.Results[0].Status.ShouldBe(SyncOperationStatus.Invalid);
+ }
+
+ [Fact]
+ public async Task Push_RelayEnabledWithoutAnAddress_IsRejected()
+ {
+ var (subject, vaultId) = await SeedUserWithVaultAsync();
+ var client = fixture.CreateClientFor(subject);
+
+ var response = await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest(
+ [
+ new SyncPushOperation(
+ Guid.CreateVersion7(),
+ SyncEntityType.Host,
+ Guid.CreateVersion7(),
+ SyncOperation.Upsert,
+ null,
+ new EncryptedPayload([1, 2, 3], 1, 1),
+ new SyncPlaintextFields(RelayEnabled: true)),
+ ]));
+
+ response.EnsureSuccessStatusCode();
+
+ var body = await response.Content.ReadFromJsonAsync();
+ body!.Results[0].Status.ShouldBe(SyncOperationStatus.Invalid);
+ }
+
+ [Fact]
+ public async Task Delete_ClearsTheRelayAddress()
+ {
+ // Leaving it would keep the server able to resolve a host the user believes is gone.
+ var (subject, vaultId) = await SeedUserWithVaultAsync();
+ var client = fixture.CreateClientFor(subject);
+
+ var entityId = Guid.CreateVersion7();
+ await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest(
+ [
+ new SyncPushOperation(
+ Guid.CreateVersion7(),
+ SyncEntityType.Host,
+ entityId,
+ SyncOperation.Upsert,
+ null,
+ new EncryptedPayload([1, 2, 3], 1, 1),
+ new SyncPlaintextFields(RelayEnabled: true, Hostname: "bastion.internal", Port: 22)),
+ ]));
+
+ await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest(
+ [
+ new SyncPushOperation(
+ Guid.CreateVersion7(),
+ SyncEntityType.Host,
+ entityId,
+ SyncOperation.Delete,
+ ExpectedVersion: 1,
+ Payload: null,
+ PlaintextFields: null),
+ ]));
+
+ await using var scope = fixture.CreateScope();
+ var database = scope.ServiceProvider.GetRequiredService();
+ var stored = await database.Hosts.SingleAsync(h => h.Id == entityId);
+
+ stored.DeletedAtUtc.ShouldNotBeNull();
+ stored.RelayEnabled.ShouldBeFalse();
+ stored.Hostname.ShouldBeNull();
+ stored.Port.ShouldBeNull();
+ }
+
+ [Fact]
+ public async Task Pull_ADeletedItem_ReturnsATombstoneWithNoPayload()
+ {
+ var (subject, vaultId) = await SeedUserWithVaultAsync();
+ var client = fixture.CreateClientFor(subject);
+
+ var create = NewCreateBatch();
+ var entityId = create.Operations[0].EntityId;
+ await client.PostAsJsonAsync(PushUrl(vaultId), create);
+
+ await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest(
+ [
+ new SyncPushOperation(
+ Guid.CreateVersion7(),
+ SyncEntityType.Host,
+ entityId,
+ SyncOperation.Delete,
+ ExpectedVersion: 1,
+ Payload: null,
+ PlaintextFields: null),
+ ]));
+
+ var pull = await client.PostAsJsonAsync(
+ PullUrl(vaultId),
+ new SyncPullRequest(null, null, null));
+
+ var body = await pull.Content.ReadFromJsonAsync();
+ body.ShouldNotBeNull();
+
+ var tombstone = body.Changes.Last(c => c.EntityId == entityId);
+ tombstone.Operation.ShouldBe(SyncOperation.Delete);
+ tombstone.Payload.ShouldBeNull();
+ tombstone.PlaintextFields.ShouldBeNull();
+ }
+
+ // ---- Batch limits ----
+
+ [Fact]
+ public async Task Push_AnEmptyBatch_Is400()
+ {
+ var (subject, vaultId) = await SeedUserWithVaultAsync();
+ var client = fixture.CreateClientFor(subject);
+
+ var response = await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest([]));
+
+ response.StatusCode.ShouldBe(HttpStatusCode.BadRequest);
+ }
+
+ [Fact]
+ public async Task Push_AnUnsupportedEntityType_IsInvalidNotAFailedBatch()
+ {
+ // A newer client asking for something this server does not do yet gets a precise
+ // per-operation answer rather than a whole-batch rejection.
+ var (subject, vaultId) = await SeedUserWithVaultAsync();
+ var client = fixture.CreateClientFor(subject);
+
+ var response = await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest(
+ [
+ new SyncPushOperation(
+ Guid.CreateVersion7(),
+ SyncEntityType.Credential,
+ Guid.CreateVersion7(),
+ SyncOperation.Upsert,
+ null,
+ new EncryptedPayload([1], 1, 1),
+ null),
+ ]));
+
+ response.EnsureSuccessStatusCode();
+
+ var body = await response.Content.ReadFromJsonAsync();
+ body!.Results[0].Status.ShouldBe(SyncOperationStatus.Invalid);
+ }
+
+ // ---- JIT provisioning ----
+
+ [Fact]
+ public async Task AFirstRequest_ProvisionsTheUser()
+ {
+ var subject = NewSubject();
+ var email = $"{subject}@example.com";
+ var client = fixture.CreateClientFor(subject, email);
+
+ // Any authenticated call is enough to trigger provisioning.
+ await client.PostAsJsonAsync(
+ PullUrl(Guid.CreateVersion7()),
+ new SyncPullRequest(null, null, null));
+
+ await using var scope = fixture.CreateScope();
+ var database = scope.ServiceProvider.GetRequiredService();
+
+ var user = await database.Users.SingleOrDefaultAsync(u => u.Subject == subject);
+ user.ShouldNotBeNull();
+ user.Issuer.ShouldBe(fixture.IdentityProvider.Authority);
+ user.Email.ShouldBe(email);
+ user.Status.ShouldBe(UserStatus.Active);
+ }
+
+ [Fact]
+ public async Task RepeatedRequests_ProvisionOnlyOnce()
+ {
+ var subject = NewSubject();
+ var client = fixture.CreateClientFor(subject);
+
+ for (var i = 0; i < 3; i++)
+ {
+ await client.PostAsJsonAsync(
+ PullUrl(Guid.CreateVersion7()),
+ new SyncPullRequest(null, null, null));
+ }
+
+ await using var scope = fixture.CreateScope();
+ var database = scope.ServiceProvider.GetRequiredService();
+
+ (await database.Users.CountAsync(u => u.Subject == subject)).ShouldBe(1);
+ }
+
+ // ---- Helpers ----
+
+ private static string PullUrl(Guid vaultId) => $"/api/v1/vaults/{vaultId}/sync/pull";
+
+ private static string PushUrl(Guid vaultId) => $"/api/v1/vaults/{vaultId}/sync/push";
+
+ private static string NewSubject() => $"user-{Guid.CreateVersion7():N}";
+
+ private static SyncPushOperation NewOperation(Guid entityId, int? expectedVersion, byte[] envelope) =>
+ new(
+ Guid.CreateVersion7(),
+ SyncEntityType.Host,
+ entityId,
+ SyncOperation.Upsert,
+ expectedVersion,
+ new EncryptedPayload(envelope, 1, 1),
+ new SyncPlaintextFields());
+
+ private static SyncPushRequest NewCreateBatch() =>
+ new([NewOperation(Guid.CreateVersion7(), expectedVersion: null, envelope: [1, 2, 3, 4])]);
+
+ private async Task<(string Subject, Guid VaultId)> SeedUserWithVaultAsync()
+ {
+ var subject = NewSubject();
+
+ await using var scope = fixture.CreateScope();
+ var database = scope.ServiceProvider.GetRequiredService();
+
+ var user = new UserAccount
+ {
+ Id = Guid.CreateVersion7(),
+ Issuer = fixture.IdentityProvider.Authority,
+ Subject = subject,
+ Status = UserStatus.Active,
+ CreatedAtUtc = Now,
+ UpdatedAtUtc = Now,
+ };
+
+ var vault = new Vault
+ {
+ Id = Guid.CreateVersion7(),
+ Name = "Personal",
+ OwnerKind = VaultOwnerKind.Personal,
+ OwnerUserId = user.Id,
+ KeyGeneration = 1,
+ CreatedAtUtc = Now,
+ UpdatedAtUtc = Now,
+ };
+
+ database.Users.Add(user);
+ database.Vaults.Add(vault);
+ await database.SaveChangesAsync();
+
+ return (subject, vault.Id);
+ }
+
+ private async Task SeedTeamVaultAsync()
+ {
+ await using var scope = fixture.CreateScope();
+ var database = scope.ServiceProvider.GetRequiredService();
+
+ var owner = new UserAccount
+ {
+ Id = Guid.CreateVersion7(),
+ Issuer = fixture.IdentityProvider.Authority,
+ Subject = NewSubject(),
+ Status = UserStatus.Active,
+ CreatedAtUtc = Now,
+ UpdatedAtUtc = Now,
+ };
+
+ var team = new Team
+ {
+ Id = Guid.CreateVersion7(),
+ Name = "Team",
+ Slug = $"team-{Guid.CreateVersion7():N}",
+ CreatedByUserId = owner.Id,
+ CreatedAtUtc = Now,
+ };
+
+ var vault = new Vault
+ {
+ Id = Guid.CreateVersion7(),
+ Name = "Shared",
+ OwnerKind = VaultOwnerKind.Team,
+ TeamId = team.Id,
+ KeyGeneration = 1,
+ CreatedAtUtc = Now,
+ UpdatedAtUtc = Now,
+ };
+
+ database.Users.Add(owner);
+ database.Teams.Add(team);
+ database.Vaults.Add(vault);
+ await database.SaveChangesAsync();
+
+ return vault.Id;
+ }
+
+ /// Minimal ProblemDetails shape, for asserting on the code extension.
+ private sealed record JsonProblem(string? Type, string? Detail, string? Code);
+}
diff --git a/tests/DodoSSH.Api.Tests/packages.lock.json b/tests/DodoSSH.Api.Tests/packages.lock.json
new file mode 100644
index 0000000..ed006cb
--- /dev/null
+++ b/tests/DodoSSH.Api.Tests/packages.lock.json
@@ -0,0 +1,1809 @@
+{
+ "version": 2,
+ "dependencies": {
+ "net10.0": {
+ "Meziantou.Analyzer": {
+ "type": "Direct",
+ "requested": "[3.0.134, )",
+ "resolved": "3.0.134",
+ "contentHash": "tTYCcYKyOko3TMNxmxmA9nakbcHVUgglENmCMIhzIjl9y9FBZO/0tWSxTGC74Sp198FmWih5S5KkjQRBg5ePkQ=="
+ },
+ "Microsoft.AspNetCore.Mvc.Testing": {
+ "type": "Direct",
+ "requested": "[10.0.10, )",
+ "resolved": "10.0.10",
+ "contentHash": "pTWE4RtRbb9sl/U9QjZA5oapEZ01ZEMfRilZvvh55ZW97caTQ2XuAF6sgc+7ojKWBbR2qrcWVo5P80gMPuW/tQ==",
+ "dependencies": {
+ "Microsoft.AspNetCore.TestHost": "10.0.10",
+ "Microsoft.Extensions.DependencyModel": "10.0.10",
+ "Microsoft.Extensions.Hosting": "10.0.10"
+ }
+ },
+ "Microsoft.CodeAnalysis.BannedApiAnalyzers": {
+ "type": "Direct",
+ "requested": "[5.6.0, )",
+ "resolved": "5.6.0",
+ "contentHash": "Kcobt3pnOdO0A+6CKiMHZdTEluJpsfxiV20axtZdmfBQnDmiWTKPJADlgAfdTuKNAnVarrkJa0UEGwuOo91muw=="
+ },
+ "NSubstitute": {
+ "type": "Direct",
+ "requested": "[6.0.0, )",
+ "resolved": "6.0.0",
+ "contentHash": "0gvKMbiJ+/WrfbcfBfqRZZrvfLJcd3rqkqVMjjlY5dtmLRVzMY+o/K/rJUStofQ2haSr9Vd04YDfvZtVVGS3/A==",
+ "dependencies": {
+ "Castle.Core": "5.1.1"
+ }
+ },
+ "Shouldly": {
+ "type": "Direct",
+ "requested": "[4.3.0, )",
+ "resolved": "4.3.0",
+ "contentHash": "sDetrWXrl6YXZ4HeLsdBoNk3uIa7K+V4uvIJ+cqdRa5DrFxeTED7VkjoxCuU1kJWpUuBDZz2QXFzSxBtVXLwRQ==",
+ "dependencies": {
+ "DiffEngine": "11.3.0",
+ "EmptyFiles": "4.4.0"
+ }
+ },
+ "Testcontainers.PostgreSql": {
+ "type": "Direct",
+ "requested": "[4.13.0, )",
+ "resolved": "4.13.0",
+ "contentHash": "2ow4AE8drI9iA9Fr4ycAPusXPB1lJfQyyNONSMLE/XqLpm8VuNAh3pK38fOjkOWtTrnD03s4hAIdl4036Ik69A==",
+ "dependencies": {
+ "Testcontainers": "4.13.0"
+ }
+ },
+ "WireMock.Net": {
+ "type": "Direct",
+ "requested": "[2.13.0, )",
+ "resolved": "2.13.0",
+ "contentHash": "msedNpcc2vBHSpmdpRmKSxJUsMIIA/MgeJw1OfiOnHEbpxiXDKWNe/LSOGfyMtzzoeWmbQ4XDu1FpwyBG+8JMQ==",
+ "dependencies": {
+ "WireMock.Net.GraphQL": "2.13.0",
+ "WireMock.Net.Matchers.SystemTextJsonPath": "2.13.0",
+ "WireMock.Net.MimePart": "2.13.0",
+ "WireMock.Net.Minimal": "2.13.0",
+ "WireMock.Net.OpenTelemetry": "2.13.0",
+ "WireMock.Net.ProtoBuf": "2.13.0"
+ }
+ },
+ "xunit.v3": {
+ "type": "Direct",
+ "requested": "[3.2.2, )",
+ "resolved": "3.2.2",
+ "contentHash": "L+4/4y0Uqcg8/d6hfnxhnwh4j9FaeULvefTwrk30rr1o4n/vdPfyUQ8k0yzH8VJx7bmFEkDdcRfbtbjEHlaYcA==",
+ "dependencies": {
+ "xunit.v3.mtp-v1": "[3.2.2]"
+ }
+ },
+ "AnyOf": {
+ "type": "Transitive",
+ "resolved": "0.5.0.1",
+ "contentHash": "WDQw5Qos3mhCumSCgKD70TM1dmqBAuJFGv1cFtNTwTaDLZR7kGy33M5C+L0vZV/bNRNwyi5ABvRGPWHL17rkNw=="
+ },
+ "Castle.Core": {
+ "type": "Transitive",
+ "resolved": "5.1.1",
+ "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==",
+ "dependencies": {
+ "System.Diagnostics.EventLog": "6.0.0"
+ }
+ },
+ "DiffEngine": {
+ "type": "Transitive",
+ "resolved": "11.3.0",
+ "contentHash": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==",
+ "dependencies": {
+ "EmptyFiles": "4.4.0",
+ "System.Management": "6.0.1"
+ }
+ },
+ "Docker.DotNet.Enhanced": {
+ "type": "Transitive",
+ "resolved": "4.3.3",
+ "contentHash": "nGicLwvd42FhRk+khY5uS6cx49ErNdwYKnYBg0F4m4BDKLp/R77AVmmN9xAiqI3W/wN5ZCHkdUhgxf5ORkZuFQ==",
+ "dependencies": {
+ "Docker.DotNet.Enhanced.Handler.Abstractions": "4.3.3",
+ "Docker.DotNet.Enhanced.LegacyHttp": "4.3.3",
+ "Docker.DotNet.Enhanced.NPipe": "4.3.3",
+ "Docker.DotNet.Enhanced.NativeHttp": "4.3.3",
+ "Docker.DotNet.Enhanced.Unix": "4.3.3",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
+ }
+ },
+ "Docker.DotNet.Enhanced.Handler.Abstractions": {
+ "type": "Transitive",
+ "resolved": "4.3.3",
+ "contentHash": "9Cp8hOgtynixcDoAs9lnEaQosluojSYmiW3fsLsLIVfZjlq/fznSIZNUhnmyT4Xo1Iyuok/y49WL/25O47u0Pw==",
+ "dependencies": {
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
+ }
+ },
+ "Docker.DotNet.Enhanced.LegacyHttp": {
+ "type": "Transitive",
+ "resolved": "4.3.3",
+ "contentHash": "7j3M16emv9PAQN7VwFn23xLYNj8GJmwPOcogveHkaWnOCqiC+anRaNKQwqIBNApM1AuwZKivehTKTPmmrjUUnw==",
+ "dependencies": {
+ "Docker.DotNet.Enhanced.Handler.Abstractions": "4.3.3"
+ }
+ },
+ "Docker.DotNet.Enhanced.NativeHttp": {
+ "type": "Transitive",
+ "resolved": "4.3.3",
+ "contentHash": "iNzK+jRFeEMobSA7l/h4ARwCKOOefOWtVN5/RB0ft6/6H6IQXvVUuOgGyZAjYLBT7TsyClRYno2B904f3dtBuQ==",
+ "dependencies": {
+ "Docker.DotNet.Enhanced.Handler.Abstractions": "4.3.3"
+ }
+ },
+ "Docker.DotNet.Enhanced.NPipe": {
+ "type": "Transitive",
+ "resolved": "4.3.3",
+ "contentHash": "ZTLYufuEfY0e6qLOgeH9QgXx2KYuoABRVaY5A8rsggyLgYqbDj9rCRfVAhHPCUv83S7pVxDHy+Tvm/BnxjWVpg==",
+ "dependencies": {
+ "Docker.DotNet.Enhanced.Handler.Abstractions": "4.3.3"
+ }
+ },
+ "Docker.DotNet.Enhanced.Unix": {
+ "type": "Transitive",
+ "resolved": "4.3.3",
+ "contentHash": "ypo8qNbmvHw1t9VfpRTMogCw2vht6VjkXzlGYUUeP2H2bf83USURdla1maW1njn2oq2rfLUFOGMfmt+A37QU2w==",
+ "dependencies": {
+ "Docker.DotNet.Enhanced.Handler.Abstractions": "4.3.3"
+ }
+ },
+ "Docker.DotNet.Enhanced.X509": {
+ "type": "Transitive",
+ "resolved": "4.3.3",
+ "contentHash": "oBDibWezEv4hgj3RIQxI3DVcxkNV1MdrD0d/jhjUu+h3DL+qc0wlkQva15kkwMatXmC/hWp1VP0DMoFXe+BmEw==",
+ "dependencies": {
+ "Docker.DotNet.Enhanced.Handler.Abstractions": "4.3.3"
+ }
+ },
+ "EmptyFiles": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw=="
+ },
+ "Fare": {
+ "type": "Transitive",
+ "resolved": "2.2.1",
+ "contentHash": "21XZo/yuXK1k0EUhdLnjgRD4n0HQYmPFchV6uaORcRc65rasZ1vdm2dmJXPBKZiIBztRRYRmmg/B76W721VWkA=="
+ },
+ "GraphQL": {
+ "type": "Transitive",
+ "resolved": "8.5.0",
+ "contentHash": "BZkfH7GVacTZEkyqa4XN9mW12UA/0XYrpEkkrJnNBf0Pqw8CZWQfItLaWgG2C1Ju9YHH1UUG+LmIpo8iMP6pKA==",
+ "dependencies": {
+ "GraphQL-Parser": "9.5.0",
+ "GraphQL.Analyzers": "8.5.0"
+ }
+ },
+ "GraphQL-Parser": {
+ "type": "Transitive",
+ "resolved": "9.5.0",
+ "contentHash": "5XWJGKHdVi8pyD4P0EglmJmlXEGs0HzvGlEBf3+/Ve1jLYBBKIOkKvY0Ej17b9Kn1bbBxkrmghqbmsMbkLL1nQ=="
+ },
+ "GraphQL.Analyzers": {
+ "type": "Transitive",
+ "resolved": "8.5.0",
+ "contentHash": "jwfvZD5agmw9J8iZEe6BUfKAY+/lC7EqDQg+6JRwXaQ6G/MCLy8jyBc2SHF/2JdAtrkygc1bVyCUP5mR2PHzVA=="
+ },
+ "GraphQL.NewtonsoftJson": {
+ "type": "Transitive",
+ "resolved": "8.5.0",
+ "contentHash": "tAeUoUhJih5fdZRCV0ue3G/gsu8YBiyNZkgLVFyk0wTk8vJGLTBDJaP5o5LVo1edVnk0bR+0/PaNXAEJxkVrTw==",
+ "dependencies": {
+ "GraphQL": "[8.5.0, 9.0.0)",
+ "Newtonsoft.Json": "13.0.3"
+ }
+ },
+ "Handlebars.Net": {
+ "type": "Transitive",
+ "resolved": "2.1.6",
+ "contentHash": "WsYWCEXsIM6hEOSOSRHtIYLjC8BnbT5MVmqhNKRqUI7qiv0t8x3nJiBTEv0ZZfvUAMAFnadGIzSsS/U2anVG1Q=="
+ },
+ "Handlebars.Net.Helpers": {
+ "type": "Transitive",
+ "resolved": "2.5.5",
+ "contentHash": "MZ0/Nvy3XdEy/igZD4fJy5HiUKcKUA170Sq+2RmJFsGiWSqlroXzp8TQqJTDCi1aBtETfQOVdBG0YNvuDs9+uQ==",
+ "dependencies": {
+ "Handlebars.Net.Helpers.Core": "2.5.5"
+ }
+ },
+ "Handlebars.Net.Helpers.Core": {
+ "type": "Transitive",
+ "resolved": "2.5.5",
+ "contentHash": "vLTL6UrLUPPiWDCKig8FLhSU+i9J4n/8RfrhadvnvxqziyK0ArxKMT2gLqQ+X/8vJaRcI9zvD5HxA8KjWbq3Dw==",
+ "dependencies": {
+ "Handlebars.Net": "2.1.6",
+ "Stef.Validation": "0.1.1"
+ }
+ },
+ "Handlebars.Net.Helpers.Humanizer": {
+ "type": "Transitive",
+ "resolved": "2.5.5",
+ "contentHash": "A7TmfLtv7x8HiVckXBmKmOAsO5GKxjSOjxymXS70upqzLLH8BjrhFl+QIGFCdVIWQRx3+yNjGcsz/JXNwt9YZg==",
+ "dependencies": {
+ "Handlebars.Net.Helpers": "2.5.5",
+ "Handlebars.Net.Helpers.Core": "2.5.5",
+ "Humanizer": "[2.14.1, 4.0.0)"
+ }
+ },
+ "Handlebars.Net.Helpers.Json": {
+ "type": "Transitive",
+ "resolved": "2.5.5",
+ "contentHash": "iRBo/ik0M8M6ezJt4QzZm5KQptEdeh6bVtnDbieuxh5YPTUsPMFvtoq0gg426PwrahE+5rXoFZmIM11Oy5GwTg==",
+ "dependencies": {
+ "Handlebars.Net.Helpers": "2.5.5",
+ "Handlebars.Net.Helpers.Core": "2.5.5",
+ "Newtonsoft.Json": "13.0.3"
+ }
+ },
+ "Handlebars.Net.Helpers.Random": {
+ "type": "Transitive",
+ "resolved": "2.5.5",
+ "contentHash": "zKcfFDN4QxgEjk4Em9yz/PQu0mBpIgEaqjhacg2Fl6M0oSsF7VBVflae2WRM9MtiVeRTwLkVwcy7TvJ6iqFuVQ==",
+ "dependencies": {
+ "Handlebars.Net.Helpers": "2.5.5",
+ "Handlebars.Net.Helpers.Core": "2.5.5",
+ "RandomDataGenerator.Net": "1.0.19"
+ }
+ },
+ "Handlebars.Net.Helpers.Xeger": {
+ "type": "Transitive",
+ "resolved": "2.5.5",
+ "contentHash": "J+w9KalIuYlTKMeIv8eoisdoMEz44elri0UOLtfTAuDbADwnBBsJGp4kAQI107+hBcqery9OCRCXm8fvH4eCxQ==",
+ "dependencies": {
+ "Fare": "2.2.1",
+ "Handlebars.Net.Helpers": "2.5.5",
+ "Handlebars.Net.Helpers.Core": "2.5.5"
+ }
+ },
+ "Handlebars.Net.Helpers.XPath": {
+ "type": "Transitive",
+ "resolved": "2.5.5",
+ "contentHash": "uUGzjR5w5YCv+BdWQ4RpWAho0tUG0zfAKG5v+abXS6+E+fjbfSshOg7LyoWTVcGTWO0PouukhSMUFaumB2K4tg==",
+ "dependencies": {
+ "Handlebars.Net.Helpers": "2.5.5",
+ "Handlebars.Net.Helpers.Core": "2.5.5",
+ "XPath2.Extensions": "1.1.5"
+ }
+ },
+ "Handlebars.Net.Helpers.Xslt": {
+ "type": "Transitive",
+ "resolved": "2.5.5",
+ "contentHash": "bOaX47avO4Uja6jTZcBAgS5KjL/2ZaewCpB0Oy7cVegctPyxiiRx/T44XGSt0133hHry9f5nJVsjFKNLrYq0Pg==",
+ "dependencies": {
+ "Handlebars.Net.Helpers": "2.5.5",
+ "Handlebars.Net.Helpers.Core": "2.5.5"
+ }
+ },
+ "Humanizer": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "/FUTD3cEceAAmJSCPN9+J+VhGwmL/C12jvwlyM1DFXShEMsBzvLzLqSrJ2rb+k/W2znKw7JyflZgZpyE+tI7lA==",
+ "dependencies": {
+ "Humanizer.Core.af": "2.14.1",
+ "Humanizer.Core.ar": "2.14.1",
+ "Humanizer.Core.az": "2.14.1",
+ "Humanizer.Core.bg": "2.14.1",
+ "Humanizer.Core.bn-BD": "2.14.1",
+ "Humanizer.Core.cs": "2.14.1",
+ "Humanizer.Core.da": "2.14.1",
+ "Humanizer.Core.de": "2.14.1",
+ "Humanizer.Core.el": "2.14.1",
+ "Humanizer.Core.es": "2.14.1",
+ "Humanizer.Core.fa": "2.14.1",
+ "Humanizer.Core.fi-FI": "2.14.1",
+ "Humanizer.Core.fr": "2.14.1",
+ "Humanizer.Core.fr-BE": "2.14.1",
+ "Humanizer.Core.he": "2.14.1",
+ "Humanizer.Core.hr": "2.14.1",
+ "Humanizer.Core.hu": "2.14.1",
+ "Humanizer.Core.hy": "2.14.1",
+ "Humanizer.Core.id": "2.14.1",
+ "Humanizer.Core.is": "2.14.1",
+ "Humanizer.Core.it": "2.14.1",
+ "Humanizer.Core.ja": "2.14.1",
+ "Humanizer.Core.ko-KR": "2.14.1",
+ "Humanizer.Core.ku": "2.14.1",
+ "Humanizer.Core.lv": "2.14.1",
+ "Humanizer.Core.ms-MY": "2.14.1",
+ "Humanizer.Core.mt": "2.14.1",
+ "Humanizer.Core.nb": "2.14.1",
+ "Humanizer.Core.nb-NO": "2.14.1",
+ "Humanizer.Core.nl": "2.14.1",
+ "Humanizer.Core.pl": "2.14.1",
+ "Humanizer.Core.pt": "2.14.1",
+ "Humanizer.Core.ro": "2.14.1",
+ "Humanizer.Core.ru": "2.14.1",
+ "Humanizer.Core.sk": "2.14.1",
+ "Humanizer.Core.sl": "2.14.1",
+ "Humanizer.Core.sr": "2.14.1",
+ "Humanizer.Core.sr-Latn": "2.14.1",
+ "Humanizer.Core.sv": "2.14.1",
+ "Humanizer.Core.th-TH": "2.14.1",
+ "Humanizer.Core.tr": "2.14.1",
+ "Humanizer.Core.uk": "2.14.1",
+ "Humanizer.Core.uz-Cyrl-UZ": "2.14.1",
+ "Humanizer.Core.uz-Latn-UZ": "2.14.1",
+ "Humanizer.Core.vi": "2.14.1",
+ "Humanizer.Core.zh-CN": "2.14.1",
+ "Humanizer.Core.zh-Hans": "2.14.1",
+ "Humanizer.Core.zh-Hant": "2.14.1"
+ }
+ },
+ "Humanizer.Core": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
+ },
+ "Humanizer.Core.af": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "BoQHyu5le+xxKOw+/AUM7CLXneM/Bh3++0qh1u0+D95n6f9eGt9kNc8LcAHLIOwId7Sd5hiAaaav0Nimj3peNw==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.ar": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "3d1V10LDtmqg5bZjWkA/EkmGFeSfNBcyCH+TiHcHP+HGQQmRq3eBaLcLnOJbVQVn3Z6Ak8GOte4RX4kVCxQlFA==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.az": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "8Z/tp9PdHr/K2Stve2Qs/7uqWPWLUK9D8sOZDNzyv42e20bSoJkHFn7SFoxhmaoVLJwku2jp6P7HuwrfkrP18Q==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.bg": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "S+hIEHicrOcbV2TBtyoPp1AVIGsBzlarOGThhQYCnP6QzEYo/5imtok6LMmhZeTnBFoKhM8yJqRfvJ5yqVQKSQ==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.bn-BD": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "U3bfj90tnUDRKlL1ZFlzhCHoVgpTcqUlTQxjvGCaFKb+734TTu3nkHUWVZltA1E/swTvimo/aXLtkxnLFrc0EQ==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.cs": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "jWrQkiCTy3L2u1T86cFkgijX6k7hoB0pdcFMWYaSZnm6rvG/XJE40tfhYyKhYYgIc1x9P2GO5AC7xXvFnFdqMQ==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.da": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "5o0rJyE/2wWUUphC79rgYDnif/21MKTTx9LIzRVz9cjCIVFrJ2bDyR2gapvI9D6fjoyvD1NAfkN18SHBsO8S9g==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.de": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "9JD/p+rqjb8f5RdZ3aEJqbjMYkbk4VFii2QDnnOdNo6ywEfg/A5YeOQ55CaBJmy7KvV4tOK4+qHJnX/tg3Z54A==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.el": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "Xmv6sTL5mqjOWGGpqY7bvbfK5RngaUHSa8fYDGSLyxY9mGdNbDcasnRnMOvi0SxJS9gAqBCn21Xi90n2SHZbFA==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.es": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "e//OIAeMB7pjBV1HqqI4pM2Bcw3Jwgpyz9G5Fi4c+RJvhqFwztoWxW57PzTnNJE2lbhGGLQZihFZjsbTUsbczA==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.fa": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "nzDOj1x0NgjXMjsQxrET21t1FbdoRYujzbmZoR8u8ou5CBWY1UNca0j6n/PEJR/iUbt4IxstpszRy41wL/BrpA==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.fi-FI": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "Vnxxx4LUhp3AzowYi6lZLAA9Lh8UqkdwRh4IE2qDXiVpbo08rSbokATaEzFS+o+/jCNZBmoyyyph3vgmcSzhhQ==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.fr": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "2p4g0BYNzFS3u9SOIDByp2VClYKO0K1ecDV4BkB9EYdEPWfFODYnF+8CH8LpUrpxL2TuWo2fiFx/4Jcmrnkbpg==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.fr-BE": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "o6R3SerxCRn5Ij8nCihDNMGXlaJ/1AqefteAssgmU2qXYlSAGdhxmnrQAXZUDlE4YWt/XQ6VkNLtH7oMqsSPFQ==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.he": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "FPsAhy7Iw6hb+ZitLgYC26xNcgGAHXb0V823yFAzcyoL5ozM+DCJtYfDPYiOpsJhEZmKFTM9No0jUn1M89WGvg==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.hr": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "chnaD89yOlST142AMkAKLuzRcV5df3yyhDyRU5rypDiqrq2HN8y1UR3h1IicEAEtXLoOEQyjSAkAQ6QuXkn7aw==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.hu": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "hAfnaoF9LTGU/CmFdbnvugN4tIs8ppevVMe3e5bD24+tuKsggMc5hYta9aiydI8JH9JnuVmxvNI4DJee1tK05A==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.hy": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "sVIKxOiSBUb4gStRHo9XwwAg9w7TNvAXbjy176gyTtaTiZkcjr9aCPziUlYAF07oNz6SdwdC2mwJBGgvZ0Sl2g==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.id": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "4Zl3GTvk3a49Ia/WDNQ97eCupjjQRs2iCIZEQdmkiqyaLWttfb+cYXDMGthP42nufUL0SRsvBctN67oSpnXtsg==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.is": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "R67A9j/nNgcWzU7gZy1AJ07ABSLvogRbqOWvfRDn4q6hNdbg/mjGjZBp4qCTPnB2mHQQTCKo3oeCUayBCNIBCw==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.it": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "jYxGeN4XIKHVND02FZ+Woir3CUTyBhLsqxu9iqR/9BISArkMf1Px6i5pRZnvq4fc5Zn1qw71GKKoCaHDJBsLFw==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.ja": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "TM3ablFNoYx4cYJybmRgpDioHpiKSD7q0QtMrmpsqwtiiEsdW5zz/q4PolwAczFnvrKpN6nBXdjnPPKVet93ng==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.ko-KR": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "CtvwvK941k/U0r8PGdEuBEMdW6jv/rBiA9tUhakC7Zd2rA/HCnDcbr1DiNZ+/tRshnhzxy/qwmpY8h4qcAYCtQ==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.ku": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "vHmzXcVMe+LNrF9txpdHzpG7XJX65SiN9GQd/Zkt6gsGIIEeECHrkwCN5Jnlkddw2M/b0HS4SNxdR1GrSn7uCA==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.lv": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "E1/KUVnYBS1bdOTMNDD7LV/jdoZv/fbWTLPtvwdMtSdqLyRTllv6PGM9xVQoFDYlpvVGtEl/09glCojPHw8ffA==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.ms-MY": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "vX8oq9HnYmAF7bek4aGgGFJficHDRTLgp/EOiPv9mBZq0i4SA96qVMYSjJ2YTaxs7Eljqit7pfpE2nmBhY5Fnw==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.mt": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "pEgTBzUI9hzemF7xrIZigl44LidTUhNu4x/P6M9sAwZjkUF0mMkbpxKkaasOql7lLafKrnszs0xFfaxQyzeuZQ==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.nb": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "mbs3m6JJq53ssLqVPxNfqSdTxAcZN3njlG8yhJVx83XVedpTe1ECK9aCa8FKVOXv93Gl+yRHF82Hw9T9LWv2hw==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.nb-NO": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "AsJxrrVYmIMbKDGe8W6Z6//wKv9dhWH7RsTcEHSr4tQt/80pcNvLi0hgD3fqfTtg0tWKtgch2cLf4prorEV+5A==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.nl": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "24b0OUdzJxfoqiHPCtYnR5Y4l/s4Oh7KW7uDp+qX25NMAHLCGog2eRfA7p2kRJp8LvnynwwQxm2p534V9m55wQ==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.pl": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "17mJNYaBssENVZyQHduiq+bvdXS0nhZJGEXtPKoMhKv3GD//WO0mEfd9wjEBsWCSmWI7bjRqhCidxzN+YtJmsg==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.pt": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "8HB8qavcVp2la1GJX6t+G9nDYtylPKzyhxr9LAooIei9MnQvNsjEiIE4QvHoeDZ4weuQ9CsPg1c211XUMVEZ4A==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.ro": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "psXNOcA6R8fSHoQYhpBTtTTYiOk8OBoN3PKCEDgsJKIyeY5xuK81IBdGi77qGZMu/OwBRQjQCBMtPJb0f4O1+A==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.ru": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "zm245xUWrajSN2t9H7BTf84/2APbUkKlUJpcdgsvTdAysr1ag9fi1APu6JEok39RRBXDfNRVZHawQ/U8X0pSvQ==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.sk": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "Ncw24Vf3ioRnbU4MsMFHafkyYi8JOnTqvK741GftlQvAbULBoTz2+e7JByOaasqeSi0KfTXeegJO+5Wk1c0Mbw==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.sl": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "l8sUy4ciAIbVThWNL0atzTS2HWtv8qJrsGWNlqrEKmPwA4SdKolSqnTes9V89fyZTc2Q43jK8fgzVE2C7t009A==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.sr": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "rnNvhpkOrWEymy7R/MiFv7uef8YO5HuXDyvojZ7JpijHWA5dXuVXooCOiA/3E93fYa3pxDuG2OQe4M/olXbQ7w==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.sr-Latn": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "nuy/ykpk974F8ItoQMS00kJPr2dFNjOSjgzCwfysbu7+gjqHmbLcYs7G4kshLwdA4AsVncxp99LYeJgoh1JF5g==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.sv": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "E53+tpAG0RCp+cSSI7TfBPC+NnsEqUuoSV0sU+rWRXWr9MbRWx1+Zj02XMojqjGzHjjOrBFBBio6m74seFl0AA==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.th-TH": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "eSevlJtvs1r4vQarNPfZ2kKDp/xMhuD00tVVzRXkSh1IAZbBJI/x2ydxUOwfK9bEwEp+YjvL1Djx2+kw7ziu7g==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.tr": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "rQ8N+o7yFcFqdbtu1mmbrXFi8TQ+uy+fVH9OPI0CI3Cu1om5hUU/GOMC3hXsTCI6d79y4XX+0HbnD7FT5khegA==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.uk": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "2uEfujwXKNm6bdpukaLtEJD+04uUtQD65nSGCetA1fYNizItEaIBUboNfr3GzJxSMQotNwGVM3+nSn8jTd0VSg==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.uz-Cyrl-UZ": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "TD3ME2sprAvFqk9tkWrvSKx5XxEMlAn1sjk+cYClSWZlIMhQQ2Bp/w0VjX1Kc5oeKjxRAnR7vFcLUFLiZIDk9Q==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.uz-Latn-UZ": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "/kHAoF4g0GahnugZiEMpaHlxb+W6jCEbWIdsq9/I1k48ULOsl/J0pxZj93lXC3omGzVF1BTVIeAtv5fW06Phsg==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.vi": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "rsQNh9rmHMBtnsUUlJbShMsIMGflZtPmrMM6JNDw20nhsvqfrdcoDD8cMnLAbuSovtc3dP+swRmLQzKmXDTVPA==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.zh-CN": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "uH2dWhrgugkCjDmduLdAFO9w1Mo0q07EuvM0QiIZCVm6FMCu/lGv2fpMu4GX+4HLZ6h5T2Pg9FIdDLCPN2a67w==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.zh-Hans": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "WH6IhJ8V1UBG7rZXQk3dZUoP2gsi8a0WkL8xL0sN6WGiv695s8nVcmab9tWz20ySQbuzp0UkSxUQFi5jJHIpOQ==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "Humanizer.Core.zh-Hant": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "VIXB7HCUC34OoaGnO3HJVtSv2/wljPhjV7eKH4+TFPgQdJj2lvHNKY41Dtg0Bphu7X5UaXFR4zrYYyo+GNOjbA==",
+ "dependencies": {
+ "Humanizer.Core": "[2.14.1]"
+ }
+ },
+ "JmesPath.Net": {
+ "type": "Transitive",
+ "resolved": "1.1.0",
+ "contentHash": "sL1LeqBm+BWSKvgZN/T470IqkXcKQXmOYsRUZU18jDZeiIBmvUfIe9m3VhiII/jOK/6WmrQ+W8Pqwz3k28WX9g==",
+ "dependencies": {
+ "JmesPath.Net.Parser": "1.1.0",
+ "Newtonsoft.Json": "13.0.4"
+ }
+ },
+ "JmesPath.Net.Parser": {
+ "type": "Transitive",
+ "resolved": "1.1.0",
+ "contentHash": "NLTE/dPy8lMcZO6E7SL5Jw3fay8Vesll7+hkeRVSRaVNg1RRyPBV3/u6CM7QNgtnzhvyFPDyxUHUuRdh0vzCSg=="
+ },
+ "Json.More.Net": {
+ "type": "Transitive",
+ "resolved": "3.0.1",
+ "contentHash": "fRctF2J2SILYG6wqP21drmeEODmCVkVQ/b3MndDu2fT1swfySyUgq7ePCk+aENGlDcIm05fyfjh9vcuqDEfv3w=="
+ },
+ "JsonConverter.Abstractions": {
+ "type": "Transitive",
+ "resolved": "0.13.0",
+ "contentHash": "Ci3nuKx3GgMDfW9JA4dJpU+hJV5G1ve72mploQP8ivSDpOmo2QbfVAQkxsVzb3UQJCgwxxM6rdE/fPXwM0yj0g=="
+ },
+ "JsonConverter.Newtonsoft.Json": {
+ "type": "Transitive",
+ "resolved": "0.13.0",
+ "contentHash": "K6doeW12emLiJV4laUf58y3kkjng6/IARRtC7+20qIOBrP1pBxGRsjz2IfxCgSBkTML3q6FWe7O+/UE374lsaA==",
+ "dependencies": {
+ "JsonConverter.Abstractions": "0.13.0",
+ "Newtonsoft.Json": "13.0.4",
+ "Stef.Validation": "0.1.1"
+ }
+ },
+ "JsonConverter.System.Text.Json": {
+ "type": "Transitive",
+ "resolved": "0.13.0",
+ "contentHash": "UtRbkZT16Z0OVZ9n/h60E0GlUDTul/DjpuuajdsCNvefsmTxf6WNB8Cq9Hjwu9pGjfqOaFOmaz8k54DaHBmc0g==",
+ "dependencies": {
+ "JsonConverter.Abstractions": "0.13.0",
+ "Stef.Validation": "0.1.1"
+ }
+ },
+ "JsonPath.Net": {
+ "type": "Transitive",
+ "resolved": "3.0.2",
+ "contentHash": "Cmt2mvPYOLljjqSfM1xUYZYTPf8MPbwv2XpCpPxxq9u23/CGrz/fljgd1fJUNujd3+E1adNOyF1TLwppbyQwxg==",
+ "dependencies": {
+ "Json.More.Net": "3.0.1"
+ }
+ },
+ "MetadataReferenceService.Abstractions": {
+ "type": "Transitive",
+ "resolved": "0.0.1",
+ "contentHash": "Sf5ip58vlqWkQIAULIOKFIIFuhtRd8lChsJRZdFo746NVApEp/qgxNf/zCLjbB/RA/8TQGXWrFPKpqjyeh3EMg==",
+ "dependencies": {
+ "Microsoft.CodeAnalysis.CSharp": "4.8.0",
+ "Stef.Validation": "0.1.1"
+ }
+ },
+ "MetadataReferenceService.Default": {
+ "type": "Transitive",
+ "resolved": "0.0.1",
+ "contentHash": "ihrchqYobpQMA9tn0W+MGD3oe5onqCttbR3lQfEiVzwF0V9/DS+K4YtvsUPGDC9XIie2Xw3lugSSk97k+OUwnQ==",
+ "dependencies": {
+ "MetadataReferenceService.Abstractions": "0.0.1"
+ }
+ },
+ "Microsoft.ApplicationInsights": {
+ "type": "Transitive",
+ "resolved": "2.23.0",
+ "contentHash": "nWArUZTdU7iqZLycLKWe0TDms48KKGE6pONH2terYNa8REXiqixrMOkf1sk5DHGMaUTqONU2YkS4SAXBhLStgw=="
+ },
+ "Microsoft.AspNetCore.Http": {
+ "type": "Transitive",
+ "resolved": "2.3.9",
+ "contentHash": "+CcfWi1LoKYbcxt+3toO4xbBG+qSSMbPuuow+cbZKIrITXuu1geN1traamL4jG8QaHdHGm3M0eCh+EOgdMgNPA==",
+ "dependencies": {
+ "Microsoft.AspNetCore.Http.Abstractions": "2.3.0",
+ "Microsoft.AspNetCore.WebUtilities": "2.3.0",
+ "Microsoft.Extensions.ObjectPool": "8.0.11",
+ "Microsoft.Extensions.Options": "8.0.2",
+ "Microsoft.Net.Http.Headers": "2.3.8"
+ }
+ },
+ "Microsoft.AspNetCore.Http.Abstractions": {
+ "type": "Transitive",
+ "resolved": "2.3.0",
+ "contentHash": "39r9PPrjA6s0blyFv5qarckjNkaHRA5B+3b53ybuGGNTXEj1/DStQJ4NWjFL6QTRQpL9zt7nDyKxZdJOlcnq+Q==",
+ "dependencies": {
+ "Microsoft.AspNetCore.Http.Features": "2.3.0"
+ }
+ },
+ "Microsoft.AspNetCore.Http.Features": {
+ "type": "Transitive",
+ "resolved": "2.3.0",
+ "contentHash": "f10WUgcsKqrkmnz6gt8HeZ7kyKjYN30PO7cSic1lPtH7paPtnQqXPOveul/SIPI43PhRD4trttg4ywnrEmmJpA==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.AspNetCore.TestHost": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "Kks+OpQlP/eWQhnTjkiv0H9kc9Uqa7ieAzNV62yJpZ8Ips/WwpfpwrwZUKzV83CBJaBl5OI7J2XxVVTC8vah/Q=="
+ },
+ "Microsoft.AspNetCore.WebUtilities": {
+ "type": "Transitive",
+ "resolved": "2.3.0",
+ "contentHash": "trbXdWzoAEUVd0PE2yTopkz4kjZaAIA7xUWekd5uBw+7xE8Do/YOVTeb9d9koPTlbtZT539aESJjSLSqD8eYrQ==",
+ "dependencies": {
+ "Microsoft.Net.Http.Headers": "2.3.0"
+ }
+ },
+ "Microsoft.Bcl.AsyncInterfaces": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg=="
+ },
+ "Microsoft.Bcl.Cryptography": {
+ "type": "Transitive",
+ "resolved": "10.0.2",
+ "contentHash": "LG9Yll3B5aNpxv0+D47g6LiOiKBIlodhcHdQwcYzo8VeexFLGqx5ymetmA2aBRyo9cCcWsQWrFsdbsr8LvmWDw=="
+ },
+ "Microsoft.CodeAnalysis.Analyzers": {
+ "type": "Transitive",
+ "resolved": "3.3.4",
+ "contentHash": "AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g=="
+ },
+ "Microsoft.CodeAnalysis.Common": {
+ "type": "Transitive",
+ "resolved": "4.8.0",
+ "contentHash": "/jR+e/9aT+BApoQJABlVCKnnggGQbvGh7BKq2/wI1LamxC+LbzhcLj4Vj7gXCofl1n4E521YfF9w0WcASGg/KA==",
+ "dependencies": {
+ "Microsoft.CodeAnalysis.Analyzers": "3.3.4"
+ }
+ },
+ "Microsoft.CodeAnalysis.CSharp": {
+ "type": "Transitive",
+ "resolved": "4.8.0",
+ "contentHash": "+3+qfdb/aaGD8PZRCrsdobbzGs1m9u119SkkJt8e/mk3xLJz/udLtS2T6nY27OTXxBBw10HzAbC8Z9w08VyP/g==",
+ "dependencies": {
+ "Microsoft.CodeAnalysis.Common": "[4.8.0]"
+ }
+ },
+ "Microsoft.EntityFrameworkCore.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "bOzrFCl6uZCjaSh2bG1ToRQRdx+iXvxosCg9hFyG9OWeAzOFI4xev9OqKeWfKf/kAHyox2JnbcvLVf2ceA7sqA=="
+ },
+ "Microsoft.EntityFrameworkCore.Analyzers": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "2gLDordUCGf3aNOOuqtTbP5mxhiP9nk6TnvGiE3RnqT891O+Zf/qKu1PIREubs1M16A0SImr4vULBfU5BTDs1Q=="
+ },
+ "Microsoft.Extensions.Caching.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "4ZFBNE+jzR+CrWWlhOesnmywCW7pYKT0dxyAQRdL11yJwxe4jvcAu31eorFtEkoFeCDcUTeNssgPv2yaRRptaQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Caching.Memory": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "N1w5H7uK6gCTnCBZAWzE0/EQYSPysij/uYwDqntqBVvBa6bjMmBKitsnEFd6yh/SX3wLm67nO6+OnZ84K+gZWg==",
+ "dependencies": {
+ "Microsoft.Extensions.Caching.Abstractions": "10.0.10",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Options": "10.0.10",
+ "Microsoft.Extensions.Primitives": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Configuration": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "plJWK2zpWuuyxI8F8s2scx6Je7N1Ajjs6HvYUGKwRnDMWIVIz9FHwAkiT7ASgrvAOd10T0FPVlh9BzAJJME+jg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Primitives": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "5Vnd2I75DmZCVEjSynIdJ/0EGafgnLQwgR3t2C2/fkjx/nRG+cLwxLLdInoHeCEpkD5K4Ov/g9ZCRYrl4TRsaA==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Binder": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "GqmN2o1CkJvk7uWp+p4CwBYW0w/zfoEbvsiFDbO2G8l1Uz+mrDAbAcZiXhU2lufKPby1cjAUdd5GTWpebYOkOA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.10",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Configuration.CommandLine": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "33cBeR2HRbzHUTtmcmLdNOApneNGcymwwL4arHuotgVK9Frba8kcDTrvVTj7cSCmF1R9OiSbZH0KxNOwab3HUg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.10",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Configuration.EnvironmentVariables": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "KRfFSSCV58vEdU7mPED/YMzeovIWF5P0g8s9K8n9HEfy0/WzMq37SrPdXdFN5/dFT/rPMHpF7AvpoXHckbcBFg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.10",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Configuration.FileExtensions": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "ZOhZYwvbXGTgGVRwswIirofEMVHuWdxjdh0JeUZXwaF9cgcjXdz/t0ELtgaevw7ezTyv47yPNCgGreWtLkn3IQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.10",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.10",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.10",
+ "Microsoft.Extensions.FileProviders.Physical": "10.0.10",
+ "Microsoft.Extensions.Primitives": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Json": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "uvJ6sHwjgrkMEJOgiC76G0mcZGXerwyyWkwX34EOjCbxKG6TCtfAoqDKAMsCvEBf9HxjlGQEgqsSMOGCmGBf+A==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.10",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Configuration.FileExtensions": "10.0.10",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Configuration.UserSecrets": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "1s1sKFTk/Foam64JY6+m/diH8drL3Wx6V3gtSd5v1IEZtszZYyc1pW8uRnMblzpNiR0l0t8gGk7tXj3xHzFgdg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Configuration.Json": "10.0.10",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.10",
+ "Microsoft.Extensions.FileProviders.Physical": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "ANyvsgkNBRvcJh2XLgn8veGmajf+8m0AbKK+HPWdRL1yraSNVVSmQhFntLtdz/C795jxqqup+k05cs/3jZQPOA==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "z/2xXlFw2aLGjHyEm6E0tQ+In6VfzQzTrtArbQ2c0TQE16ZbyDCMGPvaUT9I0s8rgy9sRWlU2P9waW37qV04qA=="
+ },
+ "Microsoft.Extensions.DependencyModel": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "rfZA1RjR021RPqSmIPovfz2aOd79TGqJ9BengbjnzIISOVwjLmuSDnhCMmiY/1c6iYvGolQ1iNGzkav0u11XEA=="
+ },
+ "Microsoft.Extensions.Diagnostics": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "Kr/e7lUf4+N8tacbqJ2Ctwe/HarKdAc9ZkgKVVqvtJDBKbez+T/KnUwu82KSlnBp/SrpBcxc7u7xkE2oUZT/5Q==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.10",
+ "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Diagnostics.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "9uWiKpeOVac355STyChWR/pliFX/5CeLqChW9kKsaxyDH4EUTZxMkT4Jwp/J/peLm0GBFmSX5c0WCse3yCnq1Q==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Options": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.FileProviders.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "c5zqFCY9DiIpMovLd7/d/CTiEtrMOuQ639dhv3PABtKQIKNQikSHwQt8+N679uii9q+B55lgK28Uv64FOwEu8w==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.FileProviders.Physical": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "jhJAyo38kSrH3ARvWUk0h8itogVnQu2DCZuPo+s0Z+tXes0ugTxMPaHYzap85785eHQmPFqD9TYERqBbtGxn/w==",
+ "dependencies": {
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.10",
+ "Microsoft.Extensions.FileSystemGlobbing": "10.0.10",
+ "Microsoft.Extensions.Primitives": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.FileSystemGlobbing": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "jSOCVxEwCd4Aq925kJVz1kSO1EpX2OHYKL04qVREXkDU7Ce3pVDdHPYm+fEy8y/th2kJf/DAstRHpJAqoNWP8w=="
+ },
+ "Microsoft.Extensions.Hosting": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "tL9FkfV64GPUDSPvwrgyw42LVzsnVAnyrqJEuZVJbODgrQ3eL63zmzEcVWoCHzfgqUhWggzbgAyUCnz/zfI3Pg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.10",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.10",
+ "Microsoft.Extensions.Configuration.CommandLine": "10.0.10",
+ "Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.10",
+ "Microsoft.Extensions.Configuration.FileExtensions": "10.0.10",
+ "Microsoft.Extensions.Configuration.Json": "10.0.10",
+ "Microsoft.Extensions.Configuration.UserSecrets": "10.0.10",
+ "Microsoft.Extensions.DependencyInjection": "10.0.10",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Diagnostics": "10.0.10",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.10",
+ "Microsoft.Extensions.FileProviders.Physical": "10.0.10",
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Logging": "10.0.10",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Logging.Configuration": "10.0.10",
+ "Microsoft.Extensions.Logging.Console": "10.0.10",
+ "Microsoft.Extensions.Logging.Debug": "10.0.10",
+ "Microsoft.Extensions.Logging.EventLog": "10.0.10",
+ "Microsoft.Extensions.Logging.EventSource": "10.0.10",
+ "Microsoft.Extensions.Options": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Hosting.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "5LugpYGHk+mkn0a8IZgcyfBca8PCTAU9RQFoMrTdtOOidq88M2SI5f3px6ugnzgxC+eTkvYYJi8pzlUnG5xdAQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.10",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.10",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Logging": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "Tf6z5HsL0VDYRTfvsoNrTGHGheCwkTsZBA2FFh5ATJUbkAwug+FFNISJK2gjpUNemlAOoWllAK52HOWCjto3EQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection": "10.0.10",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Options": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Logging.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "zkFxGYUvdxAvIKTyXHrmW+Sux53D4SezD9dMyZ6hrwwzPQJNuwCRy1f5W7AvYTqacEGhWF2XderRQG1OvbV8og==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Logging.Configuration": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "cLrqxkuEfcilZ8SjK+9KAnpLk9lOoMPaOokF+wRUYie+iUEcdX4/p/+gJkt0BYgWLthjpBUCkVTBI6Kxg0nsOw==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.10",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.10",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Logging": "10.0.10",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Options": "10.0.10",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Logging.Console": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "VIlNzPwPS0GeQVSmCqqo36ugryX3LpE9ul6gEkks5VLET3weH/XMLeWmclwfoGn4Nxi2mwVibB+OZBVJ9tDqvg==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Logging": "10.0.10",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Logging.Configuration": "10.0.10",
+ "Microsoft.Extensions.Options": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Logging.Debug": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "8+TZBnV5fgBXoVNJ5ROSErUwYogk4hOgV7c2HWK1u5cqKGmiUTUn7+KqZ35iQu8e/B7Ykccyz5OTjdXcidNZ9g==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Logging": "10.0.10",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Logging.EventLog": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "0RE4951AzQ+YD4gVrvbq0BhdsiBgSDo44yM7+QBZ2mrmMJeNjY+teCIYfUjqDPVYnKs0HR6SkkhgrX1YgXZq3Q==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Logging": "10.0.10",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Options": "10.0.10",
+ "System.Diagnostics.EventLog": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Logging.EventSource": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "85SAPwXhJtdBInzN2k7SChiFiBGh3KOWay5AfoY+GREF6P7oZA98+ST2p7Z9384iLKYjkZSKIZ/FqIO5aojtNw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Logging": "10.0.10",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Options": "10.0.10",
+ "Microsoft.Extensions.Primitives": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.ObjectPool": {
+ "type": "Transitive",
+ "resolved": "8.0.11",
+ "contentHash": "6ApKcHNJigXBfZa6XlDQ8feJpq7SG1ogZXg6M4FiNzgd6irs3LUAzo0Pfn4F2ZI9liGnH1XIBR/OtSbZmJAV5w=="
+ },
+ "Microsoft.Extensions.Options": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "srnhnk7nE8krBiIXp71LvBmKBtraBONWSRzdjJgRv1Ko9Mp8IVNqv4vIS9hGeVteBig8aQkva9ZG+sC+o5sVcA==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Primitives": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Options.ConfigurationExtensions": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "tnBmu/LwF25ZQK+HBNCu2xrwnkKoB/XEbJyooGGoYxHrhvxbSKi7eOFiJ4AXBy/QU4vtCvCJfoi8k9Ej72qzOQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.10",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Options": "10.0.10",
+ "Microsoft.Extensions.Primitives": "10.0.10"
+ }
+ },
+ "Microsoft.Extensions.Primitives": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "5wu/GrYVd8mG2DVUw3vFJzF+O336TyTGg/Kmcgw9bfwYhCoFiV5lR5QeEmKecJyrW4W54nMfD3p3589E8a7czQ=="
+ },
+ "Microsoft.IdentityModel.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.19.2",
+ "contentHash": "HJbo/lnSfNHUfphPRT910poQc4T2/9+8svFLvzuaYHGAOJ2Tu+oEDqpX0BVP3BJ4OuUM1kylEKyaiX2fCAK3Cw=="
+ },
+ "Microsoft.IdentityModel.JsonWebTokens": {
+ "type": "Transitive",
+ "resolved": "8.19.2",
+ "contentHash": "ui3fuBT4fs8kdKfBthI4NzLYIBIneEVS8UrL1JVBzAn80UiKmngBBi0BEByE7n/9c+EElcfFlCMFFTqpkBSLNA==",
+ "dependencies": {
+ "Microsoft.IdentityModel.Tokens": "8.19.2"
+ }
+ },
+ "Microsoft.IdentityModel.Logging": {
+ "type": "Transitive",
+ "resolved": "8.19.2",
+ "contentHash": "r5YLDIxGOnkVJHrqXv/iD1FM1CgGrQOdriXuvuWvTPmKbnGANhEysq9XKmN6IHjf2a+9bAGEpnoRBsAQlvJU5w==",
+ "dependencies": {
+ "Microsoft.IdentityModel.Abstractions": "8.19.2"
+ }
+ },
+ "Microsoft.IdentityModel.Protocols": {
+ "type": "Transitive",
+ "resolved": "8.19.2",
+ "contentHash": "sGxSsSrZXNmca6D+jHH2rVRyo2nNRd/g4H9CFbPmLLq0xgoH1U0orLWE5minfijw7+zq49tBs7txenbfAErRoQ==",
+ "dependencies": {
+ "Microsoft.IdentityModel.Tokens": "8.19.2"
+ }
+ },
+ "Microsoft.IdentityModel.Protocols.OpenIdConnect": {
+ "type": "Transitive",
+ "resolved": "8.19.2",
+ "contentHash": "1XOcyY36cVymzE3qKdzKaUEZ4Pzt7ZpSa14JZoPPK1NLFUkQDs85TCqpV6XDo0YjFXj6nVK00AfOHppjghjhtw==",
+ "dependencies": {
+ "Microsoft.IdentityModel.Protocols": "8.19.2",
+ "System.IdentityModel.Tokens.Jwt": "8.19.2"
+ }
+ },
+ "Microsoft.IdentityModel.Tokens": {
+ "type": "Transitive",
+ "resolved": "8.19.2",
+ "contentHash": "GtPC1S02uH1gOO4fQ+zRysIicKmEXaYFP8PIkdJYXqMyruYhopre4ozVHp0XiDSA0+GJvOZH9prxCPvgBMg4Ww==",
+ "dependencies": {
+ "Microsoft.Bcl.Cryptography": "10.0.2",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.0",
+ "Microsoft.IdentityModel.Logging": "8.19.2"
+ }
+ },
+ "Microsoft.Net.Http.Headers": {
+ "type": "Transitive",
+ "resolved": "2.3.8",
+ "contentHash": "JO60u/VVUdaZfv4XQ//zgcH54y8rnxdpcvXnsDqWLKB4adDKaCiaozixDfQ/6H+PKYfkNV2CL8b8U+F9mciE3Q==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.Testing.Extensions.Telemetry": {
+ "type": "Transitive",
+ "resolved": "1.9.1",
+ "contentHash": "No5AudZMmSb+uNXjlgL2y3/stHD2IT4uxqc5yHwkE+/nNux9jbKcaJMvcp9SwgP4DVD8L9/P3OUz8mmmcvEIdQ==",
+ "dependencies": {
+ "Microsoft.ApplicationInsights": "2.23.0",
+ "Microsoft.Testing.Platform": "1.9.1"
+ }
+ },
+ "Microsoft.Testing.Extensions.TrxReport.Abstractions": {
+ "type": "Transitive",
+ "resolved": "1.9.1",
+ "contentHash": "AL46Xe1WBi85Ntd4mNPvat5ZSsZ2uejiVqoKCypr8J3wK0elA5xJ3AN4G/Q4GIwzUFnggZoH/DBjnr9J18IO/g==",
+ "dependencies": {
+ "Microsoft.Testing.Platform": "1.9.1"
+ }
+ },
+ "Microsoft.Testing.Platform": {
+ "type": "Transitive",
+ "resolved": "1.9.1",
+ "contentHash": "QafNtNSmEI0zazdebnsIkDKmFtTSpmx/5PLOjURWwozcPb3tvRxzosQSL8xwYNM1iPhhKiBksXZyRSE2COisrA=="
+ },
+ "Microsoft.Testing.Platform.MSBuild": {
+ "type": "Transitive",
+ "resolved": "1.9.1",
+ "contentHash": "oTUtyR4X/s9ytuiNA29FGsNCCH0rNmY5Wdm14NCKLjTM1cT9edVSlA+rGS/mVmusPqcP0l/x9qOnMXg16v87RQ==",
+ "dependencies": {
+ "Microsoft.Testing.Platform": "1.9.1"
+ }
+ },
+ "Microsoft.Win32.Registry": {
+ "type": "Transitive",
+ "resolved": "5.0.0",
+ "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg=="
+ },
+ "Namotion.Reflection": {
+ "type": "Transitive",
+ "resolved": "2.1.2",
+ "contentHash": "7tSHAzX8GWKy0qrW6OgQWD7kAZiqzhq+m1503qczuwuK6ZYhOGCQUxw+F3F4KkRM70aB6RMslsRVSCFeouIehw=="
+ },
+ "Newtonsoft.Json": {
+ "type": "Transitive",
+ "resolved": "13.0.4",
+ "contentHash": "pdgNNMai3zv51W5aq268sujXUyx7SNdE2bj1wZcWjAQrKMFZV260lbqYop1d2GM67JI1huLRwxo9ZqnfF/lC6A=="
+ },
+ "NJsonSchema": {
+ "type": "Transitive",
+ "resolved": "10.9.0",
+ "contentHash": "IBPo6Srxn2MEcIFM3HdM4QImrJbsIeujENQyzHL2Pv6wLsKSYAyAEilecRqaLOhoy3snEiPLx7hhv7opbhOxKQ==",
+ "dependencies": {
+ "Namotion.Reflection": "2.1.2",
+ "Newtonsoft.Json": "9.0.1"
+ }
+ },
+ "NJsonSchema.Extensions": {
+ "type": "Transitive",
+ "resolved": "0.2.0",
+ "contentHash": "zLHUfuCmnaaQbKxqvTALrxhXV6Pbdy4G3ZlAI+7oaXdJmSyQPpMHGcxDBmw0+qHziT7jVImxU1BjcidKJHeprg==",
+ "dependencies": {
+ "NJsonSchema": "10.9.0"
+ }
+ },
+ "Npgsql": {
+ "type": "Transitive",
+ "resolved": "10.0.3",
+ "contentHash": "7nb5YzXuvWWJxB0J8DiyL3we+X4FOctZrt0fIBnucOIaIevFEEwGQVZKtiu9olXdlNAK1eNgqSral6r/jlhI4w==",
+ "dependencies": {
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.0"
+ }
+ },
+ "NSwag.Core": {
+ "type": "Transitive",
+ "resolved": "13.16.1",
+ "contentHash": "xiX+H3Bv6zxrqJExPepO5WQVutkDUMdlUA3NqQ8VguwsYwJlkV05eF8XvmbJn/yGJWUag7vLImuXAoj0/327Bg==",
+ "dependencies": {
+ "NJsonSchema": "10.7.2",
+ "Newtonsoft.Json": "9.0.1"
+ }
+ },
+ "OpenTelemetry": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "N0i6WjPoHPbZyms1ugbDIFAJFuGlpeExJMU/+XSL0lQRUkg/D0utFkDoLXf8Z1km5B+xVZ2GyMXXiX8qdeNmPg==",
+ "dependencies": {
+ "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.0",
+ "Microsoft.Extensions.Logging.Configuration": "10.0.0",
+ "OpenTelemetry.Api.ProviderBuilderExtensions": "1.15.3"
+ }
+ },
+ "OpenTelemetry.Api": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "fX+fkCysfPut+qCcT3bKqyX4QN9Saf4CgX8HLOHywEVD+Xr7sULtfuypITpoDysjx8R59dn/3mWhgimMH8cm/g=="
+ },
+ "OpenTelemetry.Api.ProviderBuilderExtensions": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "SYn0lqYDwLMWhv/zlNGsQcl2yX++yTumanX46bmOZE/ZDOd1WjPBO2kZaZgKLEZTZk48pavIFGJ6vOvxXgWVFQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0",
+ "OpenTelemetry.Api": "1.15.3"
+ }
+ },
+ "OpenTelemetry.Exporter.OpenTelemetryProtocol": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "FEXJepcseTGbATiCkUfP7ipoFEYYfl/0UmmUwi0KxCPg9PaUA8ab2P1LGopK+/HExasJ1ZutFhZrN6WvUIR23g==",
+ "dependencies": {
+ "OpenTelemetry": "1.15.3"
+ }
+ },
+ "OpenTelemetry.Extensions.Hosting": {
+ "type": "Transitive",
+ "resolved": "1.15.3",
+ "contentHash": "u8n/W8yIlqv0BXZmvId1iVaeWXG42tGKdTkuLYg5g57Y/r9CeUNzqtrSHNdG5IoO8iPX79w3v+WsbAHgUQbfeg==",
+ "dependencies": {
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.0",
+ "OpenTelemetry": "1.15.3"
+ }
+ },
+ "OpenTelemetry.Instrumentation.AspNetCore": {
+ "type": "Transitive",
+ "resolved": "1.15.2",
+ "contentHash": "2nPd7r0ug/gd6/CNFL6Rlu+RSQ9WYGSGHAYQ1ssbSqyzKJpqTunfx2I/1O0WB5k+L0cyXbG4XVZpoSoUc3M7wg==",
+ "dependencies": {
+ "OpenTelemetry.Api.ProviderBuilderExtensions": "[1.15.3, 2.0.0)"
+ }
+ },
+ "protobuf-net": {
+ "type": "Transitive",
+ "resolved": "3.2.52",
+ "contentHash": "XbZurNU3B/VaL/5OJ0kshO+AWxsZroI1saKuLfZpDwH2ngb2K9bdF1nIW6elFOViZw7TQCmfVZapxrMKCDqecQ==",
+ "dependencies": {
+ "protobuf-net.Core": "3.2.52"
+ }
+ },
+ "protobuf-net.Core": {
+ "type": "Transitive",
+ "resolved": "3.2.52",
+ "contentHash": "zOpGtUo2QTgbsiI0D0yCe8aUTgDPov6kqIu1CDHI6isqhYcAHdirRrdnfsQXmAUfAWx1LwVYGgC6xe6fNS4UAg=="
+ },
+ "ProtoBufJsonConverter": {
+ "type": "Transitive",
+ "resolved": "0.11.0",
+ "contentHash": "lxvcZQlCtgYZpfm9hhAJVZ1jsPkb9g3fyaAOnQLyEu8MiAowEIdED6jzwfjqLqOhY4AahqnbfRvZ904Ud43X7w==",
+ "dependencies": {
+ "MetadataReferenceService.Default": "0.0.1",
+ "Microsoft.CodeAnalysis.CSharp": "4.8.0",
+ "Newtonsoft.Json": "13.0.3",
+ "Stef.Validation": "0.1.1",
+ "protobuf-net": "3.2.52"
+ }
+ },
+ "RamlToOpenApiConverter.SourceOnly": {
+ "type": "Transitive",
+ "resolved": "0.21.0",
+ "contentHash": "x0g2c4tgPC5i+ZofhlhSeiWAsOjzkatv523tKGglx0mL05JYevQ5sYPP4r0xthpRAv99/6EuBJ6TbbipsHTMzA=="
+ },
+ "RandomDataGenerator.Net": {
+ "type": "Transitive",
+ "resolved": "1.0.19.1",
+ "contentHash": "OkAqBA69VbYYg+biX2DYWcucOI/yEivkdJ/XPqife/mQAC0r/NArcAU/EI3i/1oRFUUCjibV0b7qEjK+TYTqDw==",
+ "dependencies": {
+ "Fare": "2.2.1",
+ "Stef.Validation": "0.1.1"
+ }
+ },
+ "Scriban.Signed": {
+ "type": "Transitive",
+ "resolved": "7.2.5",
+ "contentHash": "Fu1AjcAyrZIAW9LIhxVgVyo5EMVdwLhXagKKI2A1UZoI0Wvz2CiRT+VXp1tuiMq2JhlkjVyebj/JQcF4koZacg=="
+ },
+ "SharpYaml": {
+ "type": "Transitive",
+ "resolved": "2.1.4",
+ "contentHash": "/iwULhVBpTjD4wPZhLU+eUWBanDvri/2AGx5YbaAj5kp9kXzhqUfJEy56H5Yi+c+OXsdm/oKD1aTKB24BFp8cw=="
+ },
+ "SharpZipLib": {
+ "type": "Transitive",
+ "resolved": "1.4.2",
+ "contentHash": "yjj+3zgz8zgXpiiC3ZdF/iyTBbz2fFvMxZFEBPUcwZjIvXOf37Ylm+K58hqMfIBt5JgU/Z2uoUS67JmTLe973A=="
+ },
+ "SimMetrics.Net": {
+ "type": "Transitive",
+ "resolved": "1.0.5",
+ "contentHash": "LaSDYOJDh2WncgRboqiWtk/Igqoim/LV7v808qBeWY/f36Ol5oEKguEYpKrWw5ap8KYP0SRXf7/v3zil9koY6Q=="
+ },
+ "SSH.NET": {
+ "type": "Transitive",
+ "resolved": "2025.1.0",
+ "contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==",
+ "dependencies": {
+ "BouncyCastle.Cryptography": "2.6.2",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
+ }
+ },
+ "Stef.Validation": {
+ "type": "Transitive",
+ "resolved": "0.3.0",
+ "contentHash": "OfzmxQMK4eBzmobph43p1NsLTgVAC3XGTcvQS0odhsdL6uS7UsFzBMK6S9mAfIijZdLW4q98aZ3dtTOeTaQo6Q=="
+ },
+ "System.CodeDom": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA=="
+ },
+ "System.Diagnostics.EventLog": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "OvGz3PrzuAI/Sj7LTcXcCe3FClRI1IyRMZjNONcZtFh+Ww7nAtSh4kh08r8KVe/xxkXJPjR0Y1jF7H+N42d4xQ=="
+ },
+ "System.IdentityModel.Tokens.Jwt": {
+ "type": "Transitive",
+ "resolved": "8.19.2",
+ "contentHash": "gqhDC/icByKEutygpr+OFgAmjwTVowyzFjWB8K0q1ww8uFj5a1BQNL+QvijUl9uhq4p8OdDwcAIrjVC+eC9FVA==",
+ "dependencies": {
+ "Microsoft.IdentityModel.JsonWebTokens": "8.19.2",
+ "Microsoft.IdentityModel.Tokens": "8.19.2"
+ }
+ },
+ "System.Management": {
+ "type": "Transitive",
+ "resolved": "6.0.1",
+ "contentHash": "10J1D0h/lioojphfJ4Fuh5ZUThT/xOVHdV9roGBittKKNP2PMjrvibEdbVTGZcPra1399Ja3tqIJLyQrc5Wmhg==",
+ "dependencies": {
+ "System.CodeDom": "6.0.0"
+ }
+ },
+ "Testcontainers": {
+ "type": "Transitive",
+ "resolved": "4.13.0",
+ "contentHash": "j8vi9jPBNSwaraGGx8w+2gtZyWrlbKxdhiGMS3nektg+KiwjFWx9ghCjs57EoQfvI+IAbzti0oQJupQChwgMog==",
+ "dependencies": {
+ "Docker.DotNet.Enhanced": "4.3.3",
+ "Docker.DotNet.Enhanced.X509": "4.3.3",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.3",
+ "SSH.NET": "2025.1.0",
+ "SharpZipLib": "1.4.2"
+ }
+ },
+ "TinyMapper.Signed": {
+ "type": "Transitive",
+ "resolved": "4.0.0",
+ "contentHash": "W5uc9QXp8PUgP3VQ1Qyt3vK8ptyjj38tJ7nEAtRKA6R/4e6+2gsgYrAmRg9fCK1hhe3E0yeAm5acC14qx2CINg=="
+ },
+ "WireMock.Net.Abstractions": {
+ "type": "Transitive",
+ "resolved": "2.13.0",
+ "contentHash": "7ZmPVJxlSBj0E7d47PHLydz15w0TEJ6WH2m7T/hucT3QfxbhN07AV0mwBoyO/T6jv+Af+hgEgmlep3v9TACxPw=="
+ },
+ "WireMock.Net.GraphQL": {
+ "type": "Transitive",
+ "resolved": "2.13.0",
+ "contentHash": "3nMUB7E8ner6fpdOZe91qx/1O3ebEOke1dCeCw+wIvnmtPhlfdOuss2saICqc1nrwLv2bAOVQ29606Ayinf7kw==",
+ "dependencies": {
+ "GraphQL.NewtonsoftJson": "8.5.0",
+ "WireMock.Net.Shared": "2.13.0"
+ }
+ },
+ "WireMock.Net.Matchers.SystemTextJsonPath": {
+ "type": "Transitive",
+ "resolved": "2.13.0",
+ "contentHash": "uhuQW2mFvi2X3v5Om3nDYYaF/ApkDHsFB0A1pklyLJnBUp1iUp+6Imt3t0j1bpMggHKaXJXEhDCqoDxvGP4FIQ==",
+ "dependencies": {
+ "JsonPath.Net": "3.0.2",
+ "WireMock.Net.Shared": "2.13.0"
+ }
+ },
+ "WireMock.Net.MimePart": {
+ "type": "Transitive",
+ "resolved": "2.13.0",
+ "contentHash": "bFJsUJ+zKOZC8FMO+8kyRHW51Qt5DkKRKS7//dzuBcuJVY2XdEnOdfaAlMfnN9T73XrHCi8skU+G1xdfWD2x3g==",
+ "dependencies": {
+ "Stef.Validation": "0.3.0",
+ "WireMock.Net.Shared": "2.13.0"
+ }
+ },
+ "WireMock.Net.Minimal": {
+ "type": "Transitive",
+ "resolved": "2.13.0",
+ "contentHash": "7VIKKuEiAHa19x7rbyf1s90UymTVQVTXz1CZc9mUM7DdW1LdaYyHUqBK4mBycvzsxuam7+jkU7l+aMJGAb0kBA==",
+ "dependencies": {
+ "JmesPath.Net": "1.1.0",
+ "Microsoft.IdentityModel.Protocols.OpenIdConnect": "6.34.0",
+ "NJsonSchema.Extensions": "0.2.0",
+ "NSwag.Core": "13.16.1",
+ "Scriban.Signed": "7.2.5",
+ "SimMetrics.Net": "1.0.5",
+ "TinyMapper.Signed": "4.0.0",
+ "WireMock.Net.OpenApiParser": "2.13.0",
+ "WireMock.Net.Shared": "2.13.0",
+ "WireMock.Org.Abstractions": "2.13.0"
+ }
+ },
+ "WireMock.Net.OpenApiParser": {
+ "type": "Transitive",
+ "resolved": "2.13.0",
+ "contentHash": "XQ2hgycULdhp1F16OqixwDwz2zaPtk3rpdurmL9MLZjr8TUWMCyHfStXeaU/vHu/of/xnYAh+qB0dwtdmGwY0Q==",
+ "dependencies": {
+ "Newtonsoft.Json": "13.0.4",
+ "RamlToOpenApiConverter.SourceOnly": "0.21.0",
+ "RandomDataGenerator.Net": "1.0.19.1",
+ "SharpYaml": "2.1.4",
+ "Stef.Validation": "0.3.0",
+ "WireMock.Net.Abstractions": "2.13.0",
+ "YamlDotNet": "18.1.0"
+ }
+ },
+ "WireMock.Net.OpenTelemetry": {
+ "type": "Transitive",
+ "resolved": "2.13.0",
+ "contentHash": "3SjRQcAd1pPZXB7jtj7vx7cbWdkskQl030W2QJldIOf+P9VtvKPJ5SsNCKI43Eg5O7RyrYuy2AQVdik7lbUi/Q==",
+ "dependencies": {
+ "OpenTelemetry.Exporter.OpenTelemetryProtocol": "1.15.3",
+ "OpenTelemetry.Extensions.Hosting": "1.15.3",
+ "OpenTelemetry.Instrumentation.AspNetCore": "1.15.2",
+ "WireMock.Net.Shared": "2.13.0"
+ }
+ },
+ "WireMock.Net.ProtoBuf": {
+ "type": "Transitive",
+ "resolved": "2.13.0",
+ "contentHash": "WRG6cujOXRe3ZiStkElH82lOlDhS3YiV/cpXQbnCLpTkf+F/RBmrivzqO7ILcTccVKdwiIbT+ANdCR0vVCXZMg==",
+ "dependencies": {
+ "ProtoBufJsonConverter": "0.11.0",
+ "WireMock.Net.Shared": "2.13.0"
+ }
+ },
+ "WireMock.Net.Shared": {
+ "type": "Transitive",
+ "resolved": "2.13.0",
+ "contentHash": "vvA0ssOFv3IoQI5UL5dr3mtAgF5imyit999sWEJYtXp7lLeA/fGvmbYLEpG5CeQ1RRtEcIxnJkRdtcttYmIC4Q==",
+ "dependencies": {
+ "AnyOf": "0.5.0.1",
+ "Handlebars.Net.Helpers": "2.5.5",
+ "Handlebars.Net.Helpers.Humanizer": "2.5.5",
+ "Handlebars.Net.Helpers.Json": "2.5.5",
+ "Handlebars.Net.Helpers.Random": "2.5.5",
+ "Handlebars.Net.Helpers.XPath": "2.5.5",
+ "Handlebars.Net.Helpers.Xeger": "2.5.5",
+ "Handlebars.Net.Helpers.Xslt": "2.5.5",
+ "JsonConverter.Newtonsoft.Json": "0.13.0",
+ "JsonConverter.System.Text.Json": "0.13.0",
+ "Microsoft.AspNetCore.Http": "2.3.9",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2",
+ "Stef.Validation": "0.3.0",
+ "WireMock.Net.Abstractions": "2.13.0"
+ }
+ },
+ "WireMock.Org.Abstractions": {
+ "type": "Transitive",
+ "resolved": "2.13.0",
+ "contentHash": "1z7W3ryp+xufZ77ux3NODNMl/jw00Koagpj8aOaFQQgOQuxJMd8hICi1ErF1DsIXd8Ewp13s2HGntj4yWeRfRA=="
+ },
+ "XPath2": {
+ "type": "Transitive",
+ "resolved": "1.1.5",
+ "contentHash": "LQg7kZyAmmb+qvv5TiOuuijxN97rRbR05qbMkVIH+i+sx9CA2UNUKGNtdVxWEXOabS8BIwlXm6ox1OOTjvZ6jw=="
+ },
+ "XPath2.Extensions": {
+ "type": "Transitive",
+ "resolved": "1.1.5",
+ "contentHash": "oEbdGUJsF25QL3Vj1GgSlT2xdbxnka5dKcjuA9CouWCV/l9ecSfypOv78B1+YUD8a8w47prLNw2i3ofLNcrbGA==",
+ "dependencies": {
+ "Newtonsoft.Json": "13.0.3",
+ "XPath2": "1.1.5"
+ }
+ },
+ "xunit.analyzers": {
+ "type": "Transitive",
+ "resolved": "1.27.0",
+ "contentHash": "y/pxIQaLvk/kxAoDkZW9GnHLCEqzwl5TW0vtX3pweyQpjizB9y3DXhb9pkw2dGeUqhLjsxvvJM1k89JowU6z3g=="
+ },
+ "xunit.v3.assert": {
+ "type": "Transitive",
+ "resolved": "3.2.2",
+ "contentHash": "BPciBghgEEaJN/JG00QfCYDfEfnLgQhfnYEy+j1izoeHVNYd5+3Wm8GJ6JgYysOhpBPYGE+sbf75JtrRc7jrdA=="
+ },
+ "xunit.v3.common": {
+ "type": "Transitive",
+ "resolved": "3.2.2",
+ "contentHash": "Hj775PEH6GTbbg0wfKRvG2hNspDCvTH9irXhH4qIWgdrOSV1sQlqPie+DOvFeigsFg2fxSM3ZAaaCDQs+KreFA==",
+ "dependencies": {
+ "Microsoft.Bcl.AsyncInterfaces": "6.0.0"
+ }
+ },
+ "xunit.v3.core.mtp-v1": {
+ "type": "Transitive",
+ "resolved": "3.2.2",
+ "contentHash": "Ga5aA2Ca9ktz+5k3g5ukzwfexwoqwDUpV6z7atSEUvqtd6JuybU1XopHqg1oFd78QdTfZgZE9h5sHpO4qYIi5w==",
+ "dependencies": {
+ "Microsoft.Testing.Extensions.Telemetry": "1.9.1",
+ "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.9.1",
+ "Microsoft.Testing.Platform": "1.9.1",
+ "Microsoft.Testing.Platform.MSBuild": "1.9.1",
+ "xunit.v3.extensibility.core": "[3.2.2]",
+ "xunit.v3.runner.inproc.console": "[3.2.2]"
+ }
+ },
+ "xunit.v3.extensibility.core": {
+ "type": "Transitive",
+ "resolved": "3.2.2",
+ "contentHash": "srY8z/oMPvh/t8axtO2DwrHajhFMH7tnqKildvYrVQIfICi8fOn3yIBWkVPAcrKmHMwvXRJ/XsQM3VMR6DOYfQ==",
+ "dependencies": {
+ "xunit.v3.common": "[3.2.2]"
+ }
+ },
+ "xunit.v3.mtp-v1": {
+ "type": "Transitive",
+ "resolved": "3.2.2",
+ "contentHash": "O41aAzYKBT5PWqATa1oEWVNCyEUypFQ4va6K0kz37dduV3EKzXNMaV2UnEhufzU4Cce1I33gg0oldS8tGL5I0A==",
+ "dependencies": {
+ "xunit.analyzers": "1.27.0",
+ "xunit.v3.assert": "[3.2.2]",
+ "xunit.v3.core.mtp-v1": "[3.2.2]"
+ }
+ },
+ "xunit.v3.runner.common": {
+ "type": "Transitive",
+ "resolved": "3.2.2",
+ "contentHash": "/hkHkQCzGrugelOAehprm7RIWdsUFVmIVaD6jDH/8DNGCymTlKKPTbGokD5czbAfqfex47mBP0sb0zbHYwrO/g==",
+ "dependencies": {
+ "Microsoft.Win32.Registry": "[5.0.0]",
+ "xunit.v3.common": "[3.2.2]"
+ }
+ },
+ "xunit.v3.runner.inproc.console": {
+ "type": "Transitive",
+ "resolved": "3.2.2",
+ "contentHash": "ulWOdSvCk+bPXijJZ73bth9NyoOHsAs1ZOvamYbCkD4DNLX/Bd29Ve2ZNUwBbK0MqfIYWXHZViy/HKrdEC/izw==",
+ "dependencies": {
+ "xunit.v3.extensibility.core": "[3.2.2]",
+ "xunit.v3.runner.common": "[3.2.2]"
+ }
+ },
+ "YamlDotNet": {
+ "type": "Transitive",
+ "resolved": "18.1.0",
+ "contentHash": "5K+9KFg2TdTl7VXv88Qzi/0lqK6JFoNP3lRuImPYGRV7K/QYklDyTrj4+A+KAki1JsQi6qKY+hDyY7d6WRqjrw=="
+ },
+ "dodossh.api": {
+ "type": "Project",
+ "dependencies": {
+ "DodoSSH.Contracts": "[1.0.0, )",
+ "DodoSSH.Crypto": "[1.0.0, )",
+ "DodoSSH.Domain": "[1.0.0, )",
+ "DodoSSH.Infrastructure": "[1.0.0, )",
+ "Microsoft.AspNetCore.Authentication.JwtBearer": "[10.0.10, )",
+ "Microsoft.AspNetCore.OpenApi": "[10.0.10, )"
+ }
+ },
+ "dodossh.contracts": {
+ "type": "Project"
+ },
+ "dodossh.crypto": {
+ "type": "Project",
+ "dependencies": {
+ "NSec.Cryptography": "[26.4.0, )"
+ }
+ },
+ "dodossh.domain": {
+ "type": "Project"
+ },
+ "dodossh.infrastructure": {
+ "type": "Project",
+ "dependencies": {
+ "DodoSSH.Domain": "[1.0.0, )",
+ "EFCore.NamingConventions": "[10.0.1, )",
+ "Npgsql.EntityFrameworkCore.PostgreSQL": "[10.0.3, )"
+ }
+ },
+ "BouncyCastle.Cryptography": {
+ "type": "CentralTransitive",
+ "requested": "[2.6.2, )",
+ "resolved": "2.6.2",
+ "contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w=="
+ },
+ "EFCore.NamingConventions": {
+ "type": "CentralTransitive",
+ "requested": "[10.0.1, )",
+ "resolved": "10.0.1",
+ "contentHash": "Xs5k8XfNKPkkQSkGmZkmDI1je0prLTdxse+s8PgTFZxyBrlrTLzTBUTVJtQKSsbvu4y+luAv8DdtO5SALJE++A==",
+ "dependencies": {
+ "Microsoft.EntityFrameworkCore": "[10.0.1, 11.0.0)",
+ "Microsoft.EntityFrameworkCore.Relational": "[10.0.1, 11.0.0)",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1"
+ }
+ },
+ "libsodium": {
+ "type": "CentralTransitive",
+ "requested": "[1.0.22, )",
+ "resolved": "1.0.22",
+ "contentHash": "KPD9SloJFclrsjnhABu7dzWrcyYkwPbvx5l1gRSPAX/0n+OBtSiVCKtGFv4n+ecWUHU0tCG9LSSwoZZx673zBQ=="
+ },
+ "Microsoft.AspNetCore.Authentication.JwtBearer": {
+ "type": "CentralTransitive",
+ "requested": "[10.0.10, )",
+ "resolved": "10.0.10",
+ "contentHash": "VAcqS42zb9WJd9DjPdkVTS5YrQENmNzPNJuRu8VAW7x3TEWUipc4d4hHzVJdFB0h/KLdr4XcXZzRHcUOKVanMQ==",
+ "dependencies": {
+ "Microsoft.IdentityModel.Protocols.OpenIdConnect": "8.19.2"
+ }
+ },
+ "Microsoft.AspNetCore.OpenApi": {
+ "type": "CentralTransitive",
+ "requested": "[10.0.10, )",
+ "resolved": "10.0.10",
+ "contentHash": "d4Atx9IHq7JgX0F/h7Db+m9zAUzC+cKdI9k+OWnnyQIOUQtfvjIEuhvbjPigVMkAmPUgCbJ8Yp6M9ghUqHtJSQ==",
+ "dependencies": {
+ "Microsoft.OpenApi": "2.0.0"
+ }
+ },
+ "Microsoft.EntityFrameworkCore": {
+ "type": "CentralTransitive",
+ "requested": "[10.0.10, )",
+ "resolved": "10.0.10",
+ "contentHash": "a0V7zj/VbYP6dTdWpUgE/r2PuLKtUGe2aJ0lVKkn/wP9ZhaxUz2kQydVfvOjCv2SKxlrqdBfHhPD4Cvlf+4ffA==",
+ "dependencies": {
+ "Microsoft.EntityFrameworkCore.Abstractions": "10.0.10",
+ "Microsoft.EntityFrameworkCore.Analyzers": "10.0.10",
+ "Microsoft.Extensions.Caching.Memory": "10.0.10",
+ "Microsoft.Extensions.Logging": "10.0.10"
+ }
+ },
+ "Microsoft.EntityFrameworkCore.Relational": {
+ "type": "CentralTransitive",
+ "requested": "[10.0.10, )",
+ "resolved": "10.0.10",
+ "contentHash": "wNonj40aZxia+GtuBiiD6ZqVh4h6y5Nje1bGdmzZ8/ui0QRsAN+S0SIrLHFCEGbG9cDbeaE40sh+Lr7o9rRs6g==",
+ "dependencies": {
+ "Microsoft.EntityFrameworkCore": "10.0.10",
+ "Microsoft.Extensions.Caching.Memory": "10.0.10",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.10",
+ "Microsoft.Extensions.Logging": "10.0.10"
+ }
+ },
+ "Microsoft.OpenApi": {
+ "type": "CentralTransitive",
+ "requested": "[2.11.0, )",
+ "resolved": "2.11.0",
+ "contentHash": "/ignjfdeKT2SGLIR7QEv19KnI0rvoxRG/TYDOZdK9EsWLjKK9IK8i1Mo5NRm9PRV3i64DzlTqnIflWvoyfljLg=="
+ },
+ "Npgsql.EntityFrameworkCore.PostgreSQL": {
+ "type": "CentralTransitive",
+ "requested": "[10.0.3, )",
+ "resolved": "10.0.3",
+ "contentHash": "IPGrrZnRkuW7OlHDhUESZz4G5DLkW7Nej/O3Cx+0iTsgyU5XJxBgpsvTHLloo3WWuAKKbDHXBvWPVkX1deRh1Q==",
+ "dependencies": {
+ "Microsoft.EntityFrameworkCore": "[10.0.4, 11.0.0)",
+ "Microsoft.EntityFrameworkCore.Relational": "[10.0.4, 11.0.0)",
+ "Npgsql": "10.0.3"
+ }
+ },
+ "NSec.Cryptography": {
+ "type": "CentralTransitive",
+ "requested": "[26.4.0, )",
+ "resolved": "26.4.0",
+ "contentHash": "0vsCtY5f+YgQROiWNqzgWp+l2pddfk9FkWoGV/bEo0MuEYPKlJWuoA8aOfO6qp3f+EnObKE3zSJhn1PspJeJVg==",
+ "dependencies": {
+ "libsodium": "[1.0.22, 1.0.23)"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file