Subscription that broadcasts cache invalidation events to connected clients, so cross-server
cache invalidation reaches browsers.
Delivery is filtered only by ENTITY-level read permission (see cacheInvalidationFilter).
Every session that may read an entity at all still receives every event for it, from every
other session and tenant.
Because that narrowing stops well short of row level, RecordData is only populated for entities a
deployment has opted in through cacheSettings.recordDataBroadcastEntities — see
MayBroadcastRecordData, applied at both publish sites.
EntityName, PrimaryKeyValues, Action and Timestamp ARE still broadcast to every
session, and that is a disclosure, not a safe residue. An earlier version of this comment
called them "safe to broadcast" on the grounds that reading the record still goes through
the access-controlled path — true of the row's CONTENTS, and beside the point for the
metadata itself. A session with no relationship to a record still learns that it exists,
its stable key, and every time it changes; across entities it learns the cadence and volume
of another tenant's activity.
The key is kept for a subscriber who could read the row anyway, because consumers need it to
re-read the record they were told about (see ResolveEntityEventKey in @memberjunction/core),
and the coarse "something in this entity changed" forces whole-entity invalidation on the
hottest write paths. It is withheld, with the row, from a subscriber whose every read of the
entity goes through a row-level security filter (ReadIsRowFiltered): a filter the
subscription cannot evaluate per event stands between them and the row, so they learn only
that a row of the entity changed, and their caches refresh the entity as a whole. That is the
same in-memory role match as the entity-level filter, once more per subscriber per event.
Subscription that broadcasts cache invalidation events to connected clients, so cross-server cache invalidation reaches browsers.
Delivery is filtered only by ENTITY-level read permission (see cacheInvalidationFilter). Every session that may read an entity at all still receives every event for it, from every other session and tenant.
Because that narrowing stops well short of row level,
RecordDatais only populated for entities a deployment has opted in throughcacheSettings.recordDataBroadcastEntities— see MayBroadcastRecordData, applied at both publish sites.EntityName,PrimaryKeyValues,ActionandTimestampARE still broadcast to every session, and that is a disclosure, not a safe residue. An earlier version of this comment called them "safe to broadcast" on the grounds that reading the record still goes through the access-controlled path — true of the row's CONTENTS, and beside the point for the metadata itself. A session with no relationship to a record still learns that it exists, its stable key, and every time it changes; across entities it learns the cadence and volume of another tenant's activity.The key is kept for a subscriber who could read the row anyway, because consumers need it to re-read the record they were told about (see
ResolveEntityEventKeyin @memberjunction/core), and the coarse "something in this entity changed" forces whole-entity invalidation on the hottest write paths. It is withheld, with the row, from a subscriber whose every read of the entity goes through a row-level security filter (ReadIsRowFiltered): a filter the subscription cannot evaluate per event stands between them and the row, so they learn only that a row of the entity changed, and their caches refresh the entity as a whole. That is the same in-memory role match as the entity-level filter, once more per subscriber per event.