fix(cv): gate AI proposals on grounding and source freshness

This commit is contained in:
cesnimda
2026-10-08 14:28:06 +02:00
parent d8d262e4d1
commit 2398ff354e
27 changed files with 2553 additions and 157 deletions
@@ -52,6 +52,37 @@ public sealed class MeteredSummarizerServiceTests
Assert.Equal(1, fixture.Handler.RequestCount);
}
[Fact]
public async Task Cv_factual_review_is_admitted_and_finalized_by_the_shared_usage_gate()
{
await using var fixture = await Fixture.CreateAsync("{\"assessment\":{\"verdict\":\"unknown\",\"coverageComplete\":false,\"claims\":[]}}");
var result = await fixture.Service.EvaluateCvRewriteAsync("Built APIs.", "Developed APIs.", "Requires SQL.");
Assert.NotNull(result);
var usage = Assert.Single(await fixture.Db.AiUsageRecords.AsNoTracking().ToListAsync());
Assert.Equal("synchronous.cv-factual-review", usage.TaskType);
Assert.Equal("Built APIs.\nDeveloped APIs.\nRequires SQL.".Length, usage.InputCharacterCount);
Assert.Equal(result.Text.Length, usage.OutputCharacterCount);
Assert.Equal(1, fixture.Handler.RequestCount);
}
[Fact]
public async Task Exhausted_limit_rejects_cv_factual_review_before_provider_call()
{
await using var fixture = await Fixture.CreateAsync("{\"assessment\":{}}");
fixture.Db.AiUsageRecords.Add(new AiUsageRecord
{
OwnerUserId = "owner", SourceType = "synthetic", SourceId = "review-limit", TaskType = "synthetic",
CallCount = 250, EstimatedTokenCount = 1, CreatedAtUtc = DateTimeOffset.UtcNow,
});
await fixture.Db.SaveChangesAsync();
var error = await Assert.ThrowsAsync<AiUsageLimitException>(() => fixture.Service.EvaluateCvRewriteAsync("Built APIs.", "Developed APIs.", null));
Assert.Equal("monthly_ai_calls_exhausted", error.Code);
Assert.Equal(0, fixture.Handler.RequestCount);
}
[Fact]
public async Task Exhausted_limit_rejects_before_provider_call()
{