lex-apollo 0.4.25 → 0.4.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fe40d4ba4efef56b2bd1cec166f525ca3c165989453e455bdacc14a2a5377d16
|
|
4
|
+
data.tar.gz: e4f7f0daabec002d031c00a6936308df68d75dd2200b1f49f7264de6f2833153
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7d0995197fb2c7191a81a91bba476792570baaf5e750df8b0464de096beabc82a4fbec8fabb8b09fda967d5c834c7bf682041d8775ce24c602d1790e332692c7
|
|
7
|
+
data.tar.gz: 6c0277a64242c3c37795dde7229db4d2f0c27d191a7ad5d252266db1c4b433538ec5741f57624a3e262d1ad1b38ca9abf9c0ec1b2463a7bd69b9fac2126cb190
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.26] - 2026-05-11
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Handle `Sequel::UniqueConstraintViolation` in `create_candidate_entry` gracefully — a race condition during concurrent knowledge ingestion can cause two threads to pass the content_hash dedup check simultaneously and both attempt to insert the same row. On collision, the rescue block now looks up the existing winner row by content_hash (excluding archived) and returns its ID so the caller continues normally (access log, contradiction detection, etc.) instead of propagating a database error.
|
|
7
|
+
- Added `Sequel::UniqueConstraintViolation` stub to the test-only Sequel shim so the race-condition rescue path is exercisable in unit tests without a live database.
|
|
8
|
+
|
|
3
9
|
## [0.4.25] - 2026-05-08
|
|
4
10
|
|
|
5
11
|
### Fixed
|
|
@@ -469,6 +469,22 @@ module Legion
|
|
|
469
469
|
)
|
|
470
470
|
log.info("Apollo Knowledge.handle_ingest created entry_id=#{new_entry.id} status=candidate domain=#{metadata[:domain]} source_agent=#{metadata[:source_agent]}") # rubocop:disable Layout/LineLength
|
|
471
471
|
new_entry.id
|
|
472
|
+
rescue Sequel::UniqueConstraintViolation => e
|
|
473
|
+
# Race condition: another thread/process inserted the same content_hash between our
|
|
474
|
+
# dedup check and this insert. Fetch and return the winner's id so the caller can
|
|
475
|
+
# continue normally (access log, contradiction detection, etc.).
|
|
476
|
+
winner = Helpers::DataModels.apollo_entry
|
|
477
|
+
.where(content_hash: content_hash)
|
|
478
|
+
.exclude(status: 'archived')
|
|
479
|
+
.first
|
|
480
|
+
if winner
|
|
481
|
+
log.warn("Apollo Knowledge.create_candidate_entry race_dedup entry_id=#{winner.id} content_hash=#{content_hash} source_agent=#{metadata[:source_agent]}") # rubocop:disable Layout/LineLength
|
|
482
|
+
winner.id
|
|
483
|
+
else
|
|
484
|
+
handle_exception(e, level: :warn, handled: true, operation: 'apollo.knowledge.create_candidate_entry',
|
|
485
|
+
content_hash: content_hash)
|
|
486
|
+
nil
|
|
487
|
+
end
|
|
472
488
|
end
|
|
473
489
|
|
|
474
490
|
def browse_query?(query)
|
|
@@ -321,6 +321,30 @@ RSpec.describe Legion::Extensions::Apollo::Runners::Knowledge do
|
|
|
321
321
|
expect(result[:deduped]).to be true
|
|
322
322
|
expect(result[:entry_id]).to eq('uuid-existing')
|
|
323
323
|
end
|
|
324
|
+
|
|
325
|
+
it 'recovers gracefully when a concurrent ingest wins the content_hash unique constraint race' do
|
|
326
|
+
# Simulate: dedup check passes (nil — no existing entry yet), then .create
|
|
327
|
+
# raises UniqueConstraintViolation (another thread inserted between check and insert).
|
|
328
|
+
# create_candidate_entry must rescue and return the existing entry's id so the
|
|
329
|
+
# caller succeeds rather than propagating a database error.
|
|
330
|
+
race_entry = double('race_entry', id: 'uuid-race-winner')
|
|
331
|
+
|
|
332
|
+
allow(mock_entry_class).to receive(:create)
|
|
333
|
+
.and_raise(Sequel::UniqueConstraintViolation, 'duplicate key value violates unique constraint "idx_apollo_content_hash"')
|
|
334
|
+
|
|
335
|
+
collision_dataset = double('collision_dataset')
|
|
336
|
+
allow(mock_entry_class).to receive(:where).with(content_hash: anything).and_return(collision_dataset)
|
|
337
|
+
allow(collision_dataset).to receive(:exclude).with(status: 'archived').and_return(collision_dataset)
|
|
338
|
+
# First call: dedup pre-check returns nil (not yet in DB).
|
|
339
|
+
# Second call: post-collision lookup returns the winner inserted by the other thread.
|
|
340
|
+
allow(collision_dataset).to receive(:first).and_return(nil, race_entry)
|
|
341
|
+
|
|
342
|
+
result = host.handle_ingest(content: 'concurrent content', content_type: 'fact',
|
|
343
|
+
source_agent: 'agent-1',
|
|
344
|
+
content_hash: 'd3861b2862454c5a6a9e480829333841')
|
|
345
|
+
expect(result[:success]).to be true
|
|
346
|
+
expect(result[:entry_id]).to eq('uuid-race-winner')
|
|
347
|
+
end
|
|
324
348
|
end
|
|
325
349
|
end
|
|
326
350
|
|
data/spec/spec_helper.rb
CHANGED