GES — Gap & edge-case review
Severity: Critical / High / Medium / Low. Disposition: Add (fold into spec), Defer (post-1.0), Non-goal (document as out of scope), Fixed (already corrected).
Found inconsistencies (mechanical)
Section titled “Found inconsistencies (mechanical)”- R1 [Fixed] §9.1.2 still referenced the pre-0.3 trailers
GES-Base-Position + GES-Count; corrected toGES-Positionin v0.11.0. Action for Claude Code: grep the spec for other stale references to the batch/GES-Countmodel that the one-event-per-commit switch (§5.2) obsoleted, and forrefs/ges/shadow/snapshots/streamsname consistency.
Semantics gaps
Section titled “Semantics gaps”- R2 [H · Add] Event schema evolution / upcasting. The spec versions the record format (
gesVersion) but not the payload schema of an eventtype. Over years,OrderPlacedv1 → v2 will happen; readers must upcast old events. There is no upcasting hook, no per-type schema version, no migration story. This is a core event-sourcing concern and its absence will bite. Recommend a per-event optionalschemaVersionand an upcaster stage inreadStream/aggregateStream. - R3 [C · Add] Interop test vectors. For a standard, two implementations must agree byte-for-byte on JCS, event id, and the integrity chain (
predecessorHash, §8.3). Without published test vectors (canonical input → expected JCS bytes → expectedsha256:id → expected integrity-chain hash), independent implementations will silently diverge and fail to interoperate — defeating the point of a shared spec. Recommend a normativetest-vectors/fixture set. - R4 [H · Add]
$all/ cross-stream subscription & projection idempotency. §16c covers per-stream cursors, but many read models need “all events across all streams” (Emmett/ESDB$all). The mandatory global stream (§20, C22) now provides this enumeration and its total order. Still unaddressed: catch-up idempotency — if the cursor advances but the read-model write fails and replays, events double-apply unless projections are idempotent or cursor+write is transactional. Recommend: define a documented$allenumeration, require idempotent projections, and specify cursor-advance-after-apply ordering. - R5 [H · Add] Snapshot invalidation on evolve change. A snapshot (§13) is a fold under a specific
evolveimplementation. Ifevolvechanges (bug fix, new field), existing snapshots are stale and silently wrong. Snapshots are not versioned by evolve/logic identity. Recommend alogicVersion/evolveHashfield on snapshots and a rule to ignore snapshots whose logic version differs.
Robustness / edge inputs
Section titled “Robustness / edge inputs”- R6 [M · Partly fixed] Empty/degenerate inputs. Zero-event append and non-existent stream reads are now specified (§10.5.2). Still open: maximum ref-component length vs. very long stream names (encode/hash fallback), very large single event blobs (perf/backpressure), and a stream consisting only of a snapshot with all events truncated.
- R7 [M · Add] Crash recovery & corruption. §11 explains atomicity (orphaned objects on crash) but there is no operational procedure: when to
git fsck, how to reclaim orphaned append objects, and what happens if a projection cursor or shadow ref points at a GC-pruned commit. Recommend averify/recovercommand spec and a rule that dangling cursors reset to the nearest reachable position. - R8 [M · Add] Clock skew / timestamp trust. Records carry wall-clock
timestamp(§6.2). Across clones/worktrees clocks skew; any projection ordering by timestamp across streams is unsound. The spec never states thattimestampis descriptive, not an ordering key. Recommend an explicit statement: order by position/ancestry, never bytimestamp.
Security / privacy
Section titled “Security / privacy”- R9 [M · Add] Crypto-shredding key management. §13.3 names crypto-shredding but not where keys live, how they are derived per subject, rotated, or destroyed — the hard part. Recommend a key-management appendix or an explicit “out of scope, implementer must define” note.
- R10 [M · Non-goal? · Add note] Confidentiality granularity. Repo read access = read every event on every readable ref; there is no per-stream/per-tenant/field-level access control. Multi-tenant deployments need this. Either document as a non-goal or specify repo-per-tenant topology.
- R11 [L · Add] Trailer spoofing. Denormalized trailers (
GES-Tag,GES-Position) are derived from event content; eventdatacontaining trailer-like text must never be interpreted as authoritative, and denormalization must sanitize. The spec says the record is authoritative (good) but should state the sanitize/never-parse-trailers-as-truth rule explicitly. - R12 [M · Add] Signature key lifecycle. §7.4/§15 recommend signing but omit rotation, revocation, and compromise handling (a compromised key means re-anchoring trust). Recommend a short key-lifecycle subsection.
Lifecycle / operations
Section titled “Lifecycle / operations”- R13 [M · Add] Bootstrapping & migration. No
ges init(namespace setup, configcore.fsync/fsyncMethod, ref backend choice) and no migration path from an existing event store beyond JSONL import. Recommend an init/migration section. - R14 [L · Defer] Observability. No metrics for projection lag, loose-object count, pack ratio, store growth. Useful operationally; defer to implementation.
- R15 [M · Add] Emmett wiring. Emmett’s event
kind: 'Event'is absent from the §6.2 record; and the decider/CommandHandlerintegration (howGitEventStoreplugs into Emmett command handling) is assumed but never shown. Recommend addingkindto the record and a short binding note.
Process / conformance
Section titled “Process / conformance”- R16 [M · Add] Conformance profiles. §16 mixes always-required MUSTs with “(if offered)” items but defines no named profiles (e.g. Core, Distributed, Agent-capture, DCB). Claude Code and adopters need to know the minimal conforming subset. Recommend explicit profiles.
- R17 [M · Add] Performance/capacity acceptance criteria. Despite extensive performance analysis, §16.1 has only functional tests — no throughput/latency/size thresholds. Recommend at least a capacity-measurement harness (append latency at L2, packed bytes/event, export throughput) even if thresholds stay deployment-defined.
- R18 [L · Defer] Spec-internal ADRs. The deliberately-abandoned axes (N1 sync HA, the substrate’s per-stream-only order before sequencing, precise cross-stream consistency without authority) are stated in-line but not collected as referenceable decisions. Recommend an ADR appendix (previously offered).
- R19 [M · Open decision] CloudEvents-compatible record. GES defines its own record schema (§6.2); the mature stores standardize on CloudEvents (EventSourcingDB explicitly). Adopting a CloudEvents-compatible envelope (
specversion/id/subject/type/data, plustraceparent/tracestate) would buy interoperability and familiarity at the cost of extra fields. Captured in the §21 register and §19 open questions; resolve against a running implementation, not on paper. - R20 [L · Open decision] Ad-hoc query layer. GES serves ad-hoc queries only by native scan (§16b); EventSourcingDB offers EventQL (cross-subject queries, NDJSON, for DCB). Whether GES should define a richer query layer beyond scan + projections is open (§21, §19).
- R21 [process] Alignment register. §21 now maps GES to the cross-store consensus (EventStoreDB, EventSourcingDB, Marten, Message DB, Emmett) and records each deviation with justification. This is the standing mechanism for the “follow best practice, deviate only with reason” principle; it MUST be updated whenever a decision changes its relationship to the consensus.
- R22 [process] Ubiquitous-language table. §2.1 now disambiguates each borrowed term (commit, write, append, stream, stream/global position, version, chain, index, snapshot, log, canonical, aggregate, tag, leader/sequencer/authority, origin, writer/appender, decider, ref, projection, merge, id, shared) by linguistic context (bounded context) and pins GES’s meaning. This resolves the recurring cross-context ambiguity (notably “commit”: Git object vs. PostgreSQL transaction point vs. GES event) and, per DDD, a change to a term there is a change to the model — so §2.1 MUST be kept current with any terminology change, in both documents.
- R23 [process] Core / framework-binding separation. GES is standalone infrastructure (§1): a framework-neutral, stream-based core (§10.0) with Emmett as one reference binding (§10.1), not the definition. Conformance (§16.1) is framework-independent; the Emmett binding is an optional profile. DCB (dcb.events) is an optional additional interface (§18c), a different (stream-less) model — not the core vocabulary, and not reachable through Emmett (Emmett has no DCB). DCB terminology applies only inside that layer. The tag notion does not carry across models (DCB tag ≠ Git tag ≠ Emmett
data/metadatafield) — the earlier over-unification of these is the specific error this note guards against.
Highest-leverage before implementation: R3 (interop vectors) and R2/R5 (schema & snapshot versioning) are the ones whose absence causes silent, hard-to-debug divergence; R4 (projection idempotency) is the one most likely to cause a real bug in the first read model. R19 (CloudEvents) is the highest-value alignment decision — worth prototyping both envelopes early.
Git Event Store is an independent open specification for an event store that uses Git as its storage component. It is not affiliated with or endorsed by the Git Project or Software Freedom Conservancy. Git and the Git logo are either registered trademarks or trademarks of Software Freedom Conservancy, Inc., corporate home of the Git Project, in the United States and/or other countries.
Git Event Store is not affiliated with or endorsed by Kurrent, the maker of KurrentDB (formerly EventStoreDB). "Event store" is used here as the generic name of the event-sourcing storage pattern.