Skip to content

add readable multi-branch Qdrant aliases - #242

Open
rostilos wants to merge 6 commits into
1.8.1-rcfrom
feature/multi-branch-rag-index
Open

add readable multi-branch Qdrant aliases#242
rostilos wants to merge 6 commits into
1.8.1-rcfrom
feature/multi-branch-rag-index

Conversation

@rostilos

@rostilos rostilos commented Aug 7, 2026

Copy link
Copy Markdown
Owner
  • publish atomic aliases for primary and retained branch generations
  • preserve immutable generation aliases as the analysis source of truth
  • reconcile aliases for existing active indexes after deployment
  • serialize updates per branch while allowing different branches to run in parallel
  • retain backward-compatible project-level aliases
  • update RAG configuration and operator documentation
  • add Java and Python coverage for alias publication and reconciliation

- publish atomic aliases for primary and retained branch generations
- preserve immutable generation aliases as the analysis source of truth
- reconcile aliases for existing active indexes after deployment
- serialize updates per branch while allowing different branches to run in parallel
- retain backward-compatible project-level aliases
- update RAG configuration and operator documentation
- add Java and Python coverage for alias publication and reconciliation
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5e01679f-623a-4456-af5d-8c79da0ed792

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

fix #1: improve PR review reporting, RAG consistency, and concurrent analysis

fix #2: prevent out-of-diff findings from rejecting PR reviews

fix #3: handle empty Jira task descriptions ( qa-doc )
Group only plausible duplicate candidates, preserve uncertain findings, and retain all merged locations. Bind MCP verification to the exact reviewed commit, finding ID, file, and source window while keeping model context token-efficient.
@codecrow-local

codecrow-local Bot commented Aug 10, 2026

Copy link
Copy Markdown

⚠️ Code Analysis Results

Quality Gate Default Quality Gate: 🔴 FAILED

  • HIGH Issues by Severity > 0 (actual: 1) - FAILED
  • MEDIUM Issues by Severity > 0 (actual: 8) - FAILED

Summary

Pull Request Review: add readable multi-branch Qdrant aliases

Status PASS WITH WARNINGS
Risk Level HIGH
Review Coverage 22 files analyzed in depth
Confidence MEDIUM

Executive Summary

This PR adds readable aliases and supporting multi-branch Qdrant generation, reconciliation, indexing, and configuration changes across the Java and Python components. The implementation is broadly aligned with the stated title, but the cross-component changes introduce elevated compatibility and data-integrity risk, particularly around branch alias uniqueness, streaming indexing, branch-scoped commit tracking, and configuration preservation. No task-management context was provided, so task-coverage confidence is based on the reviewed changes and repository-wide findings.

Recommendation

Decision: PASS WITH WARNINGS

Proceed only after addressing the high-impact correctness concerns and confirming that the affected Java tests compile and that end-to-end multi-branch aliasing, reconciliation, and streaming-indexing flows pass. The PR is not blocked by a critical issue, but merging without these corrections could cause incorrect branch resolution, failed indexing requests, or incomplete provenance tracking.

Issues Overview

Severity Count
🔴 High 1 Critical issues requiring immediate attention
🟡 Medium 8 Issues that should be addressed
✅ Resolved 2 Resolved issues

Analysis completed on 2026-08-10 14:25:23 | View Full Report | Pull Request


📍 Findings not posted inline (2)

GitHub only accepts inline review comments on lines available in the current pull-request diff. These findings remain part of the complete review.


📋 Detailed Issues (9)

🔴 High Severity Issues

Id on Platform: 4000

Category: 🐛 Bug Risk

File: .../routers/index.py:152

Streaming endpoint accesses missing request fields

The streaming handler directly evaluates request.source_tree_sha256 and subsequently request.collection_target, but the visible IndexRequest definition contains only repo_path, workspace, project, branch, commit, preserve_other_branches, cleanup_repo_path, include_patterns, and exclude_patterns (Evidence RAG-d51e661f0c42ddcc). Unlike the ordinary endpoint, which uses getattr for these optional fields, this code raises AttributeError for a normal IndexRequest. The exception is caught inside the worker and emitted as an SSE error, so /index/repository/stream never performs indexing for requests using the current model contract.

💡 Suggested Fix

Use getattr(request, "source_tree_sha256", None) and getattr(request, "collection_target", None) as in the ordinary endpoint, or add and validate these fields on IndexRequest before accessing them directly.

View Issue Details


🟡 Medium Severity Issues

Id on Platform: 3998

Category: 🔒 Security

File: .../service/BranchArchiveService.java:343

Directory extraction has no size limit

The RAG extraction path streams every accepted ZIP member until EOF without an entry-size or total-archive limit. A highly compressed archive or a repository containing a very large member can therefore consume unbounded disk space even though the in-memory API is bounded. Because this service processes downloaded VCS archives, a malicious or compromised archive can cause disk exhaustion and make the analysis service unavailable.

💡 Suggested Fix

Track bytes written per entry and across the extraction, enforce configurable maximums, and abort or skip entries that exceed them. Clean up the target directory or partial file when extraction is rejected.

View Issue Details


Id on Platform: 3999

Category: 🐛 Bug Risk

File: .../index_manager/manager.py:231

Branch alias generation collides for case-only branch names

Branch alias generation collides for case-only branch names
The alias contract lowercases branch names before deciding whether a collision suffix is needed. For branches such as Feature/Login and feature/login, the resulting readable value is identical and the comparison against raw_branch.lower() does not trigger hashing. Both branches therefore publish the same operator alias, despite the Java reconciliation service independently repairing aliases for active branch generations.
Evidence: manager.py constructs the branch operator alias from a lowercased branch name, while RagBranchOperatorAliasReconciliationService periodically restores aliases for active branch indexes. Two case-distinct Git branches can consequently update or reconcile the same Qdrant alias and make operator queries resolve to the wrong branch generation.
Business impact: Operators and legacy integrations can query one branch and receive another branch's Qdrant contents; concurrent publication or reconciliation can repeatedly overwrite the alias target.

Also affects: java-ecosystem/libs/rag-engine/src/main/java/org/rostilos/codecrow/ragengine/branch/RagBranchOperatorAliasReconciliationService.java, python-ecosystem/rag-pipeline/src/rag_pipeline/core/index_manager/manager.py:262

💡 Suggested Fix

Use a collision-resistant canonical encoding for the complete case-sensitive branch name, or always append a digest of the original branch whenever normalization changes case. Apply the same helper in Python publication and Java reconciliation so both paths produce identical, unique aliases.

View Issue Details


Id on Platform: 4001

Category: 🧹 Code Quality

File: .../service/AnalyzedCommitService.java:65

Branch-scoped receipts violate commit uniqueness

In multi-branch mode this code assigns a target branch and deliberately queries for existing rows scoped to that branch. However, the current AnalyzedCommit entity still has the unique constraint uq_analyzed_commit_project_hash on (project_id, commit_hash) (Evidence RAG-9f957c9fc710bda3). The same commit cannot therefore be recorded for two target branches, even though the new service logic treats those as independent receipts. saveAll will fail on the second branch, leaving branch coverage/provenance receipts missing and causing repeated analysis attempts.

💡 Suggested Fix

Align the database uniqueness model with branch-scoped receipts, for example by making uniqueness include target_branch (while handling legacy null rows), and add the corresponding migration. Alternatively, do not create duplicate commit rows and retain a separate branch-context receipt table.

View Issue Details


Id on Platform: 4002

Category: 🧪 Testing

File: .../service/RagOperationsServiceImplTest.java:1233

RagConfig test constructor has invalid arity

The changed setup constructs RagConfig with eight arguments. The visible RagConfig declaration has six record components (enabled, branch, includePatterns, excludePatterns, multiBranchEnabled, and branchRetentionDays) and the visible overloads do not provide an eight-argument constructor. This test class therefore cannot compile until the call matches an available constructor.

Also affects: java-ecosystem/libs/rag-engine/src/test/java/org/rostilos/codecrow/ragengine/service/RagOperationsServiceImplTest.java:1

💡 Suggested Fix

Use the available six-argument RagConfig constructor, or construct the configuration and set the supported branch-pattern fields through the actual project configuration API if those patterns belong to another configuration type.

View Issue Details


Id on Platform: 4003

Category: 🧹 Code Quality

File: .../analysis/PullRequestAnalysisProcessor.java:783

Records commits beyond pull request head

When targetBaseRevision is available, the loop stops only at the target base and never stops at request.getCommitHash(). If the source branch has advanced beyond the PR head, getCommitHistory returns newer branch commits and they are added to prCommits, captured as PR evidence, and recorded as covered by this PR analysis. This misattributes SCM provenance and can suppress later analysis for commits that were not part of the pull request.

💡 Suggested Fix

Stop collecting when the requested PR head commit is reached, and only then continue toward the target base if appropriate. Ensure the collected range is bounded to commits from the PR base through the requested PR head.

View Issue Details


Id on Platform: 4004

Category: 🧪 Testing

File: .../service/RagBranchIndexStatusServiceTest.java:32

RagConfig test constructor has invalid arity

The added test constructs RagConfig with eight arguments. The visible RagConfig declaration has six record components and the visible constructors do not expose an eight-argument overload, so this test cannot compile as written.

💡 Suggested Fix

Change the test to use the supported six-argument RagConfig constructor and configure retained branch patterns through the actual supported configuration object/API.

View Issue Details


Id on Platform: 4005

Category: 🐛 Bug Risk

File: .../branch/BranchIndexGenerationBuildService.java:210

Case-distinct branches share the same readable Qdrant alias

Case-distinct branches share the same readable Qdrant alias
The Java generation builder and reconciliation service publish or restore readable aliases whose names are generated by the Python index manager. The Python alias construction lowercases the sanitized branch name, so branches such as Feature/Login and feature/login produce the same alias. Both the normal publication path and the scheduled reconciliation path can therefore repeatedly rebind one alias to different active generations.
Evidence: BranchIndexGenerationBuildService invokes publishGenerationAliases after activation, while RagBranchOperatorAliasReconciliationService independently repairs aliases for active generations. The Python manager's branch alias normalization uses re.sub(...).lower(), which erases case distinctions before collision detection. Consequently, reconciliation can make an operator-facing alias resolve to the wrong branch even though the registry retains distinct branch generations.
Business impact: Queries or operator integrations using the readable alias for one case-distinct branch may retrieve the other branch's Qdrant contents. Concurrent generation publication and scheduled reconciliation can overwrite the alias target repeatedly.
Also affects: java-ecosystem/libs/rag-engine/src/main/java/org/rostilos/codecrow/ragengine/branch/RagBranchOperatorAliasReconciliationService.java, python-ecosystem/rag-pipeline/src/rag_pipeline/core/index_manager/manager.py

💡 Suggested Fix

Use a collision-safe alias identity shared by Java and Python. Preserve case distinctions in the collision check or always append a deterministic branch-name hash when normalization changes the raw branch name; apply the same algorithm in normal publication and reconciliation, and add tests for Feature/Login versus feature/login.

View Issue Details


Id on Platform: 4006

Category: 🐛 Bug Risk

File: .../service/ProjectService.java:785

Compatibility updates erase branch settings

The backward-compatible updateRagConfig overload delegates with null, null for the newly added indexedBranches and transientBranchIndexesEnabled fields. The main overload then constructs a new RagConfig from those values, so any caller still using the old signature silently clears previously configured retained branches and transient-index settings whenever it updates an unrelated RAG option. The shorter compatibility overload has the same behavior.

💡 Suggested Fix

When the new arguments are null, preserve the corresponding values from the existing RagConfig instead of writing nulls. Alternatively, make the compatibility overloads read the current configuration and pass its indexed-branch and transient-index values explicitly.

View Issue Details


Files Affected

  • .../service/AnalyzedCommitService.java: 1 issue
  • .../analysis/PullRequestAnalysisProcessor.java: 1 issue
  • .../branch/BranchIndexGenerationBuildService.java: 1 issue
  • .../service/ProjectService.java: 1 issue
  • .../routers/index.py: 1 issue
  • .../service/BranchArchiveService.java: 1 issue
  • .../service/RagBranchIndexStatusServiceTest.java: 1 issue
  • .../service/RagOperationsServiceImplTest.java: 1 issue
  • .../index_manager/manager.py: 1 issue

@codecrow-local codecrow-local Bot 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.

CodeCrow Review

Actionable comments posted: 7

Each finding below is attached to the relevant changed line. The complete analysis remains available in the CodeCrow summary comment.

output.write(prefix);
byte[] buffer = new byte[8192];
int length;
while ((length = zis.read(buffer)) > 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 MEDIUM | Security

Directory extraction has no size limit

The RAG extraction path streams every accepted ZIP member until EOF without an entry-size or total-archive limit. A highly compressed archive or a repository containing a very large member can therefore consume unbounded disk space even though the in-memory API is bounded. Because this service processes downloaded VCS archives, a malicious or compromised archive can cause disk exhaustion and make the analysis service unavailable.

💡 Suggested fix

Track bytes written per entry and across the extraction, enforce configurable maximums, and abort or skip entries that exceed them. Clean up the target directory or partial file when extraction is rejected.

View issue in CodeCrow

def run_index() -> None:
try:
optional_generation_args = {}
if request.source_tree_sha256:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 HIGH | Bug Risk

Streaming endpoint accesses missing request fields

The streaming handler directly evaluates request.source_tree_sha256 and subsequently request.collection_target, but the visible IndexRequest definition contains only repo_path, workspace, project, branch, commit, preserve_other_branches, cleanup_repo_path, include_patterns, and exclude_patterns (Evidence RAG-d51e661f0c42ddcc). Unlike the ordinary endpoint, which uses getattr for these optional fields, this code raises AttributeError for a normal IndexRequest. The exception is caught inside the worker and emitted as an SSE error, so /index/repository/stream never performs indexing for requests using the current model contract.

💡 Suggested fix

Use getattr(request, "source_tree_sha256", None) and getattr(request, "collection_target", None) as in the ordinary endpoint, or add and validate these fields on IndexRequest before accessing them directly.

View issue in CodeCrow

toSave.add(new AnalyzedCommit(project, hash, AnalysisType.BRANCH_ANALYSIS));
AnalyzedCommit analyzed = new AnalyzedCommit(
project, hash, AnalysisType.BRANCH_ANALYSIS);
analyzed.setTargetBranch(targetBranch);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 MEDIUM | Code Quality

Branch-scoped receipts violate commit uniqueness

In multi-branch mode this code assigns a target branch and deliberately queries for existing rows scoped to that branch. However, the current AnalyzedCommit entity still has the unique constraint uq_analyzed_commit_project_hash on (project_id, commit_hash) (Evidence RAG-9f957c9fc710bda3). The same commit cannot therefore be recorded for two target branches, even though the new service logic treats those as independent receipts. saveAll will fail on the second branch, leaving branch coverage/provenance receipts missing and causing repeated analysis attempts.

💡 Suggested fix

Align the database uniqueness model with branch-scoped receipts, for example by making uniqueness include target_branch (while handling legacy null rows), and add the corresponding migration. Alternatively, do not create duplicate commit rows and retain a separate branch-context receipt table.

View issue in CodeCrow

List<VcsCommit> prCommits = selectPrEvidenceCommits(
newestFirst, commitHash, targetBaseRevision);
Collections.reverse(prCommits);
if (!prCommits.isEmpty()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 MEDIUM | Code Quality

Records commits beyond pull request head

When targetBaseRevision is available, the loop stops only at the target base and never stops at request.getCommitHash(). If the source branch has advanced beyond the PR head, getCommitHistory returns newer branch commits and they are added to prCommits, captured as PR evidence, and recorded as covered by this PR analysis. This misattributes SCM provenance and can suppress later analysis for commits that were not part of the pull request.

💡 Suggested fix

Stop collecting when the requested PR head commit is reached, and only then continue toward the target base if appropriate. Ensure the collected range is bounded to commits from the PR base through the requested PR head.

View issue in CodeCrow

Project project = new Project();
ReflectionTestUtils.setField(project, "id", 42L);
ProjectConfig config = new ProjectConfig();
config.setRagConfig(new RagConfig(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 MEDIUM | Testing

RagConfig test constructor has invalid arity

The added test constructs RagConfig with eight arguments. The visible RagConfig declaration has six record components and the visible constructors do not expose an eight-argument overload, so this test cannot compile as written.

💡 Suggested fix

Change the test to use the supported six-argument RagConfig constructor and configure retained branch patterns through the actual supported configuration object/API.

View issue in CodeCrow

}
}

private void publishReadableAliasesIfActive(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 MEDIUM | Bug Risk

Case-distinct branches share the same readable Qdrant alias

Case-distinct branches share the same readable Qdrant alias
The Java generation builder and reconciliation service publish or restore readable aliases whose names are generated by the Python index manager. The Python alias construction lowercases the sanitized branch name, so branches such as Feature/Login and feature/login produce the same alias. Both the normal publication path and the scheduled reconciliation path can therefore repeatedly rebind one alias to different active generations.
Evidence: BranchIndexGenerationBuildService invokes publishGenerationAliases after activation, while RagBranchOperatorAliasReconciliationService independently repairs aliases for active generations. The Python manager's branch alias normalization uses re.sub(...).lower(), which erases case distinctions before collision detection. Consequently, reconciliation can make an operator-facing alias resolve to the wrong branch even though the registry retains distinct branch generations.
Business impact: Queries or operator integrations using the readable alias for one case-distinct branch may retrieve the other branch's Qdrant contents. Concurrent generation publication and scheduled reconciliation can overwrite the alias target repeatedly.
Also affects: java-ecosystem/libs/rag-engine/src/main/java/org/rostilos/codecrow/ragengine/branch/RagBranchOperatorAliasReconciliationService.java, python-ecosystem/rag-pipeline/src/rag_pipeline/core/index_manager/manager.py

💡 Suggested fix

Use a collision-safe alias identity shared by Java and Python. Preserve case distinctions in the collision check or always append a deterministic branch-name hash when normalization changes the raw branch name; apply the same algorithm in normal publication and reconciliation, and add tests for Feature/Login versus feature/login.

View issue in CodeCrow

Boolean multiBranchEnabled,
Integer branchRetentionDays) {
return updateRagConfig(workspaceId, projectId, enabled, branch, includePatterns, excludePatterns,
multiBranchEnabled, branchRetentionDays, null, null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 MEDIUM | Bug Risk

Compatibility updates erase branch settings

The backward-compatible updateRagConfig overload delegates with null, null for the newly added indexedBranches and transientBranchIndexesEnabled fields. The main overload then constructs a new RagConfig from those values, so any caller still using the old signature silently clears previously configured retained branches and transient-index settings whenever it updates an unrelated RAG option. The shorter compatibility overload has the same behavior.

💡 Suggested fix

When the new arguments are null, preserve the corresponding values from the existing RagConfig instead of writing nulls. Alternatively, make the compatibility overloads read the current configuration and pass its indexed-branch and transient-index values explicitly.

View issue in CodeCrow

Add bounded archive extraction, case-safe branch aliases, preserve RAG settings through compatibility updates, and clean up all registered branch generations.
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.

1 participant