Synergy.Platform.Marten.NumericRevisions
1.6.0
dotnet add package Synergy.Platform.Marten.NumericRevisions --version 1.6.0
NuGet\Install-Package Synergy.Platform.Marten.NumericRevisions -Version 1.6.0
<PackageReference Include="Synergy.Platform.Marten.NumericRevisions" Version="1.6.0" />
<PackageVersion Include="Synergy.Platform.Marten.NumericRevisions" Version="1.6.0" />
<PackageReference Include="Synergy.Platform.Marten.NumericRevisions" />
paket add Synergy.Platform.Marten.NumericRevisions --version 1.6.0
#r "nuget: Synergy.Platform.Marten.NumericRevisions, 1.6.0"
#:package Synergy.Platform.Marten.NumericRevisions@1.6.0
#addin nuget:?package=Synergy.Platform.Marten.NumericRevisions&version=1.6.0
#tool nuget:?package=Synergy.Platform.Marten.NumericRevisions&version=1.6.0
Synergy.Platform.Marten
Centralized Marten governance for Synergy services. The Marten version and every persistence convention come from these packages, and no developer can drift them by mistake.
Packages
| Package | Reference it when… | Concurrency (mt_version) |
|---|---|---|
| Synergy.Platform.Marten.Uuid | your service owns its schema and shares no document tables (e.g. Dynamic Domain Builder) | uuid optimistic concurrency |
| Synergy.Platform.Marten.NumericRevisions | your service is a shared event-sourced service (Auth, Admin, Organisation, Template, DMS, …) | integer numeric revisions |
| Synergy.Platform.Marten.Core | never referenced directly — it's the shared core both leaves depend on | — |
You reference one leaf. Marten (pinned), the serializer, schema-per-service, StreamIdentity,
the analyzer, and the build guard all arrive transitively via Core. The concurrency standard
is the package name — there is no runtime flag to set wrong.
Usage
// the only line in your .csproj:
// <PackageReference Include="Synergy.Platform.Marten.NumericRevisions" Version="1.1.0" />
// (or Synergy.Platform.Marten.Uuid for an isolated service)
builder.Services.AddPlatformMarten(o =>
{
o.ConnectionString = builder.Configuration.GetConnectionString("Default")!;
o.ServiceSchema = "auth"; // your owned schema (never "public")
o.AutoCreate = AutoCreate.None; // CreateOrUpdate only for throwaway dev DBs / runtime-schema services
o.Customize = opts => { /* non-governed extras: indexes, extra mappings, BC schemas */ };
});
Enum storage (⚠️ read if upgrading from 1.1.0/1.2.0)
EnumStorage defaults to Marten's own default, AsInteger — so adopting the package does not change how your existing service stores enums. Set it only if your data actually stores enums as strings:
o.EnumStorage = EnumStorage.AsString; // only if your existing data stores enums as strings
1.1.0 / 1.2.0 forced
AsStringon every service that used the governed serializer (bug). A service whose data stores enums as integers had its on-disk shape flipped, breaking reads/queries. 1.3.0 fixes it: the default is nowAsInteger(Marten's default), and the value is per-service.Note: a service that supplies its own serializer via the
Customizehook (e.g. Dynamic Domain Builder installs its ownJsonNetSerializer) overrides the governed serializer completely, so this setting does not affect it — its enum storage is whatever its own serializer uses.
Enforcement (why drift is impossible)
| Wall | Mechanism | Stops |
|---|---|---|
Analyzer SYN001 (Core) |
compile error | raw AddMarten(...) |
Build target SYNB01 (Core, buildTransitive) |
build error | a direct Marten PackageReference |
Pinned MartenVersion (Core) + Central Package Management |
version can't float | Marten version drift |
nuget.config package-source-mapping + locked restore |
resolves only from the controlled feed | pulling Marten from anywhere else |
Package choice (.Uuid vs .NumericRevisions) |
reviewed in the .csproj |
the wrong concurrency model |
Consumer setup files are in templates/ — copy to each service repo root.
Versions at a glance
| Package version | Marten | Why you would move to it |
|---|---|---|
| 1.6.0 | 8.38.1 | Latest stable 8.x. Same dependency set as 8.37.4, and it carries the security fix. |
| 1.5.1 | 8.37.4 | The security release for CVE-2026-75513. Minimum safe version. |
| 1.1.0 - 1.5.0 | 8.37.3 | Vulnerable. NuGet resolves the lowest allowed version, so these still pull 8.37.3 no matter what is published. |
Services on 1.1.0-1.5.0 are NOT fixed by a new release existing; each one has to raise its own reference. As of 2026-10-01 the consumers in this estate were: Dynamic.Domain.Builder on 1.5.1, Synergy.Base.Auth and Synergy.Base.Notification on 1.5.0, platform-licensing-api, Synergy.Base.Admin and Synergy.DynamicDomains on 1.4.0.
Note on 8.38.x: it is published on NuGet but has no GitHub tag or release notes, so its changes are not publicly documented. Its dependency set is identical to 8.37.4. Services that reflect into Marten internals (Dynamic.Domain.Builder does, for dynamic document/event registration) should smoke test on a non-production environment before adopting 1.6.0; a plain consumer using AddPlatformMarten only has nothing to verify beyond its own tests.
Security: Marten 8.37.4 (⚠️ upgrade to 1.5.1)
1.5.1 raises the pinned Marten from 8.37.3 to 8.37.4, the 8.x security release for
CVE-2026-75513
(critical): SQL injection in Marten's LINQ provider through unescaped string literals. The main
attack path is a Dictionary<,> indexer key used in a Where filter, e.g.
Where(x => x.Attributes[key] == v) with a user-supplied key. It can bypass filters (including
tenant scoping) and leak data. ContainsKey(key) (Newtonsoft serializer) and constant strings in
Select(...) are fixed too.
Every earlier version of this package (1.1.0 through 1.5.0) allows Marten 8.37.3, and NuGet picks the lowest allowed version, so services get the vulnerable build unless they upgrade. 1.5.1 changes nothing else: Marten 8.37.4 is a patch release with the same API and the same dependency versions. Bump the package version and redeploy. No code changes are needed.
Advisory-lock ids (⚠️ read if upgrading from ≤ 1.4.0)
Postgres advisory locks are per-database, not per-schema — and Marten ships the same default lock ids for every store. On the Synergy shared databases that broke the async daemon's HotCold leader election platform-wide: every service asked for the same lock, ONE service won and ran its projections, and every other service's daemon idled forever ("another copy of me has it"). Writes landed in the event store, read models never updated — the "created X doesn't show in the list until a manual projection rebuild" bug.
1.5.0 fixes it as a governed default: the daemon lock id is derived from ServiceSchema with a
stable hash (FNV-1a — deliberately not string.GetHashCode(), which is per-process randomized and
would break failover between replicas). Each service gets its own lock automatically; replicas of
the same service still share one, so HotCold failover is unchanged. The schema-migration lock
(ApplyChangesLockId) is derived alongside it. Nothing to configure — upgrade the package and
restart.
o.DaemonLockId = 4001; // escape hatch only — e.g. two services forced onto one schema name;
// must then be unique per service sharing the database
The derivation assumes one schema = one service per database (the recommended model this package already enforces). Services still on ≤ 1.4.0 against a shared database keep racing for Marten's default lock — upgrade every cohabiting service.
Migrations (schema changes without runtime AutoCreate)
The package owns migration so no service hand-rolls it. The running app never creates schema
(AutoCreate.None is the default); schema is applied as a deliberate step, run as the
schema-owning database role.
1. Drift guard — fail fast instead of running on a half-migrated DB. Turn on in UAT/prod:
builder.Services.AddPlatformMarten(o =>
{
o.ConnectionString = builder.Configuration.GetConnectionString("Default")!;
o.ServiceSchema = "auth";
o.AssertSchemaOnStartup = !builder.Environment.IsDevelopment(); // boot throws if DB is behind the code
});
AssertSchemaOnStartup asserts the database matches the configuration on boot and throws if it
doesn't — it never creates or alters anything. This catches "the DB is missing the latest tables/
functions" before the service serves traffic, instead of a cryptic runtime failure.
2. Apply the schema — a migration job (run as the owning role). Resolve the store the package already registered and call the governed migrator:
using var host = Host.CreateApplicationBuilder(args).Build(); // wires AddPlatformMarten
var store = host.Services.GetRequiredService<IDocumentStore>();
await PlatformMartenMigrator.AssertMatchesAsync(store); // CI gate: throws on drift
await PlatformMartenMigrator.ApplyAsync(store); // apply CREATE/ALTER (as owning role)
await PlatformMartenMigrator.WriteCreationScriptAsync(store, "schema.sql"); // or dump SQL for DBA/Flyway
3. Or use the built-in CLI. Wire the service host once with
return await app.RunJasperFxCommands(args); and every service gets db-assert, db-apply,
db-dump <file>, db-patch <file>.
Ownership note (PostgreSQL): Marten upgrades replace its functions with
CREATE OR REPLACE FUNCTION, which requires being the owner of the object. Run migrations as the schema-owning role (or a superuser), or reassign ownership once:REASSIGN OWNED BY <old_role> TO <service_role>;+ALTER DEFAULT PRIVILEGES IN SCHEMA <svc> GRANT ALL ON TABLES, FUNCTIONS TO <service_role>;Otherwise you getERROR: must be owner of function ….
Feed (nuget.org — public)
Published to the public NuGet gallery (https://api.nuget.org/v3/index.json).
- Consuming (CI or local dev): no credentials needed — it's the default public feed. Just reference the leaf package; restore works everywhere with no token.
- Read is open to everyone; publishing is locked to the holder of the nuget.org API key (the Synergy organization) — see below.
Build & publish
dotnet build
dotnet pack Synergy.Platform.Marten.slnx -c Release -o artifacts
# publish (API key never committed — run by a maintainer or a CI secret):
dotnet nuget push "artifacts/*.nupkg" --source "https://api.nuget.org/v3/index.json" --api-key <NUGET_ORG_API_KEY> --skip-duplicate
Versions are immutable once pushed. Only the API-key holder can publish, so the version line moves only by a deliberate maintainer action.
Layout
src/Synergy.Platform.Marten.Core/ shared config + Marten pin + buildTransitive guard
src/Synergy.Platform.Marten.Core.Analyzers/ Roslyn analyzer (SYN001)
src/Synergy.Platform.Marten.Uuid/ uuid leaf (AddPlatformMarten -> uuid)
src/Synergy.Platform.Marten.NumericRevisions/ integer leaf (AddPlatformMarten -> numeric revisions)
templates/ Directory.Packages.props + nuget.config for consumers
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Synergy.Platform.Marten.Core (>= 1.6.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.