fix(#43): reuse live document on same content hash - #143
Conversation
Path-only dedup still misses the same bytes under a new filename. Look up tenant + fingerprint before starting OCR so the existing card is returned.
…rse junk Ingest used response.json() on nginx HTML, which became Unexpected token. Route upload through fetchJson and map generic HTTP statuses to plain messages.
KDwevedi
left a comment
There was a problem hiding this comment.
Two blockers:
-
The documented “soft-deleted match starts a fresh ingest” contract is not enforced for path-identical uploads.
dedup_or_none()checksdb.get_document(workflow_id)before the live-fingerprint query and never excludesis_disabled; if that Temporal workflow still answers, it returns the disabled row asduplicate: true. If Temporal no longer answers, the caller reuses/upserts the same disabled row rather than allocating a fresh run, so it can remain hidden. Apply the disabled-row rule to the path-identity branch and add an exact same filename/path regression for both queryable and closed Temporal cases. -
The full synthetic merge suite now trips the shared
/uploadlimiter in the two final new dedup tests with 429, while the other branches do not.tests/test_ingest_dedup.pypasses alone, which confirms suite-order/global limiter leakage. Reset or disable the limiter in these route tests so the repository suite stays green.
Separately, the existing taxonomy-console assertion still fails on main and is unrelated.
Path-identical uploads of a disabled document now allocate a rerun id whether or not Temporal still answers. Disable the shared /upload limiter in the dedup route tests so the full suite cannot 429 them, and merge current main.
|
Addressed the review and rebased/merged current
|
KDwevedi
left a comment
There was a problem hiding this comment.
The previous soft-delete and limiter findings are fixed, but one dedup blocker remains: fingerprint lookup and document creation are still a read-then-write sequence with no uniqueness guard.
Two simultaneous uploads of the same bytes under different filenames derive different workflow IDs. Both requests can complete find_live_document_by_fingerprint() before either reaches upsert_document(), then both create rows and start OCR. Since documents has no unique constraint on tenant + live fingerprint, this recreates the same-document_id alias condition that #43 is intended to prevent.
Please make reservation/creation atomic (for example, a database uniqueness invariant for a normalized tenant + active fingerprint with conflict handling that returns the winning row) and add a concurrent-upload regression. Soft-deleted rows must remain eligible for a fresh run.
Add a unique live tenant+hash index and return the winning row on INSERT conflict so concurrent same-bytes uploads cannot create a second OCR run. Soft-deleted rows stay outside the index and can still start a fresh ingest.
|
Addressed the remaining atomic-reservation blocker. Live tenant + Regressions: concurrent two-thread INSERT (exactly one live row), |
KDwevedi
left a comment
There was a problem hiding this comment.
The clean-database race is fixed, but the migration path still leaves the blocker active on legacy databases.
init_db() catches IntegrityError while creating ux_documents_live_tenant_fingerprint and only logs/skips the index when duplicate live tenant+fingerprint rows already exist. That is exactly the legacy state discussed in #43. Once the index is skipped, there is no uniqueness protection for any future fingerprint, so concurrent first-time uploads can still create duplicates.
I reproduced this by starting with two legacy live aliases: the unique index remained absent after init_db(), and two subsequent inserts sharing a new tenant+fingerprint both succeeded.
Please make the migration preserve the invariant even when legacy duplicates exist—for example, reconcile/fail explicitly before enabling writes, or use a separate unique fingerprint-reservation table that can select a winner without requiring the historical document rows themselves to be unique. Add a regression that initializes from a legacy duplicate database and proves concurrent inserts for a new fingerprint cannot both succeed.
…ready duplicate init_db skipped the documents unique index on legacy duplicate rows, which left every future hash unconstrained. Claim tenant+fingerprint in a separate table so new uploads stay unique, and release the claim on soft-delete.
|
Addressed the leftover uniqueness hole on legacy duplicate DBs.
Regression: Also merged |
A cascade delete left document_live_fingerprints pointing at a gone workflow_id, so the same bytes could not be ingested again. Drop the claim after the documents row is removed, and keep backfill order stable with a subquery.
Summary
200+duplicate: true) instead of starting a second OCR run. Path-identical uploads still dedup the way they do on main (fix(api): shared ingest _dedup_or_none with duplicate:true contract #104).Closes #43.
Test plan
pytest tests/test_ingest_dedup.py(12 passed)npm run buildinui/workflow_id, no new pipelineduplicate: true