Release v20.39.1
Summary
The Chronicle read model interceptor (PII release) is registered for every read model, so QueryPipeline.ApplyInterceptors ran it against observable (ISubject<T>) and async-enumerable query results. It handed the streaming wrapper to IInterceptReadModel<TReadModel>.Intercept(TReadModel), throwing Object of type 'LifetimeAwareSubject<…>' cannot be converted to type '<ReadModel>' and breaking every observable model-bound query served over the multiplexed SSE/WebSocket hub. Streaming interception now lives behind a single helper shared by all streaming transports, so compliance/PII release runs per emission — including for collection queries, which previously skipped it.
Fixed
- Observable (
ISubject<T>) andIAsyncEnumerable<T>model-bound query results no longer crash under read model interception.QueryPipeline.ApplyInterceptorsnow leaves streaming results untouched for the streaming transport to intercept per emission. - Collection observable queries (
ISubject<IEnumerable<TReadModel>>) now have compliance/PII release applied to each item. The streaming transports previously keyed interception on the enumerable type, found no matching interceptor, and silently served undecrypted values.
Changed
- All streaming transports (
ObservableQueryDemultiplexer,ClientObservable,ClientObservableSSE,ClientEnumerableObservable,ClientEnumerableObservableSSE) now share a singleIReadModelInterceptors.InterceptEmissionhelper that intercepts each emission, unwrapping collection emissions to the read model element type.
🤖 Generated with Claude Code