Skip to content

fix(indexing): hand OpenSearch reindexing to the queue from the JDO transaction's completion callback - #1752

Merged
JasonWildMe merged 16 commits into
mainfrom
fix/indexing-post-commit-sync
Oct 27, 2026
Merged

JasonWildMe merged 16 commits into
mainfrom
fix/indexing-post-commit-sync

Conversation

@JasonWildMe

@JasonWildMe JasonWildMe commented Sep 5, 2026 •

Copy link
Copy Markdown
Collaborator

Re-derivation of #1743 on the JDO transaction's own completion callback, as recommended by the Codex review of that PR. #1743 stays open as the problem record until this lands.

Closes #1696. That report (GiraffeSpotter, React encounter search) is this bug seen from the search page: the Identity filter "Include only encounters with no assigned Individual ID" listed encounters that already had an ID, because the encounter document's individualId was indexed from the pre-commit row on the match-results Confirm path (iaResultsSetID.jsp). That path never bumps Encounter.modified, so the background reconciler could not repair it and the stale rows persisted. Closes #1548.

The bug

WildbookLifecycleListener.postStore() fires on JDO flush, not commit. It enqueued the OpenSearch reindex there, and the indexing job opens its own Shepherd (its own connection) and reloads the object by id. For a row that already exists that reload succeeds and returns pre-commit state, so the job indexed the old values and reported success. Only a brand-new row raised JDOObjectNotFoundException, which is the one case the retry path handled. The queue's dedupe (if (indexingQueue.contains(id)) return;) then silently dropped the correct post-commit request whenever the pre-commit job was still in flight.

Search index stale, no data loss: Postgres is always right. Affected surfaces are the OpenSearch-backed ones, including the annotation index that feeds the match candidate pool.

What changed

The park. postStore, Encounter.enqueueAclReindex() and MarkedIndividual.enqueueAclReindex() no longer enqueue. They park (id, class, unindex, DataNucleus identity) in the writing PersistenceManager's user-object map. A transient object, or a caller that has already committed, falls through to an immediate enqueue exactly as before.

The release. A javax.transaction.Synchronization (IndexingTransactionSynchronization) is registered once per PM via Transaction.setSynchronization. DataNucleus calls its afterCompletion(status) from inside commit()/rollback() after its own post-commit work. That callback is the only thing that terminalizes a park: STATUS_COMMITTED drains it (evict from L2 by DataNucleus identity, then enqueue by identity), STATUS_ROLLEDBACK drops it, anything else runs index requests (the job re-reads the DB, so that is harmless) and never an ambiguous unindex. Because it hangs off the transaction, a raw pm.currentTransaction().commit() is covered identically.

What that removes. No edits to Shepherd.commitDBTransaction(), commitDBTransactionWithStatus(), updateDBTransaction(), rollbackDBTransaction(), closeDBTransaction() or rollbackAndClose(). No lock around Shepherd's lifecycle. The only Shepherd change is beginDBTransaction(): for a fresh PM it registers the listener and the Synchronization once (main re-added a WildbookLifecycleListener on every begin, so one store event fanned out to N callbacks) and installs the park before Transaction.begin().

The dedupe. A second request for an in-flight id is remembered and runs as exactly one follow-up pass when the job reaches any terminal outcome (success, failure, retry give-up), instead of being dropped.

The reader. The indexing job's PM sets datanucleus.cache.level2.retrieveMode=bypass for its primary lookup. See the boundary below for what that does and does not cover.

Reader setup cleanup. If the indexing reader's transaction initialization throws, ShepherdIndexingWork rolls back and closes its allocated Shepherd before rethrowing the same exception. Regression coverage verifies that the PersistenceManager closes and its Shepherd diagnostic entry is removed. A test through the public Shepherd.beginDBTransaction() method also verifies that repeated begins on the same Shepherd register the lifecycle listener and transaction synchronization only once.

Why this is safe where #1743 was not

  • "Commit threw" no longer terminalizes anything. DataNucleus deliberately leaves the transaction active after a NucleusUserException so the caller can fix and retry. fix(indexing): hand OpenSearch reindexing to the queue after commit, not on JDO flush #1743 drained on that path, marking the park terminal while the transaction was still open, after which every later store in that transaction enqueued pre-commit again. Here the park stays live until the transaction actually completes. Tested against real DataNucleus with a one-shot vetoing Synchronization.
  • L2 eviction actually evicts. The L2 cache is keyed by DataNucleus's internal identity; JDOHelper.getObjectId() returns a javax.jdo.identity.* object, and evicting with it silently misses. This uses Persistable.dnGetObjectId(), asserted against the real Level2Cache.
  • Completion survives failures. The successor park is installed before any external work; each evict+enqueue is isolated; a null IndexingManager is logged, not thrown.
  • A wrapped Synchronization cannot lose the park. Capture and successor installation happen before the delegate runs; the delegate may close the PM or throw.

Boundaries (stated on purpose)

  • L2 read isolation is partial, and that is a separate problem. DataNucleus publishes an object's new state into the shared L2 cache during preCommit, before the datastore commit, so any reader in Wildbook can briefly see another in-flight transaction's uncommitted copy. That predates this PR and affects web requests as much as the indexer. The per-PM bypass here covers the job's primary getObjectById; in DataNucleus 5.2 it does not cover lazy field loads, bulk loads, or the second Shepherd the encounter serializer opens. Closing that needs either a dedicated L2-less PMF for indexing reads (a second connection pool per install) or a fleet-wide L2 policy decision. Follow-up, not this PR.
  • Delivery is best-effort. A handoff that fails at enqueue time (no IndexingManager, executor rejected) is logged and dropped; there is no durable retry. The reconciler re-indexes only when the DB version is strictly greater than the indexed one.
  • The Encounter.modified bump from fix(indexing): hand OpenSearch reindexing to the queue after commit, not on JDO flush #1743 is not here. Codex showed the second-resolution setter can decrease the version relative to EncounterForm's millisecond ISO value. A persisted monotonic revision is the right fix; separate PR.
  • postDelete() still unindexes pre-commit. Separate fix.
  • Deep-index child writes (individual, occurrence, annotations) run on their own executor after the job returns and can still be overtaken by an older cascade. This PR guarantees the top-level document only.
  • Transient objects (no PM yet) and PMs Shepherd never prepared still enqueue immediately, as on main.
  • Heuristic transaction outcomes are reported by DataNucleus as a rollback attempt; a heuristic-mixed outcome cannot be resolved here.
  • logNestedExceptions null-safety refactor from fix(indexing): hand OpenSearch reindexing to the queue after commit, not on JDO flush #1743: unrelated once the drain is out of commitDBTransaction().

Tests

70 unit tests (no datastore) + 9 Testcontainers tests against real Postgres and DataNucleus 5.2.7, with the row oracle being a raw JDBC SELECT (no Shepherd, no DataNucleus cache):

  • flush enqueues nothing; Shepherd commit enqueues exactly once, after the transaction ended; the row is committed by then
  • raw pm.currentTransaction().commit() behaves identically
  • rollback enqueues nothing
  • a second transaction begun raw on the same PM parks and drains again
  • vetoed commit leaves the transaction active and the park intact; retry enqueues once
  • vetoed commit + rollback enqueues nothing
  • vetoed commit + pm.close(): under the JDO adapter's default closeActiveTxAction=rollback the close rolls back, the park completes through the normal callback, nothing is enqueued, row unchanged
  • committed completion evicts the row from the real L2 by DataNucleus identity
  • the indexing job's reader PM has L2 retrieve mode bypass

RED evidence (Testcontainers test run against the pre-change listener): 8 of 9 fail, the first with

NoInteractionsWanted ... But found these interactions on mock 'indexingManager':
-> at org.ecocean.WildbookLifecycleListener.postStore(WildbookLifecycleListener.java:72)

that is, the enqueue at flush time.

Latest full backend run, with the independent membership-fixture correction from #1831 temporarily applied for local validation: 1777 tests, 0 failures, 0 errors, 7 skipped. The final cleanup and public-Shepherd assertions were additionally verified on this branch with IndexingJobWiringTest and ShepherdTest (21 tests, all passed). Claude reviewed the follow-up and found no blocking issues.

CI dependency: #1831 fixes the 23 membership-fixture errors already present on main. Merge it first, then update this branch from main; its fixture changes are kept out of this PR's follow-up commit.

Credits

Written by Claude Fable 5.1 (Claude Code). Codex (codex-cli 0.153.4, gpt-6-astra, reasoning effort high) reviewed #1743 and proposed this design, then reviewed the plan and the diff for this PR. Its #1743 review found the failed-commit terminalization bug this design removes. Its plan review caught four load-bearing problems before any code was written: the L2 eviction key mismatch, the pre-commit L2 publication, the delegate-first callback order that could lose the park, and the need for per-entry failure isolation with the successor installed first. Its diff review established that the per-PM L2 bypass is partial in DataNucleus 5.2, explained the close-on-active-transaction behavior via the JDO adapter default, and pushed the test oracle to raw JDBC.

Review status, stated plainly: Codex's first diff round returned not converged with two Majors, both about the partial L2 bypass. Those are answered with the scope boundary above rather than with code, because the fix they call for (an L2-less PMF or a fleet-wide cache policy) is a separate change with its own blast radius. The convergence round then evaluated the branch against that stated boundary and returned "CONVERGED: no Major findings within scope", accepting the boundary as legitimate ("not a dodge: shared-L2 isolation is a separate, pre-existing problem"). Its two remaining Minors (a DataNucleus pin() quirk in the L2 test fixture, and four comment phrasings that overclaimed recovery or isolation) are fixed in the last commit. Three Codex rounds in total on this PR: plan, diff, convergence.

Earlier rounds of #1743 were reviewed by Codex gpt-5.6-terra.

JasonWildMe and others added 7 commits September 5, 2026 11:39
…instead of dropping it

The queue dedupes by object id, and until now a request for an id whose job was
already scheduled or running was silently dropped. That is exactly wrong when the
in-flight job loaded the object before the change behind the second request was
committed: the dropped request was the one that described the object's final
state, and the stale document stuck.

addIndexingQueueEntry() now records the newest (class, unindex) for an in-flight
id, and every terminal job outcome -- success, genuine failure, retry give-up --
goes through finishIndexingJob(), which runs that remembered request as exactly
one follow-up pass while keeping the id queued so concurrent requests still dedupe
against it. Hard removal (executor rejection, queue reset) drops the follow-up
with the id.

Also adds the identity overload addIndexingQueueEntry(String, Class, boolean) for
callers that run after their transaction has ended, and names the job Runnable
(IndexingJob) so the request it carries is observable in tests.

Part of the re-derivation of #1743 on JDO transaction-completion callbacks.
Codex (gpt-6-astra) reviewed #1743 and found this bookkeeping sound for
synchronous completion; the async deep-index child writes remain out of scope.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Codex gpt-6-astra <noreply@openai.com>
…anager until its transaction completes

postStore fires on JDO flush, not commit. A request raised there is now PARKED in
the writing PM's user-object map (PendingBucket) instead of enqueued. The park is
released only by the transaction's completion outcome: COMMITTED drains it,
ROLLED_BACK drops it, and an outcome we cannot interpret runs index requests (the
job re-reads the database, so that is harmless) but never an ambiguous unindex.
Nothing else may terminalize a park -- not "commit threw" (DataNucleus leaves the
transaction ACTIVE after a NucleusUserException, and the park must stay live with
it), not close. Completion installs a fresh park before doing any external work,
so the PM's next transaction has somewhere to park however it is begun, and a
failure handing one entry to the queue never affects the others.

Level-2 eviction uses the DataNucleus internal identity (Persistable.dnGetObjectId),
not JDOHelper.getObjectId(): the L2 cache is keyed by the former, so evicting with
the javax.jdo.identity object silently misses. And because DataNucleus publishes an
object's new state to L2 during preCommit -- before the datastore commit -- the
indexing job's reader PM now sets datanucleus.cache.level2.retrieveMode=bypass so
it reads the committed row rather than another in-flight transaction's copy.

The job's datastore access is behind a small seam (IndexingWork) so a real
IndexingJob can run in a unit test and prove it reports every outcome -- success,
failure, row-not-visible retry, give-up -- through finishIndexingJob().

Nothing is wired to this yet; the Synchronization that drives completion and the
postStore/enqueueAclReindex call-site changes follow in separate commits.

Both the identity-key and the preCommit-publication facts come from Codex
(gpt-6-astra) reviewing the plan for this change against the pinned DataNucleus
5.2.7 bytecode.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Codex gpt-6-astra <noreply@openai.com>
…'s completion callback

IndexingTransactionSynchronization is a javax.transaction.Synchronization that
DataNucleus invokes from inside Transaction.commit() and rollback(), after its own
post-commit work. That callback is the only thing that terminalizes a park:
COMMITTED drains it, ROLLEDBACK drops it. Because it hangs off the transaction
itself, a raw pm.currentTransaction().commit() is covered exactly like
Shepherd.commitDBTransaction(), and Shepherd's commit/rollback/close code is not
involved at all -- which is what removes the blast radius the reviewer objected to
on #1743.

The order inside afterCompletion is fixed: capture and terminalize the park and
install its successor first (no external work, cannot throw), then call whatever
Synchronization was registered before ours (JDO allows exactly one, so we wrap it;
it may close the PM or throw, and neither can lose the snapshot), then drain the
snapshot independently of the PM. beforeCompletion propagates the delegate's
exception, since that is its chance to veto the commit.

IndexingManager.registerSynchronization(pm) installs it once per PM, idempotently.

Codex (gpt-6-astra) flagged the delegate-first ordering in the original plan as a
way to lose the park; this is the corrected order.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Codex gpt-6-astra <noreply@openai.com>
…on once per PersistenceManager, before begin

beginDBTransaction() is the only Shepherd lifecycle method this change touches.
For a freshly obtained PersistenceManager it now registers the
WildbookLifecycleListener, registers the indexing Synchronization, and installs
the deferred-indexing park -- and only then activates the transaction. A store
callback can only fire once the transaction is active, and by then it needs both
the listener and a live park in place.

The registrations happen once per PM, tracked by identity. On main every
beginDBTransaction() -- which runs again on each updateDBTransaction() -- added
another listener, so one store event fanned out to N duplicate callbacks.

The ordering lives in a static package-private helper so it is asserted against a
mock PersistenceManager without a datastore. commitDBTransaction(),
commitDBTransactionWithStatus(), updateDBTransaction(), rollbackDBTransaction(),
closeDBTransaction() and rollbackAndClose() are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Codex gpt-6-astra <noreply@openai.com>
…ead of enqueuing on flush

WildbookLifecycleListener.postStore(), Encounter.enqueueAclReindex() and
MarkedIndividual.enqueueAclReindex() now hand their request to
IndexingManager.addPendingEntry() with the object's own PersistenceManager. While
that PM has an active transaction the request is parked and released by the
transaction's completion callback; a transient object or a caller that has already
committed falls through to an immediate enqueue, exactly as before.

Encounter.enqueueAclReindexNow() is the escape hatch for the one caller whose open
transaction is rolled back on purpose: the background permissions pass reads inside
a transaction it discards at the end, so parking there would drop the request.

Requests are now keyed by identity, so an object without an id (a fresh Encounter
has catalogNumber "" until it is numbered) is no longer turned into a job that can
only fail to load.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Codex gpt-6-astra <noreply@openai.com>
… are enqueued only after commit

Testcontainers Postgres, real DataNucleus, real Shepherd. Each case modifies an
existing Encounter, flushes, and ends the transaction one way or another; a mocked
IndexingManager records what it was asked to do and whether the writer's
transaction was still active at that moment.

  - flush enqueues nothing; Shepherd commit enqueues exactly once, after the
    transaction has ended, and a fresh Shepherd then reads the committed value
  - a raw pm.currentTransaction().commit() is covered identically
  - rollback enqueues nothing
  - a second transaction begun RAW on the same PM parks and drains again
  - a commit vetoed by a NucleusUserException leaves the transaction active and the
    park intact; the retry enqueues once (#1743 terminalized the park on "commit
    threw" and would have enqueued every later store pre-commit)
  - vetoed commit + rollback, and vetoed commit + close, enqueue nothing
  - committed completion evicts the row from level-2 by DataNucleus identity
    (asserted on the real Level2Cache, not a mock)
  - the indexing job's reader PM has level-2 retrieve mode "bypass"

RED evidence: with the pre-change WildbookLifecycleListener swapped in, 8 of 9
fail; the first with
  NoInteractionsWanted ... But found these interactions on mock 'indexingManager':
  -> at org.ecocean.WildbookLifecycleListener.postStore(WildbookLifecycleListener.java:72)
i.e. the enqueue at flush time, before commit.

Observed while writing this: pm.close() on the still-active vetoed transaction did
NOT throw in this environment although DataNucleus 5.2.7's default
closeActiveTxAction is "exception"; the test therefore asserts on outcomes (no
enqueue, row unchanged) rather than on close throwing.

Codex (gpt-6-astra) specified this test's shape in its plan review: observe both
enqueue overloads for the RED run, use a one-shot vetoing Synchronization rather
than a duplicate-key insert to fail the commit, begin the second transaction raw,
and read committed content after commit returns instead of inside the callback.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Codex gpt-6-astra <noreply@openai.com>
… DB test's oracle and preconditions

Codex (gpt-6-astra) reviewed the branch diff and established that the per-PM
datanucleus.cache.level2.retrieveMode=bypass on the indexing reader is PARTIAL in
DataNucleus 5.2: it governs getObjectById, but lazy field loads and bulk loads still
consult L2 under PMF-wide settings, and the encounter serializer opens its own
Shepherd that reads normally. The underlying hazard -- DataNucleus publishing an
object's new state into the shared L2 during preCommit, before the datastore commit
-- is DataNucleus-wide, affects every reader in Wildbook, and predates this branch.
Closing it needs a dedicated L2-less PMF for indexing reads or a fleet-wide cache
policy decision, both separate changes. The bypass stays (it strictly helps the
primary lookup); the comments now say exactly what it does and does not cover, and
no longer describe eviction as read isolation or handoff as guaranteed delivery.

Shepherd.beginDBTransaction(): a null PersistenceManager from the PMF now logs and
returns instead of recording a "begin" ShepherdState for a transaction that never
began.

IndexingPostCommitHandoffDbTest:
  - the row oracle is a raw JDBC SELECT against the container -- no Shepherd, so no
    DataNucleus cache can satisfy it
  - the close-after-vetoed-commit test asserts what actually happens: the JDO API
    adapter defaults closeActiveTxAction to "rollback" (overriding core's
    "exception"), so pm.close() rolls back, the park completes through the normal
    callback, nothing is enqueued, the row is unchanged
  - the level-2 eviction test pins the DataNucleus id before commit so the soft
    cache's precondition cannot be collected between warm-up and assertion

completePendingEntries() is package-private: only tests call it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Codex gpt-6-astra <noreply@openai.com>
@codecov-commenter

codecov-commenter commented Sep 5, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 55.89%. Comparing base (06debf4) to head (06e2a60).
⚠️ Report is 6 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1752   +/-   ##
=======================================
  Coverage   55.89%   55.89%           
=======================================
  Files         316      316           
  Lines       12852    12852           
  Branches     4172     4172           
=======================================
  Hits         7184     7184           
  Misses       5367     5367           
  Partials      301      301           
Flag Coverage Δ
backend 55.89% <ø> (ø)
frontend 55.89% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

… deterministic

Codex (gpt-6-astra) convergence round: CONVERGED, no Major findings within scope,
scope boundary on partial L2 isolation accepted as legitimate. Its two remaining
Minors:

  - Comments and one log line still overclaimed. "Will index it if it exists" and
    "only when strictly greater" omitted that the reconciler also re-indexes a
    MISSING document; "the next reader goes to the database" ignored level-1 hits
    and concurrent repopulation. Reworded to: eviction removes the current shared
    entry; the reconciler re-indexes a missing document, or an existing versioned
    document whose database version is strictly greater. The DB test's class
    comment said "fresh Shepherd" where the oracle is now JDBC.
  - DataNucleus 5.2.7 AbstractReferencedLevel2Cache.pin() creates its pinnedIds set
    on the first call and skips adding the id, so a single pin before the put did
    not guarantee the L2 test's precondition. Pin twice, with the reason.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Codex gpt-6-astra <noreply@openai.com>
@JasonWildMe
JasonWildMe marked this pull request as ready for review September 5, 2026 19:55
@JasonWildMe
JasonWildMe requested a review from naknomum September 5, 2026 20:12
@JasonWildMe JasonWildMe self-assigned this Sep 5, 2026
@naknomum naknomum modified the milestones: 11.0, 10.14 Sep 11, 2026
@JasonWildMe JasonWildMe modified the milestones: 10.14, 11.0 Sep 14, 2026
Conflicts: main (#1785/#1786) replaced Encounter.opensearchIndexPermissions()
with the scheduled PermissionsAudit, which writes repairs directly with
conditional writes and never uses the indexing queue. Took main's deletion.
enqueueAclReindexNow() existed only for that deleted pass, so it and its
unit test are removed; ChildReindexTriggerTest's javadoc keeps the parking
paragraph and takes main's PermissionsAudit paragraph.

Reviewed by Codex (gpt-6.1-sol): converged, no Major findings; its stale
test-count wording Minor is fixed here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@UrbanFercec UrbanFercec left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ran it merged into today's main with the Testcontainers tests, all green. Read through the DataNucleus 5.2.7 TransactionImpl too: afterCompletion runs after the L2 put and inactive, so the evict order holds. Two small things inline.

ShepherdIndexingWork(String objectID) {
shepherd = new Shepherd("context0");
shepherd.setAction("IndexingManager_" + objectID);
shepherd.beginDBTransaction();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If begin throws anything other than JDOUserException or NPE, say a JDODataStoreException while the DB is down, the constructor throws, work stays null and the finally in run() never closes this Shepherd. Main assigned bgShepherd before begin, so its rollbackAndClose still ran. A try/catch here that closes and rethrows would keep that.

"Shepherd.beginDBTransaction(): could not obtain a PersistenceManager");
return; // nothing began; do not record a "begin" state for it
}
listenerRegisteredOn = prepareAndBeginTransaction(pm, listenerRegisteredOn);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing null instead of listenerRegisteredOn here brings back one listener per begin, and the Indexing*, ShepherdTest and ShepherdBeginRegistrationTest suites all stay green. A test that begins twice on one Shepherd and checks addInstanceLifecycleListener ran once would pin the fan-out fix.

@UrbanFercec UrbanFercec left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both points are covered now. The failed-begin test catches it when the close in the catch is removed, and the repeated-begin test catches the listener going back to null. Indexing and Shepherd suites pass on today's main.

@naknomum naknomum left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@JasonWildMe
JasonWildMe merged commit a3f4837 into main Oct 27, 2026
3 checks passed
@JasonWildMe
JasonWildMe deleted the fix/indexing-post-commit-sync branch October 27, 2026 18:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

React Encounter Search does not display updated identity status Pre-commit OpenSearch indexing queue calls race / survive rollback

4 participants