Skip to content

Polecat#119: foundation re-pin + adopt lifted *ProjectionDistributor concretes + cut 4.0.0-alpha.9#120

Merged
jeremydmiller merged 3 commits into
mainfrom
feature/119-lift-distributor-concretes
May 19, 2026
Merged

Polecat#119: foundation re-pin + adopt lifted *ProjectionDistributor concretes + cut 4.0.0-alpha.9#120
jeremydmiller merged 3 commits into
mainfrom
feature/119-lift-distributor-concretes

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Summary

Closes #119. Polecat#119 chip executed: deletes Polecat's three near-identical *ProjectionDistributor concretes in favour of the lifted versions now shipping in JasperFx.Events.Daemon, and lands the foundation re-pin needed to pick them up. Part of the Critter Stack 2026 dedupe pillar; follows on from PR #118 (interface-level lift, polecat#117), weasel#285 (Weasel.Core.IAdvisoryLock dedupe), and jasperfx PRs #318/#319/#320 (concrete lifts).

Three commits per chip shape: −255 / +59 across 6 files net.

Foundation matrix

Package From To Note
JasperFx 2.0.0-alpha.17 2.0.0-alpha.20 Phase 1
JasperFx.Events 2.0.0-alpha.16 2.0.0-alpha.20 Phase 1 → α.19, Phase 2 → α.20 (α.19 only shipped Solo + MultiTenanted; SingleTenant was orphaned per jasperfx#322, re-landed for α.20)
JasperFx.Events.SourceGenerator 2.0.0-alpha.9 2.0.0-alpha.12 Phase 1
JasperFx.RuntimeCompiler 5.0.0-alpha.5 5.0.0-alpha.8 Phase 1, 5.x line confirmed
JasperFx.SourceGeneration 2.0.0-alpha.6 2.0.0-alpha.9 Phase 1
Weasel.SqlServer 9.0.0-alpha.6 9.0.0-alpha.7 Phase 2 (picks up weasel#284AdvisoryLock implements lifted JFx.Events.Daemon.IAdvisoryLock directly)
Weasel.EntityFrameworkCore 9.0.0-alpha.6 9.0.0-alpha.7 Phase 2
Polecat 4.0.0-alpha.8 4.0.0-alpha.9 Phase 3

What the dedupe replaced

ProjectionCoordinator.BuildDistributor() now constructs the lifted concretes with store-specific closures rather than instantiating Polecat-local copies:

Func<IEnumerable<ShardName>> allShards =
    () => _options.Projections.AllShards().Select(s => s.Name);

Func<IProjectionDatabase, IReadOnlyList<ShardName>, int, IProjectionSet> setFactory =
    (db, names, lockId) => new ProjectionSet(lockId, (PolecatDatabase)db, names);

var lockLogger = _loggerFactory.CreateLogger<AdvisoryLock>();
Func<IProjectionDatabase, IAdvisoryLock> lockFactory =
    db => new AdvisoryLock(
        () => new SqlConnection(((PolecatDatabase)db).ConnectionString),
        lockLogger,
        db.Identifier);

if (cardinality == DatabaseCardinality.StaticMultiple)
{
    return new MultiTenantedProjectionDistributor(
        databaseSource: () => ValueTask.FromResult<IReadOnlyList<IProjectionDatabase>>(
            _options.Tenancy?.AllDatabases().Cast<IProjectionDatabase>().ToList() ?? []),
        allShards: allShards,
        lockFactory: lockFactory,
        setFactory: setFactory,
        baseLockId: baseLockId);
}

return new SingleTenantProjectionDistributor(
    databaseAccessor: () => _options.Tenancy!.AllDatabases().Single(),
    allShards: allShards,
    lockFactory: lockFactory,
    setFactory: setFactory,
    schemaQualifier: _options.DatabaseSchemaName,
    baseLockId: baseLockId);

Files deleted (235 lines)

  • src/Polecat/Events/Daemon/Coordination/SoloProjectionDistributor.cs
  • src/Polecat/Events/Daemon/Coordination/SingleTenantProjectionDistributor.cs
  • src/Polecat/Events/Daemon/Coordination/MultiTenantedProjectionDistributor.cs

Files retained

Behavioural tightening

The previous Polecat-local SingleTenantProjectionDistributor iterated AllDatabases() with SelectMany even in single-database tenancy — which would have happily produced sets for an unintended multi-database deployment. The lifted concrete takes a Func<IProjectionDatabase> (single, not list); the Polecat closure uses .Single() so a misconfigured deployment now surfaces with a useful exception rather than silently no-opping.

Verification

Polecat.Tests.Daemon (net10.0):   53 pass / 3 pre-existing skips / 0 fail   (matches post-PR-#118 baseline)
Polecat.AotSmoke    (net10.0):    builds + runs clean — "Polecat AOT smoke OK."
dotnet build         -c Release:   0 errors, 14 pre-existing IL warnings (unchanged)
dotnet build (full solution Debug): 0 errors

Commit shape

Three commits per chip:

  1. 8c6ec85Polecat: re-pin to JasperFx alpha.20 / Events alpha.19 / SG alpha.12 / RC 5.0-alpha.8 / SG alpha.9 (Phase 1, foundation re-pin)
  2. 7057bf0Polecat#119: adopt lifted *ProjectionDistributor concretes from JasperFx.Events (Phase 2 — folds the JFx.Events α19→α20 + Weasel α6→α7 unblocker bumps because they're mechanically inseparable from the concrete adoption)
  3. ff7e15ePolecat: cut 4.0.0-alpha.9 (Phase 3)

Backstory

This chip surfaced two blockers along the way, both upstream-fixed before this PR could finish:

  • weasel#284Weasel.SqlServer.AdvisoryLock implemented a separate Weasel.Core.IAdvisoryLock, byte-identical to the lifted JFx one but not actually assignable to it. Fixed in weasel#285 (option 3 — Weasel.Core.IAdvisoryLock deleted, both providers route at the lifted JFx interface directly) and shipped in Weasel.SqlServer 9.0.0-alpha.7.
  • jasperfx#322SingleTenantProjectionDistributor was missing from JasperFx.Events α.19 because PR #320 had merged into a deleted feature branch instead of main. Re-landed for α.20.

Both blockers are linked from the test plan boxes below so the cross-repo dependency chain is auditable.

Test plan

  • weasel#284 closed by weasel#285Weasel.SqlServer.AdvisoryLock implements lifted JFx.Events.Daemon.IAdvisoryLock (shipped in Weasel.SqlServer 9.0.0-alpha.7)
  • jasperfx#322 re-landed — SingleTenantProjectionDistributor shipped in JasperFx.Events 2.0.0-alpha.20
  • CI green on feature/119-lift-distributor-concretes
  • No regression in async daemon hot-cold lease handover (async_daemon_tests)
  • No regression in multi-tenant daemon lock-per-database semantics (multi_tenant_daemon_tests)
  • Polecat.AotSmoke still builds + runs

Per Polecat#46 master plan.

🤖 Generated with Claude Code

jeremydmiller and others added 3 commits May 19, 2026 16:22
…/ RC 5.0-alpha.8 / SG alpha.9

Foundation re-pin per Polecat#119 chip. Picks up the newly-shipped
JasperFx.Events alpha.19 surface that includes the lifted *ProjectionDistributor
concretes (jasperfx#318/#319/#320 closing #315/#316/#317) which the next
commit adopts. Also picks up the lifted JasperFx.Events.Daemon.IAdvisoryLock
(jasperfx#319) — creates a one-line ambiguity at the two Polecat distributor
sites that already import JFx.Events.Daemon for IProjectionDistributor /
IProjectionSet (PR #118). Disambiguates by fully-qualifying the existing
references to Weasel.Core.IAdvisoryLock; those files are deleted entirely in
the next commit so the qualification is a transient compile-fix, not a new
contract.

Bumps:

| Package                          | From          | To             |
|----------------------------------|---------------|----------------|
| JasperFx                         | 2.0.0-alpha.17| 2.0.0-alpha.20 |
| JasperFx.Events                  | 2.0.0-alpha.16| 2.0.0-alpha.19 |
| JasperFx.Events.SourceGenerator  | 2.0.0-alpha.9 | 2.0.0-alpha.12 |
| JasperFx.RuntimeCompiler         | 5.0.0-alpha.5 | 5.0.0-alpha.8  |
| JasperFx.SourceGeneration        | 2.0.0-alpha.6 | 2.0.0-alpha.9  |

Weasel.* stays at 9.0.0-alpha.6 — no newer cut shipped. RC pin is on the
active 5.x line (the parallel 2.0.x series is stale; matches the precedent
fix in Marten commit 44dc48425).

`dotnet build -c Release` clean against the new matrix (14 pre-existing
IL warnings in PolecatProjectionBatch / DocumentMapping / StreamCompacting,
unchanged from the alpha.17 baseline).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rFx.Events

Phase 2 of the Polecat#119 chip — Critter Stack 2026 dedupe pillar. The
*ProjectionDistributor concretes themselves were lifted into
JasperFx.Events.Daemon in jasperfx#318 / #319 / #320:

  - SoloProjectionDistributor              (jasperfx#315)
  - MultiTenantedProjectionDistributor     (jasperfx#316, also lifted IAdvisoryLock)
  - SingleTenantProjectionDistributor      (jasperfx#317)

JasperFx.Events 2.0.0-alpha.20 is the first cut that ships all three (alpha.19
shipped Solo + MultiTenanted only; SingleTenant was orphaned per
jasperfx#322 and re-landed for alpha.20). Polecat now wires the lifted
concretes from ProjectionCoordinator.BuildDistributor() with store-specific
closures and deletes its own near-identical implementations.

Foundation bumps required to land Phase 2:

  - JasperFx.Events     alpha.19 → alpha.20  (gets the SingleTenant concrete)
  - Weasel.SqlServer    alpha.6  → alpha.7   (closes weasel#284 — Weasel.SqlServer.AdvisoryLock
  - Weasel.EFCore       alpha.6  → alpha.7    now implements lifted JFx IAdvisoryLock directly)

Phase 1 (PR-internal commit 8c6ec85) re-pinned the rest of the JasperFx
alpha matrix and is unchanged here.

ProjectionCoordinator.BuildDistributor() closures:

  - allShards:    () => _options.Projections.AllShards().Select(s => s.Name)
  - setFactory:   (db, names, lockId) => new ProjectionSet(lockId, (PolecatDatabase)db, names)
                  Polecat's IProjectionSet implementation is retained per #117 / PR #118.
  - lockFactory:  db => new Weasel.SqlServer.AdvisoryLock(
                      () => new SqlConnection(((PolecatDatabase)db).ConnectionString),
                      lockLogger, db.Identifier)
                  Single closure for both multi-node distributors; Weasel α7's
                  Weasel.SqlServer.AdvisoryLock satisfies the lifted
                  JasperFx.Events.Daemon.IAdvisoryLock contract directly.

The single-tenant branch resolves the database accessor as
`_options.Tenancy!.AllDatabases().Single()` — Polecat's Single cardinality
invariant says exactly one database; .Single() surfaces misconfiguration as a
useful exception rather than silently no-opping. This also tightens the
behaviour vs the prior Polecat-local SingleTenantProjectionDistributor, which
iterated AllDatabases() with SelectMany and would have happily produced sets
for an unintended multi-database deployment.

Deleted (235 lines):

  - src/Polecat/Events/Daemon/Coordination/SoloProjectionDistributor.cs
  - src/Polecat/Events/Daemon/Coordination/SingleTenantProjectionDistributor.cs
  - src/Polecat/Events/Daemon/Coordination/MultiTenantedProjectionDistributor.cs

Retained (per Polecat#117):

  - src/Polecat/Events/Daemon/Coordination/ProjectionSet.cs — implements the
    lifted IProjectionSet with both typed PolecatDatabase access (for distributor
    call sites needing the connection string) and explicit IProjectionDatabase
    access via the interface.

Validated:

  Polecat.Tests.Daemon (net10.0):  53 pass / 3 pre-existing skips / 0 fail
                                   (matches the post-PR-#118 baseline exactly)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ships the Polecat#119 distributor-concrete dedupe (Critter Stack 2026 dedupe
pillar) plus the JFx.Events alpha.20 / Weasel alpha.7 foundation re-pin
landed in the two preceding commits on this branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit afcfa60 into main May 19, 2026
7 checks passed
@jeremydmiller jeremydmiller mentioned this pull request May 20, 2026
19 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adopt lifted *ProjectionDistributor concretes from JasperFx.Events

1 participant