Epic: per-project event sequencing, open-at-head queries, reliable archiving - #35
Merged
Conversation
The handler answering the project-events pull query implemented the plain CommandHandler type, so any logged-in user could fetch the full change history of any project just by supplying its id. The WebSocket subscribe path already required the ViewProject capability; the pull path now goes through AuthorizedCommandHandler with the same capability on the same project resource, so the two paths cannot disagree. A null project id is rejected as a bad request instead of blowing up inside the authorization wrapper, and a contract test pins the resource and capability so a refactor cannot silently drop the check. The webprotege-authorization dependency is pinned to 2.0.1 directly: Maven mediation previously resolved 0.9.3 (via backend-api), which lacks the Capability classes the authorized handler needs. The unused BuiltInAction enum was built on 0.9.3's removed ActionId API and is deleted with the bump. Fixes the service half of protegeproject/webprotege-gwt-ui#295 (part of protegeproject/webprotege-gwt-ui#303).
The archive numbered events from a single global counter incremented with a read-then-write under a JVM-local lock: not per project, and not safe with more than one service instance. Each project now has its own counter document advanced with a single findAndModify $inc, which MongoDB runs atomically server-side, so ordinals stay dense and gapless per project under concurrency. A startup migration seeds each project's counter to the maximum ordinal already archived, using setOnInsert so it is idempotent and never rewinds a counter that has advanced — newly minted numbers always stay above any bookmark a connected client still holds. After the archive write, the service now re-publishes the change bundle as a SequencedPackagedProjectChangeEvent carrying the assigned ordinal. The gateway will push that instead of the raw event, which is what lets it stamp truthful event tags — and because publication happens strictly after persistence, anything a client hears about is already fetchable through the pull path. Delivery hardening of this publish is #299's scope; the failure is not silently swallowed here. A compound index on (projectId, timeStamp) backs the pull query, and a read-only current-sequence accessor is exposed for the project-open anchor (#301). Fixes the service half of protegeproject/webprotege-gwt-ui#296 (part of protegeproject/webprotege-gwt-ui#303).
…s window The events query gains a latestOnly mode: instead of returning archived events it answers with an empty window positioned at the project's current head, read from the per-project sequence counter. A client opening a project uses it to start listening for live changes without downloading the past. The normal query is no longer unbounded: results come back ordered by sequence with a configurable cap (default 500), and the response's end tag marks the end of the returned window, so a far-behind client pages forward naturally over successive requests instead of receiving the whole archive in one response. The compound index from the sequence work backs the ordered query. The service half of protegeproject/webprotege-gwt-ui#301 (part of protegeproject/webprotege-gwt-ui#303).
A failed archive write was caught, logged, and acknowledged, so the event vanished from the catch-up history forever — exactly the store that reconnecting clients rely on to fill their gaps. The failure now propagates so the listener container redelivers the message, and a bounded retry policy with exponential backoff (tunable via webprotege.events.retry.*) turns transient store hiccups into recovered writes while stopping a genuinely poison message from hot-looping — after the attempts are exhausted it is rejected without requeue. Redelivery cannot duplicate archive rows because the event id is the Mongo document id, so a replayed save upserts; an integration test pins that. The listener container is built inside the ipc library, so the retry policy is attached through a bean post-processor. A retried attempt that already minted a sequence ordinal leaves a benign gap in the numbering; ordinals are opaque and monotonic to clients, so nothing waits on the missing value. The event-history companion to protegeproject/webprotege-gwt-ui#299 (part of protegeproject/webprotege-gwt-ui#303).
johardi
marked this pull request as ready for review
July 27, 2026 21:59
This was referenced Jul 27, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This service's share of protegeproject/webprotege-gwt-ui#303 — four atomic commits, landed via #32, #33, #34.
What this adds: the events history query now requires view permission (it previously answered anyone); events get per-project, atomic, gapless sequence numbers assigned at persist time (replacing a global JVM-locked counter) with an idempotent seed migration; a post-persistence sequenced event feeds the gateway's push so anything pushed is already fetchable; the query gains a latest-position anchor mode (project open no longer replays the whole archive) and a bounded, indexed result window; and a failed archive write is no longer acknowledged — it retries with bounded backoff and dedupes on redelivery by event id.
Verified: 21/21 module tests (Testcontainers Mongo + Rabbit) and the 85-test full-stack suite on the epic images. See the gateway epic PR for the before/after performance table.