fix(brain): unwedge suggestion approval and refresh knowledge counts live - #77
Merged
Merged
Conversation
…live Approving code-scan suggestions reported "Approved 0 of 2" and, once approving worked, the knowledge counts stayed stale until a page reload while freshly approved items sorted to the bottom of the list. Approval was permanently wedged ------------------------------ `schema_documentation` carried no unique constraint on (connection_id, object_type, object_name, parent_object, source) and `CodeSuggestionApplier.approve` took no row lock, so one bulk approve submitted twice concurrently wrote 219 duplicate pairs. Every later approve touching one of those keys threw `IncorrectResultSizeDataAccessException: Query did not return a unique result` out of the Optional-returning upsert finder, which bulk-decide swallowed per item. The duplicate never self-heals, so all 198 pending SCHEMA_DOC suggestions were unapprovable. - Those finders now return List; `SchemaDocumentationDeduplicator` collapses matches, keeps the newest row, repoints any `applied_doc_id` off the rows it deletes (a loose reference, not an FK, so a dangling value fails silently) and drops their RAG embeddings. Applied at all four call sites, including `SchemaDriftListener`. - `V116__dedupe_schema_documentation.sql` + `SchemaDocumentationDedupeInitializer` remove the duplicates and add `ux_schema_doc_target`. There is no Flyway here, so the initializer is what actually applies it; it is idempotent and skips once the index exists. On the reporting install: 219 rows and 219 orphaned embeddings removed, 0 duplicate groups left. - approve/reject load the suggestion `FOR UPDATE`, so the concurrent double-submit that created the duplicates blocks instead of racing. Counts did not refresh ---------------------- An approval also writes `schema_documentation`, served by `brain/notes`, which backs the Write-notes tab and its coverage counts. The decide hooks invalidated only codeScan + companyKnowledge, so those counts were stale until reload. `invalidateAfterDecision` now covers brain and schemaContext too. Newest items sorted last ------------------------ - `@PreUpdate` does not fire on insert, so a new note has a null `updatedAt`; sorting on it alone with nulls last sent every brand-new note to the bottom. `BrainNoteService` sorts on COALESCE(updatedAt, createdAt), matching the company-knowledge repo. - `listSuggestions` sorted every status by confidence, scattering a fresh approval among hundreds of older ones. PENDING stays confidence-first (a work queue); decided statuses sort by `decidedAt DESC NULLS LAST`. Test tooling ------------ Both self-host scripts hardcoded `sudo -u postgres psql`, which does not exist on the Compose deployment install.sh produces, so the documented verify command failed before testing anything; they now resolve the path via `scripts/self-host/vaultdb.py`. The e2e suite also parked every real CODE_DERIVED row by rewriting source to USER and never restored it, silently relabelling approved docs on a live install — it now copies rows to a scratch table and restores them, and only deletes its planted row while nothing references it. Added cases for duplicate collapse, applied_doc_id repointing, the unique index, and two genuinely concurrent approves writing exactly one row: 30/30 pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018mG2xj9gWJ8WzfDP2fDePP
venkateshsakamuri-lab
approved these changes
Aug 22, 2026
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.
Follow-up to #74. That PR made the failure visible (bulk decide now returns
failures[], and the UI renders a partial/zero success as an error) but the approval itself still threw — its diagnosis, aschema_documentation_source_checkmissingCODE_DERIVED, did not apply to the reporting install, where the CHECK was already correct and the log said something else entirely.What was actually failing
schema_documentationcarried no unique constraint on(connection_id, object_type, object_name, parent_object, source)andCodeSuggestionApplier.approvetook no row lock, so one bulk approve submitted twice concurrently wrote 219 duplicate pairs (pairs ~3s apart; in every group the older row is an orphan and the newer one holds theapplied_doc_id). Every later approve landing on such a key threw out of theOptional-returning upsert finder, and bulk-decide swallowed it per item. The duplicate never self-heals, so all 198 pending SCHEMA_DOC suggestions were permanently unapprovable.Fix
1. Duplicate-tolerant upsert. The three finders now return
List, so the compiler forces every caller to handle N matches.SchemaDocumentationDeduplicatorkeeps the newest row, repoints anyapplied_doc_idoff the rows it deletes (a loose reference, not an FK — a dangling value fails silently), and drops their RAG embeddings. Applied at all four call sites, includingSchemaDriftListener, which would have thrown identically the first time one of 17 duplicated tables was dropped.2. Data repair + constraint.
V116__dedupe_schema_documentation.sql, applied bySchemaDocumentationDedupeInitializer— there is no Flyway runtime here, so a SQL file alone would never run. Idempotent: it returns before touching a row once the index exists.coalesce(parent_object,'')in the key because Postgres treats NULLs as distinct.3. The root cause.
approve/rejectload the suggestionFOR UPDATE, so the concurrent double-submit blocks and the second caller seesAPPROVED.Also fixed (reported after the first fix landed)
schema_documentation, served bybrain/notes, which backs the Write-notes tab and its coverage counts. The decide hooks invalidated onlycodeScan+companyKnowledge.invalidateAfterDecisionnow coversbrainandschemaContexttoo.@PreUpdatenever fires on insert, so a new note has a nullupdatedAt; sorting on it alone with nulls last sent every brand-new note to the bottom. NowCOALESCE(updatedAt, createdAt), matchingCompanyKnowledgeEntryRepository.listSuggestionssorted every status by confidence. PENDING stays confidence-first (it is a work queue); decided statuses now sort bydecidedAt DESC NULLS LAST.Two bugs in #74's own test tooling
sudo -u postgres psql, which does not exist on the Compose deploymentinstall.shproduces — the verify command in fix: Review queue approvals (CODE_DERIVED + stale list + bulk errors) #74's description failed before testing anything. Now resolved throughscripts/self-host/vaultdb.py.e2e-review-approvals.pystep 10 rewrote every realCODE_DERIVEDrow tosource='USER'and never restored it. Running it against a live install silently relabelled 339 approved docs, and post-V116 it would collide with the unique index. It now parks rows in a scratch table and restores them with a verified count, and its cleanup deletes the planted row only while nothing references it.Verification
Run against the live self-host stack, not just unit tests.
removed 219 duplicate rows, 219 orphaned embeddings; 7068 → 6849 doc rows, 8190 → 7971 RAG rows, 0 duplicate groups, 0 danglingapplied_doc_id. Skipped cleanly on restart.{"requested": 2, "succeeded": 2, "failed": 0}.conf=0.97rows.brain/notes4607 → 4608 on a first-time doc (the count that never refreshed), and the just-approvedcrm.customersnow sorts first.TrainingServiceBusinessTermTest/BrainInitStageExecutorTestreproduce identically on pristinemainand are unrelated.applied_doc_idrepointing, the unique index rejecting a second row, and two genuinely concurrent approves writing exactly one row.Note: the suite consumes its own fixtures, so re-run the seed before each run.
🤖 Generated with Claude Code
https://claude.ai/code/session_018mG2xj9gWJ8WzfDP2fDePP