fix(MULTIVERSION-0001): prevent version jobs from overwriting repo-wide NULL rules entry
Version jobs now write rules only to the version-specific (repo, versionId) row. Previously every version job unconditionally wrote to the (repo, NULL) row as well, causing whichever version indexed last to contaminate the repo-wide rules that the context API merges into every query response. Adds a regression test (Bug5b) that indexes the main branch, then indexes a version with different rules, and asserts the NULL row still holds the main-branch rules. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -869,15 +869,17 @@ describe('IndexingPipeline', () => {
|
||||
|
||||
await pipeline.run(job as never);
|
||||
|
||||
// Repo-wide row (version_id IS NULL) must exist.
|
||||
// Repo-wide row (version_id IS NULL) must NOT be written by a version job —
|
||||
// writing it here would contaminate the NULL entry with version-specific rules
|
||||
// (Bug 5b regression guard).
|
||||
const repoRow = db
|
||||
.prepare(
|
||||
`SELECT rules FROM repository_configs WHERE repository_id = '/test/repo' AND version_id IS NULL`
|
||||
)
|
||||
.get() as { rules: string } | undefined;
|
||||
expect(repoRow).toBeDefined();
|
||||
expect(repoRow).toBeUndefined();
|
||||
|
||||
// Version-specific row must also exist.
|
||||
// Version-specific row must exist with the correct rules.
|
||||
const versionRow = db
|
||||
.prepare(
|
||||
`SELECT rules FROM repository_configs WHERE repository_id = '/test/repo' AND version_id = ?`
|
||||
@@ -888,6 +890,69 @@ describe('IndexingPipeline', () => {
|
||||
expect(rules).toEqual(['This is v2. Use the new Builder API.']);
|
||||
});
|
||||
|
||||
it('regression(Bug5b): version job does not overwrite the repo-wide NULL rules entry', async () => {
|
||||
// Arrange: index the main branch first to establish a repo-wide rules entry.
|
||||
const mainBranchRules = ['Always use TypeScript strict mode.'];
|
||||
const mainPipeline = makePipeline({
|
||||
files: [
|
||||
{
|
||||
path: 'trueref.json',
|
||||
content: JSON.stringify({ rules: mainBranchRules }),
|
||||
sha: 'sha-main-config',
|
||||
language: 'json'
|
||||
}
|
||||
],
|
||||
totalFiles: 1
|
||||
});
|
||||
const mainJob = makeJob('/test/repo'); // no versionId → main-branch job
|
||||
await mainPipeline.run(mainJob as never);
|
||||
|
||||
// Confirm the repo-wide entry was written.
|
||||
const afterMain = db
|
||||
.prepare(
|
||||
`SELECT rules FROM repository_configs WHERE repository_id = '/test/repo' AND version_id IS NULL`
|
||||
)
|
||||
.get() as { rules: string } | undefined;
|
||||
expect(afterMain).toBeDefined();
|
||||
expect(JSON.parse(afterMain!.rules)).toEqual(mainBranchRules);
|
||||
|
||||
// Act: index a version with different rules.
|
||||
const versionId = insertVersion(db, { tag: 'v3.0.0', state: 'pending' });
|
||||
const versionRules = ['v3 only: use the streaming API.'];
|
||||
const versionPipeline = makePipeline({
|
||||
files: [
|
||||
{
|
||||
path: 'trueref.json',
|
||||
content: JSON.stringify({ rules: versionRules }),
|
||||
sha: 'sha-v3-config',
|
||||
language: 'json'
|
||||
}
|
||||
],
|
||||
totalFiles: 1
|
||||
});
|
||||
const versionJob = makeJob('/test/repo', versionId);
|
||||
await versionPipeline.run(versionJob as never);
|
||||
|
||||
// Assert: the repo-wide NULL entry must still contain the main-branch rules,
|
||||
// not the version-specific ones.
|
||||
const afterVersion = db
|
||||
.prepare(
|
||||
`SELECT rules FROM repository_configs WHERE repository_id = '/test/repo' AND version_id IS NULL`
|
||||
)
|
||||
.get() as { rules: string } | undefined;
|
||||
expect(afterVersion).toBeDefined();
|
||||
expect(JSON.parse(afterVersion!.rules)).toEqual(mainBranchRules);
|
||||
|
||||
// And the version-specific row must contain the version rules.
|
||||
const versionRow = db
|
||||
.prepare(
|
||||
`SELECT rules FROM repository_configs WHERE repository_id = '/test/repo' AND version_id = ?`
|
||||
)
|
||||
.get(versionId) as { rules: string } | undefined;
|
||||
expect(versionRow).toBeDefined();
|
||||
expect(JSON.parse(versionRow!.rules)).toEqual(versionRules);
|
||||
});
|
||||
|
||||
it('persists rules from CrawlResult.config even when trueref.json is absent from files (folders allowlist bug)', async () => {
|
||||
// Regression test for MULTIVERSION-0001:
|
||||
// When trueref.json specifies a `folders` allowlist (e.g. ["src/"]),
|
||||
|
||||
@@ -276,10 +276,13 @@ export class IndexingPipeline {
|
||||
|
||||
// ---- Stage 6: Persist rules from config ----------------------------
|
||||
if (parsedConfig?.config.rules?.length) {
|
||||
// Repo-wide rules (versionId = null).
|
||||
this.upsertRepoConfig(repo.id, null, parsedConfig.config.rules);
|
||||
// Version-specific rules stored separately when indexing a version.
|
||||
if (normJob.versionId) {
|
||||
if (!normJob.versionId) {
|
||||
// Main-branch job: write the repo-wide entry only.
|
||||
this.upsertRepoConfig(repo.id, null, parsedConfig.config.rules);
|
||||
} else {
|
||||
// Version job: write only the version-specific entry.
|
||||
// Writing to the NULL row here would overwrite repo-wide rules
|
||||
// with whatever the last-indexed version happened to carry.
|
||||
this.upsertRepoConfig(repo.id, normJob.versionId, parsedConfig.config.rules);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user