Found by the new EventStoreExplorerCompliance suite (JasperFx.Events.ComplianceTests, compliance wave 5 — marten#5146).
IEventStore.GetStreamMetadataAsync(streamId, ct) returns a StreamMetadata whose Tags property is null.
JasperFx.Descriptors.StreamMetadata declares it as a non-nullable positional parameter:
public sealed record StreamMetadata(
...
IReadOnlyDictionary<string, string> Tags);
So null is a contract violation rather than a legitimate "this stream has no tags" answer — an empty dictionary is how that is spelled. Any consumer that trusts the declaration and enumerates Tags gets a NullReferenceException instead of an empty loop. Marten returns a non-null dictionary here.
Repro
var metadata = await store.As<IEventStore>().GetStreamMetadataAsync(streamId.ToString(), ct);
metadata.Tags.ShouldNotBeNull(); // fails on Polecat
Current state
Gated in the compliance suite behind EventStoreComplianceFixture.SupportsStreamMetadataTags, which PolecatComplianceFixture overrides to false with a pointer to this issue. The gate is temporary and tracked — the assertion stays.
🤖 Generated with Claude Code
Found by the new
EventStoreExplorerCompliancesuite (JasperFx.Events.ComplianceTests, compliance wave 5 — marten#5146).IEventStore.GetStreamMetadataAsync(streamId, ct)returns aStreamMetadatawhoseTagsproperty is null.JasperFx.Descriptors.StreamMetadatadeclares it as a non-nullable positional parameter:So null is a contract violation rather than a legitimate "this stream has no tags" answer — an empty dictionary is how that is spelled. Any consumer that trusts the declaration and enumerates
Tagsgets aNullReferenceExceptioninstead of an empty loop. Marten returns a non-null dictionary here.Repro
Current state
Gated in the compliance suite behind
EventStoreComplianceFixture.SupportsStreamMetadataTags, whichPolecatComplianceFixtureoverrides tofalsewith a pointer to this issue. The gate is temporary and tracked — the assertion stays.🤖 Generated with Claude Code