platform/catalog: generic ProjectionStore[T] consumer + race-free holder; finish C1

- Generic SnapshotWriter[T]/SnapshotReader[T] over a value-agnostic raw layer;
  SnapshotHolder[T] now RWMutex-guards munmap vs in-flight Get (race-free swap,
  TestSnapshotHolder_SwapRace) — the drain grace is now just close-batching.
- ProjectionStore[T]: snapshot begin|chunk|end assembly → atomic swap, epoch-gated
  delta overlay, tombstones, atomic epoch sidecar (ordered before the swap),
  cold-start reopen, mandatory ProjectionTransform[T]. Future-epoch deltas are
  dropped by contract (recover via next snapshot), not buffered.
- Shared bus-framing constants so producer + consumer agree.
- Bump slask-finder / go-cart-actor / backoffice pointers; refresh status.md +
  catalog-projection.md to the finished state.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
matsandClaude Opus 4.8 committed 2026-06-28 21:07:39 +02:00
1 parent 5a3ffcb6bc
commit 8b454bccac
8 files changed
+959 -170

No files matched your search

+24
View File
@@ -148,3 +148,27 @@ func Decode(raw []byte) (Projection, error) {
err := json.Unmarshal(raw, &p)
return p, err
}
// ── Bus framing contract (shared by producer + consumer) ─────────────────────
//
// catalog.projection_published carries two channels distinguished only by
// event.Event.Meta. The producer (slask-finder writeradmin) sets these keys and
// the consumer (ProjectionStore, consumer.go) reads them — one definition so the
// two never drift. See docs/catalog-projection.md.
//
// - A FULL SNAPSHOT is a framed begin → chunk* → end stream, all tagged with one
// MetaEpoch. begin/end carry MetaCount (total projections) for completeness
// validation; chunks carry the projections.
// - A DELTA has NO MetaSnapshotPhase key, and MetaEpoch names the snapshot base
// it builds on (consumers ignore a delta older than the base they hold).
const (
MetaSnapshotPhase = "snapshot" // begin|chunk|end; absent ⇒ delta
MetaEpoch = "epoch" // int64 (Unix-ms), monotonic across rebuilds
MetaSeq = "seq" // chunk sequence within a snapshot
MetaCount = "count" // total projections (begin/end only)
MetaTenant = "tenant" // namespace tag
SnapshotBegin = "begin"
SnapshotChunk = "chunk"
SnapshotEnd = "end"
)