diff --git a/AndreGoepel.Marten.Configuration.slnx b/AndreGoepel.Marten.Configuration.slnx
index d9ca280..84fb1d3 100644
--- a/AndreGoepel.Marten.Configuration.slnx
+++ b/AndreGoepel.Marten.Configuration.slnx
@@ -9,6 +9,7 @@
+
diff --git a/CLAUDE.md b/CLAUDE.md
index 34e40d3..52bf750 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -27,16 +27,25 @@ among others. Plain class library — no ASP.NET Core dependency.
- `SaveAsync` sets `SettingsDocument.Id` from `T.DocumentId` itself —
callers never set it manually.
- Public API surface is only `SettingsDocument`, `ISettingsDocument`,
- `ISettingsStore`, `SettingsStoreOptionsExtensions`, `Initialization` —
- everything else stays `internal`.
+ `ISettingsStore`, `SettingsStoreOptionsExtensions`, `Initialization`,
+ `DataProtectorExtensions` — everything else stays `internal`.
+- `DataProtectorExtensions.ProtectOrKeepExisting` is the shared
+ "protect a new value, or keep the existing ciphertext when the caller
+ left the field blank" round trip for a secret field inside a settings
+ document (SMTP password, API token, provider credential, ...). It takes
+ the caller's own `IDataProtector` — this library never constructs one
+ itself, so it only depends on `Microsoft.AspNetCore.DataProtection.Abstractions`
+ (interfaces only), not the full ASP.NET Core shared framework.
- Exception: `MartenSettingsStore` is `public` (not `internal`) despite the
rule above — Wolverine's `NotAllowed` service-location policy constructs
it directly in generated handler code and requires public visibility.
## Testing
-- Scope: the Marten store and schema-hierarchy behavior (round-trip,
- shared-table mapping); integration tests need Docker for the Postgres
- container
+- `AndreGoepel.Marten.Configuration.Tests` — pure unit tests, no I/O
+ (e.g. `DataProtectorExtensions`)
+- `AndreGoepel.Marten.Configuration.IntegrationTests` — the Marten store
+ and schema-hierarchy behavior (round-trip, shared-table mapping); needs
+ Docker for the Postgres container
## Conventions Audit
- `.editorconfig` is intentionally the 26-line variant (no Razor/CSS/JS
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 85c0160..355f83a 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -15,6 +15,10 @@
/>
+
+
+
+
diff --git a/src/AndreGoepel.Marten.Configuration/AndreGoepel.Marten.Configuration.csproj b/src/AndreGoepel.Marten.Configuration/AndreGoepel.Marten.Configuration.csproj
index f9aa8b7..d70edd8 100644
--- a/src/AndreGoepel.Marten.Configuration/AndreGoepel.Marten.Configuration.csproj
+++ b/src/AndreGoepel.Marten.Configuration/AndreGoepel.Marten.Configuration.csproj
@@ -20,10 +20,12 @@
+
+
diff --git a/src/AndreGoepel.Marten.Configuration/DataProtectorExtensions.cs b/src/AndreGoepel.Marten.Configuration/DataProtectorExtensions.cs
new file mode 100644
index 0000000..121f098
--- /dev/null
+++ b/src/AndreGoepel.Marten.Configuration/DataProtectorExtensions.cs
@@ -0,0 +1,30 @@
+using Microsoft.AspNetCore.DataProtection;
+
+namespace AndreGoepel.Marten.Configuration;
+
+///
+/// Shared round trip for a DataProtection-encrypted secret field inside a
+/// (an SMTP password, an API token, a provider credential, ...).
+///
+public static class DataProtectorExtensions
+{
+ ///
+ /// Protects when the caller supplied one; otherwise keeps
+ /// unchanged. This is the "leave the field blank to
+ /// keep the current secret" idiom used by every settings form that edits a protected value.
+ /// Returns null when there is neither a new value nor an existing one — callers
+ /// decide what that means for them (reject the save, or protect an explicit empty string
+ /// when a blank secret is itself a valid saved state).
+ ///
+ public static string? ProtectOrKeepExisting(
+ this IDataProtector protector,
+ string? newPlaintext,
+ string? existingCiphertext
+ )
+ {
+ ArgumentNullException.ThrowIfNull(protector);
+ return !string.IsNullOrEmpty(newPlaintext)
+ ? protector.Protect(newPlaintext)
+ : existingCiphertext;
+ }
+}
diff --git a/src/AndreGoepel.Marten.Configuration/packages.lock.json b/src/AndreGoepel.Marten.Configuration/packages.lock.json
index 993a654..9057e77 100644
--- a/src/AndreGoepel.Marten.Configuration/packages.lock.json
+++ b/src/AndreGoepel.Marten.Configuration/packages.lock.json
@@ -16,6 +16,12 @@
"Weasel.Storage": "9.17.0"
}
},
+ "Microsoft.AspNetCore.DataProtection.Abstractions": {
+ "type": "Direct",
+ "requested": "[10.0.10, )",
+ "resolved": "10.0.10",
+ "contentHash": "q2RNx0N/qrsU+JgbPE78I9pu5ekwguitAFVoeE1NCgxtkUTguvf7oTzBnn42zSQxuugJ4fmj0RSarob8Rvorrg=="
+ },
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Direct",
"requested": "[10.0.10, )",
diff --git a/tests/AndreGoepel.Marten.Configuration.IntegrationTests/packages.lock.json b/tests/AndreGoepel.Marten.Configuration.IntegrationTests/packages.lock.json
index e6f5995..1c7f7e9 100644
--- a/tests/AndreGoepel.Marten.Configuration.IntegrationTests/packages.lock.json
+++ b/tests/AndreGoepel.Marten.Configuration.IntegrationTests/packages.lock.json
@@ -693,6 +693,7 @@
"type": "Project",
"dependencies": {
"Marten": "[9.19.0, )",
+ "Microsoft.AspNetCore.DataProtection.Abstractions": "[10.0.10, )",
"Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.10, )"
}
},
@@ -710,6 +711,12 @@
"Weasel.Storage": "9.17.0"
}
},
+ "Microsoft.AspNetCore.DataProtection.Abstractions": {
+ "type": "CentralTransitive",
+ "requested": "[10.0.10, )",
+ "resolved": "10.0.10",
+ "contentHash": "q2RNx0N/qrsU+JgbPE78I9pu5ekwguitAFVoeE1NCgxtkUTguvf7oTzBnn42zSQxuugJ4fmj0RSarob8Rvorrg=="
+ },
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[10.0.10, )",
diff --git a/tests/AndreGoepel.Marten.Configuration.Tests/AndreGoepel.Marten.Configuration.Tests.csproj b/tests/AndreGoepel.Marten.Configuration.Tests/AndreGoepel.Marten.Configuration.Tests.csproj
new file mode 100644
index 0000000..271292b
--- /dev/null
+++ b/tests/AndreGoepel.Marten.Configuration.Tests/AndreGoepel.Marten.Configuration.Tests.csproj
@@ -0,0 +1,32 @@
+
+
+ net10.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
diff --git a/tests/AndreGoepel.Marten.Configuration.Tests/DataProtectorExtensionsTests.cs b/tests/AndreGoepel.Marten.Configuration.Tests/DataProtectorExtensionsTests.cs
new file mode 100644
index 0000000..d5bd07e
--- /dev/null
+++ b/tests/AndreGoepel.Marten.Configuration.Tests/DataProtectorExtensionsTests.cs
@@ -0,0 +1,73 @@
+using Microsoft.AspNetCore.DataProtection;
+
+namespace AndreGoepel.Marten.Configuration.Tests;
+
+public sealed class DataProtectorExtensionsTests
+{
+ private readonly IDataProtector protector =
+ new EphemeralDataProtectionProvider().CreateProtector(
+ "AndreGoepel.Marten.Configuration.Tests.DataProtectorExtensions"
+ );
+
+ [Fact]
+ public void ProtectOrKeepExisting_NewPlaintextGiven_ProtectsTheNewValue()
+ {
+ // Act
+ var ciphertext = protector.ProtectOrKeepExisting("new-secret", "old-ciphertext");
+
+ // Assert
+ Assert.NotNull(ciphertext);
+ Assert.NotEqual("old-ciphertext", ciphertext);
+ Assert.Equal("new-secret", protector.Unprotect(ciphertext));
+ }
+
+ [Fact]
+ public void ProtectOrKeepExisting_NewPlaintextEmpty_KeepsExistingCiphertext()
+ {
+ // Act
+ var ciphertext = protector.ProtectOrKeepExisting(string.Empty, "old-ciphertext");
+
+ // Assert
+ Assert.Equal("old-ciphertext", ciphertext);
+ }
+
+ [Fact]
+ public void ProtectOrKeepExisting_NewPlaintextNull_KeepsExistingCiphertext()
+ {
+ // Act
+ var ciphertext = protector.ProtectOrKeepExisting(null, "old-ciphertext");
+
+ // Assert
+ Assert.Equal("old-ciphertext", ciphertext);
+ }
+
+ [Fact]
+ public void ProtectOrKeepExisting_NeitherNewValueNorExistingCiphertextGiven_ReturnsNull()
+ {
+ // Act
+ var ciphertext = protector.ProtectOrKeepExisting(null, null);
+
+ // Assert
+ Assert.Null(ciphertext);
+ }
+
+ [Fact]
+ public void ProtectOrKeepExisting_RoundTripsThroughUnprotect()
+ {
+ // Act
+ var ciphertext = protector.ProtectOrKeepExisting("correct horse battery staple", null);
+
+ // Assert
+ Assert.NotNull(ciphertext);
+ Assert.Equal("correct horse battery staple", protector.Unprotect(ciphertext));
+ }
+
+ [Fact]
+ public void ProtectOrKeepExisting_NullProtector_Throws()
+ {
+ // Act / Assert
+ Assert.Throws(() =>
+ DataProtectorExtensions.ProtectOrKeepExisting(null!, "value", null)
+ );
+ }
+}
diff --git a/tests/AndreGoepel.Marten.Configuration.Tests/GlobalUsings.cs b/tests/AndreGoepel.Marten.Configuration.Tests/GlobalUsings.cs
new file mode 100644
index 0000000..c802f44
--- /dev/null
+++ b/tests/AndreGoepel.Marten.Configuration.Tests/GlobalUsings.cs
@@ -0,0 +1 @@
+global using Xunit;
diff --git a/tests/AndreGoepel.Marten.Configuration.Tests/packages.lock.json b/tests/AndreGoepel.Marten.Configuration.Tests/packages.lock.json
new file mode 100644
index 0000000..57b9a2c
--- /dev/null
+++ b/tests/AndreGoepel.Marten.Configuration.Tests/packages.lock.json
@@ -0,0 +1,316 @@
+{
+ "version": 2,
+ "dependencies": {
+ "net10.0": {
+ "coverlet.collector": {
+ "type": "Direct",
+ "requested": "[10.0.1, )",
+ "resolved": "10.0.1",
+ "contentHash": "27jXSV/0DbVqF5jDrAxuQFZ9oaz6gmG03p8ttxAFk+X0M4woFYj7MoWDLCna5EGLb0CE6OE7X6ZH3Wt5smTtaA=="
+ },
+ "Microsoft.NET.Test.Sdk": {
+ "type": "Direct",
+ "requested": "[18.8.1, )",
+ "resolved": "18.8.1",
+ "contentHash": "dknJL3/9Y3t4XuCBqnc0PevPxgLsUMmVhjwup/b1HNovA8zWcj3XsfIf7c6p05363DWcqL7X/YhDL9B+Zymv1w==",
+ "dependencies": {
+ "Microsoft.CodeCoverage": "18.8.1",
+ "Microsoft.TestPlatform.TestHost": "18.8.1"
+ }
+ },
+ "xunit.runner.visualstudio": {
+ "type": "Direct",
+ "requested": "[3.1.5, )",
+ "resolved": "3.1.5",
+ "contentHash": "tKi7dSTwP4m5m9eXPM2Ime4Kn7xNf4x4zT9sdLO/G4hZVnQCRiMTWoSZqI/pYTVeI27oPPqHBKYI/DjJ9GsYgA=="
+ },
+ "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]"
+ }
+ },
+ "DistributedLock.Core": {
+ "type": "Transitive",
+ "resolved": "1.0.8",
+ "contentHash": "LAOsY8WxX8JU/n3lfXFz+f2pfnv0+4bHkCrOO3bwa28u9HrS3DlxSG6jf+u76SqesKs+KehZi0CndkfaUXBKvg=="
+ },
+ "DistributedLock.Postgres": {
+ "type": "Transitive",
+ "resolved": "1.3.0",
+ "contentHash": "CyjXmbhFgG30qPg4DwGnsmZO63y5DBRo+PI2xW0NwtFrsYsMVt/T1RcEUlb36JMKhDkf+euhnkanv/nU+W35qA==",
+ "dependencies": {
+ "DistributedLock.Core": "[1.0.8, 1.1.0)",
+ "Npgsql": "8.0.6"
+ }
+ },
+ "FastExpressionCompiler": {
+ "type": "Transitive",
+ "resolved": "5.4.1",
+ "contentHash": "nqMykMspK5cQd35Y8ulQzAujxCcCwZVUne32pC5xNpXqrbPCuKOPMWkKHJ3LveAZPm6dsVvump0TJ1SZ9RzfTQ=="
+ },
+ "ImTools": {
+ "type": "Transitive",
+ "resolved": "4.0.0",
+ "contentHash": "dms0JfLKC9AQbQX/EdpRjn59EjEvaCxIgv1z9YxbEQb5SiOVoo7vlIKdEySeEwUkWhN05rZnNpUpG+aw5VqPVQ=="
+ },
+ "JasperFx": {
+ "type": "Transitive",
+ "resolved": "2.35.0",
+ "contentHash": "+UzKoGkAtfuxfAIPvGjtfjF8gV7mjLE1bfbPPkcHvuVEeemFJtTuXYiR2qxJCO4TiBTxRzuWG6CmIy9PoaKh8Q==",
+ "dependencies": {
+ "FastExpressionCompiler": "5.4.1",
+ "ImTools": "4.0.0",
+ "Microsoft.Bcl.TimeProvider": "10.0.0",
+ "Polly.Core": "8.6.5",
+ "Spectre.Console": "0.55.0"
+ }
+ },
+ "JasperFx.Events": {
+ "type": "Transitive",
+ "resolved": "2.35.0",
+ "contentHash": "L1HYGPOwsSne/B4nEeUu4eM0VFWgH6WFNbUc6aol4bfknbxl6NRhpvA25vXKgWCG4dfqeUSjUewjI26RkC31tw==",
+ "dependencies": {
+ "JasperFx": "2.35.0"
+ }
+ },
+ "Microsoft.ApplicationInsights": {
+ "type": "Transitive",
+ "resolved": "2.23.0",
+ "contentHash": "nWArUZTdU7iqZLycLKWe0TDms48KKGE6pONH2terYNa8REXiqixrMOkf1sk5DHGMaUTqONU2YkS4SAXBhLStgw=="
+ },
+ "Microsoft.Bcl.AsyncInterfaces": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg=="
+ },
+ "Microsoft.Bcl.TimeProvider": {
+ "type": "Transitive",
+ "resolved": "10.0.0",
+ "contentHash": "bUubrBD6tRJE3V1kvRloYc6NymH3R5oFKjAS9e0ELNx6u0ZR+zjps9dDQyjgqN/rArzl7f+21KGszj3YRN7F2Q=="
+ },
+ "Microsoft.CodeCoverage": {
+ "type": "Transitive",
+ "resolved": "18.8.1",
+ "contentHash": "Eclse/ZZjr4lmWzZFNN9h/OluhKL+SK/QbUyKUewgX139aGeyMEO/DkMPwuFs2MixvanTnz6891rF8UHDg+W4Q=="
+ },
+ "Microsoft.IO.RecyclableMemoryStream": {
+ "type": "Transitive",
+ "resolved": "3.0.1",
+ "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g=="
+ },
+ "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.TestPlatform.ObjectModel": {
+ "type": "Transitive",
+ "resolved": "18.8.1",
+ "contentHash": "qLbktNB1+b1XZLNJBTzaWVVJAd6PEzD7cgD406geMb6PcFZhp3EDNa1tctWx1+mtMU6MP/6ozVvFPC9vs2a9rw=="
+ },
+ "Microsoft.TestPlatform.TestHost": {
+ "type": "Transitive",
+ "resolved": "18.8.1",
+ "contentHash": "FaQHPDTUOcE+SFTjssNPfrub2lT9Zyon4J2W/KLHt/efLJACb1TCeWXyOgh0D/4Q1e4n+S3E6mOKud+9nLZlEA==",
+ "dependencies": {
+ "Microsoft.TestPlatform.ObjectModel": "18.8.1"
+ }
+ },
+ "Microsoft.Win32.Registry": {
+ "type": "Transitive",
+ "resolved": "5.0.0",
+ "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg=="
+ },
+ "NetTopologySuite": {
+ "type": "Transitive",
+ "resolved": "2.5.0",
+ "contentHash": "5/+2O2ADomEdUn09mlSigACdqvAf0m/pVPGtIPEPQWnyrVykYY0NlfXLIdkMgi41kvH9kNrPqYaFBTZtHYH7Xw=="
+ },
+ "NetTopologySuite.IO.PostGis": {
+ "type": "Transitive",
+ "resolved": "2.1.0",
+ "contentHash": "3W8XTFz8iP6GQ5jDXK1/LANHiU+988k1kmmuPWNKcJLpmSg6CvFpbTpz+s4+LBzkAp64wHGOldSlkSuzYfrIKA==",
+ "dependencies": {
+ "NetTopologySuite": "[2.0.0, 3.0.0-A)"
+ }
+ },
+ "Npgsql": {
+ "type": "Transitive",
+ "resolved": "9.0.4",
+ "contentHash": "68BASXH0FAEuL/J4J0eRfYC8/3vzqQTmoW8zDzNf0JgaVxc7LZeEkS6jaG0ib3voFLxY5ZiCwJG+uQM+mzuu0Q=="
+ },
+ "Npgsql.NetTopologySuite": {
+ "type": "Transitive",
+ "resolved": "9.0.4",
+ "contentHash": "7AwBLPz8EPmgAXveZ55K0Tz/9bNb8j4VI4pkTOQjyHrce8wWfAA9pefm3REidy+Kt2UFiAWef34YVi7sb/kB4A==",
+ "dependencies": {
+ "NetTopologySuite": "2.5.0",
+ "NetTopologySuite.IO.PostGIS": "2.1.0",
+ "Npgsql": "9.0.4"
+ }
+ },
+ "Polly.Core": {
+ "type": "Transitive",
+ "resolved": "8.6.5",
+ "contentHash": "t+sUVrIwvo7UmsgHGgOG9F0GDZSRIm47u2ylH17Gvcv1q5hNEwgD5GoBlFyc0kh/pebmPyrAgvGsR/65ZBaXlg=="
+ },
+ "Spectre.Console": {
+ "type": "Transitive",
+ "resolved": "0.55.0",
+ "contentHash": "2TVhUHFsDux0Z+y2Hv4bFsOMuON4SuotEEKkMparFkp5Rm259naaaEQrN4Sv6C1cbsiGKPjfkJDGgUp+hZPjTw==",
+ "dependencies": {
+ "Spectre.Console.Ansi": "0.55.0"
+ }
+ },
+ "Spectre.Console.Ansi": {
+ "type": "Transitive",
+ "resolved": "0.55.0",
+ "contentHash": "GIfgOvuF8MpU52bprhwtDEtx0gmVIGOepM8bw0lH/TSMD0rg1Xp+5Y53kPG8rFVdcpbNANlhDQ5mEVVPO3/2Tg=="
+ },
+ "Weasel.Core": {
+ "type": "Transitive",
+ "resolved": "9.17.0",
+ "contentHash": "KkIlUx6QS9i1+yk2klpUXeLFaXNWP77+CHQWPnx1wn3QKCz78PNAymI+Lsk/q35Rajzhp+mH7yr7aJrayYttsw==",
+ "dependencies": {
+ "JasperFx": "2.24.1"
+ }
+ },
+ "Weasel.Postgresql": {
+ "type": "Transitive",
+ "resolved": "9.17.0",
+ "contentHash": "gnRO4Gh280a3OURLHC1uaJZ7eTCDR3ECIpzOJBlUq5vtzHdRFVoZdwQQLxh2WhA1dKeFYNxMeYcMeluG2qDAVg==",
+ "dependencies": {
+ "DistributedLock.Postgres": "1.3.0",
+ "JasperFx.Events": "2.0.0",
+ "Npgsql": "9.0.4",
+ "Npgsql.NetTopologySuite": "9.0.4",
+ "Weasel.Core": "9.17.0"
+ }
+ },
+ "Weasel.Storage": {
+ "type": "Transitive",
+ "resolved": "9.17.0",
+ "contentHash": "r7s13H1xMDx9JK/MiqTRKzZ3rkh2kx9cQMGsoQ2kBsOFaA4F8CMpmkKW+9rC13OAVIxJroaJuGYU0kGMvpFPcg==",
+ "dependencies": {
+ "JasperFx.Events": "2.0.0",
+ "Weasel.Core": "9.17.0"
+ }
+ },
+ "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]"
+ }
+ },
+ "andregoepel.marten.configuration": {
+ "type": "Project",
+ "dependencies": {
+ "Marten": "[9.19.0, )"
+ }
+ },
+ "Marten": {
+ "type": "CentralTransitive",
+ "requested": "[9.19.0, )",
+ "resolved": "9.19.0",
+ "contentHash": "4QLFDZ53Rv6pqc25G2ay210H+G5aqbBn/+aMC/t6XBpU5aE1LORKH1RBh8a4SLt0ZZrGBz839W8UQamjBt3tNA==",
+ "dependencies": {
+ "JasperFx": "2.35.0",
+ "JasperFx.Events": "2.35.0",
+ "Microsoft.IO.RecyclableMemoryStream": "3.0.1",
+ "Weasel.Postgresql": "9.17.0",
+ "Weasel.Storage": "9.17.0"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file