fix(ingestion): report intentionally-excluded docs separately from Qdrant duplicates - #233
Open
detail-app[bot] wants to merge 1 commit into
Conversation
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.
Detail bug report: View on Detail
Closes #220
Bug
DocumentProcessorlogged intentionally-excluded documentation pages (Javadocclass-use/*.htmlindex pages, frameset/Redoc navigation shells) as Qdrant duplicates. It boundduplicates = outcome.backlog().skippedFiles()and labeled that count"Skipped {} duplicate files (already in Qdrant)", butIngestionBacklogStatus.skippedFileswas documented as "already indexed or intentionally excluded". Excluded pages are not upserted and may have their points deleted, so they hold zero Qdrant points — the opposite of "already in Qdrant" — yet were counted as duplicates. Root cause:LocalDocsFileIngestionProcessor.processExcludedPage(and the unchanged-excluded-page branch) reused the genericskippedFile()outcome for a semantically distinct category, and the CLI labeled the wholeskippedFilesbucket as duplicates. Producer (skippedFile(): "unchanged or already indexed") and consumer (skippedFiles: "already indexed or intentionally excluded") contracts disagreed.Fix
Introduced a dedicated excluded category end-to-end so the operator-facing labels are honest and the contracts reconcile (the bug report's preferred option):
LocalDocsFileOutcome— newExcludedsealed variant +excludedFile()factory;skippedFile()javadoc narrowed back to "unchanged or already indexed" (genuine duplicates that retain their Qdrant points).IngestionBacklogStatus— newexcludedFilescomponent; invariant becomesinspected == processed + skipped + excluded + failed;completeBatch(...,excludedCount,...);notStarted/running/startBatch/finish/abandon/resumecarryexcludedFiles.abandon()keeps excluded in the terminal-success prefix (processed + skipped + excluded), so interrupted runs don't re-process excluded pages. The record is serialized in the durable run checkpoint; old checkpoints withoutexcludedFilesdeserialize as0and still satisfy the (now-relaxed-to-old) invariant.LocalDocsFileIngestionProcessor—processExcludedPageand the "unchanged excluded Java API page" branch returnexcludedFile(); genuine unchanged-duplicate branches keepskippedFile().LocalDocsDirectoryIngestionService— routesExcludedoutcomes to abatchExcludedCountand passes it tocompleteBatch.DocumentProcessor— keeps the existing duplicate labels (now truthful, sinceskippedFilesno longer contains excluded pages) and adds" Excluded {} files (intentionally not indexed)"per set and"Total excluded files: {}"in the summary;excludedflows throughIngestionTotalsandProcessingOutcome.GitHubRepoProcessor— its exhaustiveswitchoverLocalDocsFileOutcomecombinesSkipped/Excludedinto one arm (source-code ingestion never producesExcluded; both count as the existing skipped bucket; preserves exhaustiveness without a SpotBugs duplicate-clause finding).Testing
Unit tests, typecheck, lint, and build all pass:
./gradlew test(full JVM suite),compileJava/compileTestJava,spotlessCheck,spotbugsMain/spotbugsTest,pmdMain/pmdTest, ast-grep Java rules,cd frontend && npm run check(svelte-check, 0 errors), andcd cli && node --test(43/43) +npm run pack:check.New and updated committed tests:
DocumentProcessorExcludedCategoryLogTestdrives two doc sets through the realDocumentProcessor(only the ingestion use case mocked) withskippedFiles=2/excludedFiles=3andskippedFiles=1/excludedFiles=0, capturing real Logback output, and asserts the exactSkipped … duplicate files (already in Qdrant)andExcluded … files (intentionally not indexed)lines, theTotal duplicates skipped: 3/Total excluded files: 3summary, and that the pre-fix conflation (Total duplicates skipped: 5/6) is absent.IngestionBacklogStatusTest— new cases pin thatcompleteBatchaccumulatesexcludedFilesseparately, and thatabandon()retainsexcludedFilesin the terminal-success prefix after an interruption.LocalIngestionRunStoreTest.readsCheckpointWrittenBeforeExcludedFilesFieldWasIntroduced— writes a current checkpoint, strips theexcludedFilesJSON field to simulate the old schema, and asserts it reads back withexcludedFiles=0and the invariant holds (backward compatibility for in-flight durable checkpoints).LocalDocsFileIngestionProcessorTest— the class-use, frameset, and Redoc-shell tests now assertinstanceof LocalDocsFileOutcome.Excluded, pinning that excluded pages no longer reuseSkipped; the genuine unchanged-duplicate path still returnsSkipped.DocumentProcessorFailureContractTestzero case updated for the new field and now also assertsTotal excluded files: 0.End-to-end verification against real Qdrant 1.18.3 (via
docker compose -f infra/docker-compose-qdrant.yml), running the builtbootJarwith thecliprofile against a staged Javadocclass-use/List.htmlcorpus:processExcludedPagewithdeleteByUrl+ empty marker): CLI loggedExcluded 1 files (intentionally not indexed)andTotal excluded files: 1withTotal duplicates skipped: 0(pre-fix this wasTotal duplicates skipped: 1). Qdrant RESTpoints/countfiltered by the class-use URL returned0— the page holds zero points, the opposite of "already in Qdrant".Excludednot a duplicate, confirming the unchanged-excluded path now returnsexcludedFile().One sub-case could not be verified live: a genuine already-indexed duplicate that retains its Qdrant points. Reproducing it requires indexing a file first (which calls the embedding gateway), then re-running to hit the skip path; the local embedding server / shared LLM gateway was not reachable in this environment. The preserved duplicate label is nonetheless asserted against real
DocumentProcessorlog output inDocumentProcessorExcludedCategoryLogTest, and the genuine-duplicate code branch (unchanged fingerprint + Qdrant points present →skippedFile()) is exercised through the real processor inLocalDocsFileIngestionProcessorTest.shouldKeepDistinctIngestionStateForJavaPagesThatShareOneCitation.Automatic Fixes PRs can be configured here.