diff --git a/SVSim.Bootstrap/Data/seeds/.gitkeep b/SVSim.Bootstrap/Data/seeds/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/SVSim.Bootstrap/Importers/SeedLoader.cs b/SVSim.Bootstrap/Importers/SeedLoader.cs
new file mode 100644
index 0000000..ec343ed
--- /dev/null
+++ b/SVSim.Bootstrap/Importers/SeedLoader.cs
@@ -0,0 +1,42 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace SVSim.Bootstrap.Importers;
+
+///
+/// Reads a JSON seed file under SVSim.Bootstrap/Data/seeds/. Replaces ImporterBase.LoadCapture.
+/// Files are produced by extractors in data_dumps/extract/; the bootstrap project does not
+/// transform wire formats. Missing files are non-fatal (returns empty/null) — caller decides.
+///
+public static class SeedLoader
+{
+ private static readonly JsonSerializerOptions Options = new()
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
+ NumberHandling = JsonNumberHandling.AllowReadingFromString,
+ ReadCommentHandling = JsonCommentHandling.Skip,
+ AllowTrailingCommas = true,
+ };
+
+ public static List LoadList(string path)
+ {
+ if (!File.Exists(path))
+ {
+ Console.Error.WriteLine($"[SeedLoader] Missing seed file: {path}");
+ return new List();
+ }
+ using var fs = File.OpenRead(path);
+ return JsonSerializer.Deserialize>(fs, Options) ?? new List();
+ }
+
+ public static T? LoadObject(string path) where T : class
+ {
+ if (!File.Exists(path))
+ {
+ Console.Error.WriteLine($"[SeedLoader] Missing seed file: {path}");
+ return null;
+ }
+ using var fs = File.OpenRead(path);
+ return JsonSerializer.Deserialize(fs, Options);
+ }
+}
diff --git a/SVSim.Bootstrap/Models/Seed/.gitkeep b/SVSim.Bootstrap/Models/Seed/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/SVSim.Bootstrap/SVSim.Bootstrap.csproj b/SVSim.Bootstrap/SVSim.Bootstrap.csproj
index dc5bf95..4ae4364 100644
--- a/SVSim.Bootstrap/SVSim.Bootstrap.csproj
+++ b/SVSim.Bootstrap/SVSim.Bootstrap.csproj
@@ -27,6 +27,9 @@
PreserveNewest
+
+ PreserveNewest
+
diff --git a/SVSim.UnitTests/Importers/SeedLoaderTests.cs b/SVSim.UnitTests/Importers/SeedLoaderTests.cs
new file mode 100644
index 0000000..e74049a
--- /dev/null
+++ b/SVSim.UnitTests/Importers/SeedLoaderTests.cs
@@ -0,0 +1,39 @@
+using SVSim.Bootstrap.Importers;
+
+namespace SVSim.UnitTests.Importers;
+
+public class SeedLoaderTests
+{
+ private sealed record Row(int Id, string Name);
+
+ [Test]
+ public void LoadList_returns_empty_when_file_missing()
+ {
+ string path = Path.Combine(Path.GetTempPath(), $"missing-{Guid.NewGuid()}.json");
+ var rows = SeedLoader.LoadList(path);
+ Assert.That(rows, Is.Empty);
+ }
+
+ [Test]
+ public void LoadList_deserializes_snake_case_array()
+ {
+ string path = Path.Combine(Path.GetTempPath(), $"seed-{Guid.NewGuid()}.json");
+ File.WriteAllText(path, "[{\"id\":1,\"name\":\"a\"},{\"id\":2,\"name\":\"b\"}]");
+ try
+ {
+ var rows = SeedLoader.LoadList(path);
+ Assert.That(rows, Has.Count.EqualTo(2));
+ Assert.That(rows[0].Id, Is.EqualTo(1));
+ Assert.That(rows[1].Name, Is.EqualTo("b"));
+ }
+ finally { File.Delete(path); }
+ }
+
+ [Test]
+ public void LoadObject_returns_null_when_file_missing()
+ {
+ string path = Path.Combine(Path.GetTempPath(), $"missing-{Guid.NewGuid()}.json");
+ var row = SeedLoader.LoadObject(path);
+ Assert.That(row, Is.Null);
+ }
+}