Skip to content

Read-model collections and history

Use reader.GetAll(ctx, count) for a complete collection and reader.GetSnapshots(ctx, key) for the states a projection passed through. Both use a registered readmodels.Model[T] and its frozen serialization plan. For paged sink queries or live updates, use materialized windows and watches.

reader := readmodels.For(store.ReadModels(), model)
// Default retrieval: materialized state, or an on-demand passive model.
current, err := reader.GetAll(ctx, nil)
if err != nil {
return err
}
for _, instance := range current.Instances {
fmt.Println(instance.Value)
}
// Replay/fold the first 100 matching events across all sources.
count := events.Count(100)
historical, err := reader.GetAll(ctx, &count)
if err != nil {
return err
}
fmt.Println(historical.ProcessedEventsCount)

This excerpt assumes a connected store, its registered model, a caller ctx, and an enclosing function returning error.

The raw equivalent is store.ReadModels().GetAll(ctx, model.Identifier(), count). Both return Collection[T] (raw: Collection[json.RawMessage]):

  • Instances is an owned, non-nil slice on success. Every entry is present. Deleted partitions are omitted; a present zero-valued model remains.
  • ProcessedEventsCount is the actual reported or locally folded count. Materialized retrieval reports zero, even with nonempty results.
  • Each instance’s LastHandled comes from reported progress, never count - 1. Missing/unavailable progress is nil; reserved positions are protocol errors.

events.Count is a distinct domain from events.SequenceNumber.

CountProjectionReducer
nilMaterialized sink, or replay if passiveMaterialized sink, or local fold if passive
0Empty, no RPCEmpty, no RPC or reducer activation
1..math.MaxInt32Kernel replayLocal fold, including active reducers
events.UnlimitedCountMaterialized sink, or replay if passiveLocal fold, including active reducers
Other valuesErrInvalidConfigurationErrInvalidConfiguration

Unlimited is math.MaxUint64, not zero. Keep it explicit when you want an active reducer folded locally instead of reading its sink. Finite counts are rejected above the limit, not clamped as in the C# local-fold implementation.

Local folds sort matching history, select the first N events globally, then fold each source in first-seen order. Sparse sequence positions do not change that bound. Source/type filtering follows C# historical reads; active observer metadata filters do not narrow the query. Generation-aware decoding, caller identity, scoped activation and cleanup use the production reducer plan. Query folds emit no local change notifications or observer acknowledgments.

The history RPC is unpaged and has no count field. A local count bounds folding, not the amount fetched or retained in memory. A missing local reducer plan fails with ErrUnsupported; the SDK does not delegate the fold to a connected client.

reader.GetSnapshots(ctx, key) returns []readmodels.Snapshot[T]. The raw equivalent is store.ReadModels().GetSnapshots(ctx, model.Identifier(), key). Keys must be nonblank and cannot be *.

Each snapshot contains:

  • Instance T: state after applying that correlation group.
  • Events []events.Appended: raw, server-released contributing events with their type/generation, source, stream, position, occurrence, correlation, causation, actor chain, tags, named tags, hash, observation state and compliance subject. Store, namespace and sequence are the actual request coordinates.
  • Occurred: the first contribution’s occurrence, not transaction completion.
  • CorrelationID: the group’s correlation, not the response envelope correlation.

The kernel groups correlations globally. A/B/A becomes an A group containing the first and third events, followed by B. It is not three contiguous snapshots or a transaction timeline. Returned group order is preserved.

Use events.Decode[E](store.EventTypes(), snapshot.Events[i]) to decode a known contribution. Unknown Go event types remain raw. This RPC carries no event ID, original content, revisions or alternate-generation payloads; those fields stay absent. Do not assume it offers the representations available from sequence reads.

Snapshot.Instance deliberately has no Exists or LastHandled. Kernel 19.29.4 serializes a removed root as {}, which is indistinguishable from present empty state. Empty history is a non-nil empty slice, not fabricated presence evidence.

Collections and snapshots are partial kernel replay capabilities:

  • Reducer snapshots return ErrUnsupported before I/O. Kernel 19.29.4 returns an empty list for every non-projection; Go refuses to present that as absence.
  • Projection collection replay/history requires a locally known producer with empty initial state and source-ID root keys. Nonempty defaults, custom keys, joins, joined removal, children and nested projections are refused. The SDK neither evaluates a local projection nor overlays defaults onto replay results.
  • ALL subscriptions are supported for the same simple source-ID shape, alone or mixed with explicit event mappings. Mixed shapes need kernel 19.32.1 or later (Chronicle#4562): earlier kernels filter replay and history to the explicit IDs and omit other events the projection handles live, so the SDK refuses mixed shapes with ErrUnsupported before any RPC unless the connected kernel’s verified version is 19.32.1 or later.
  • Catalog-only remote projections remain readable from their materialized sink, but replay/history fails without producer fidelity evidence. Low-level adapter constructors can supply WithProjectionReplayValidator; the adapter owns that evidence, not a heuristic based on the model schema.

Release ownership is route-specific, not a general promise that every returned string is plaintext. The SDK applies this policy on every supported kernel:

RouteProtected-model policy
Materialized collection or keyed GetKernel releases using persisted subject lineage; SDK validates without another release RPC
Local reducer foldFold already released event history, then validate the result
Bounded/passive projection collection or legacy ReplayProjectionKernel releases each value with the subject it was written under; SDK validates without another release RPC
Projection snapshot historySame as projection replay
Immediate/passive projection Get or hydration sessionSame as projection replay
Explicit Service.ReleaseAccepts unreleased sink documents; releases by schema/subject-lineage groups

Projection replay routes need kernel 19.32.2 or later (Chronicle#4561). From that release the kernel keeps the subject each protected value was written under through projection folds, so a value whose subject differs from the read-model key releases correctly, for default Id and lowercase id alike, and erasing the subject empties it. This covers PII and subject, namespace and global encryption, including nested/reference schemas and provider classifications. TestKernelModelHistoryProtectedReleaseProfile exercises every route above against the pinned kernel before and after erasure. Earlier kernels released replayed values with the source key instead. The SDK therefore refuses classified models on these routes with ErrUnsupported, before any RPC, unless the connected kernel’s verified version is 19.32.2 or later; a connection made with WithSkipCompatibilityCheck is refused too. See the minimum kernel versions.

Snapshot contributions have a separate release owner: the kernel releases each event with its generation’s schema and event subject before projecting. Unclassified model history can therefore return protected event contributions. Store services validate known protected contribution generations through their event codecs; low-level adapters supply WithSnapshotEventCatalog for that check. Ordinary unknown event fields remain raw. Never call Release on server-released results: legitimate plaintext can resemble a cipher block and a second release can corrupt it.

All authorization/validation/exception envelopes are checked. A late RPC, schema, codec, reducer, cleanup or cancellation error discards the complete result, including progress and contribution metadata. Read-error messages omit payloads; underlying causes remain inspectable with errors.Is/errors.As and can themselves contain sensitive diagnostics. Duplicate JSON property names are rejected recursively, including in unknown fields and arrays, before validation or raw/typed publication. Ordinary unknown properties are preserved. Application decoder panics become payload-free CodecPanicError failures without retaining the panic value; ordinary decoder causes remain inspectable. Codecs run outside counted transport leases, so they can close their client without self-deadlock.

These reads allocate no hydration sessions. Counts and snapshot metadata prove neither observer readiness nor catch-up, durable replay completion, authorization of a later write, or a protected decision.