feat(ai): centralize durable usage
CI and Deploy / test (pull_request) Successful in 5m19s
CI and Deploy / deploy (pull_request) Has been skipped

Add a content-free usage ledger with legacy backfill. Reserve Workspace and durable Strategy/CV work before execution so deleted history or duplicate admission cannot reset limits.
This commit is contained in:
cesnimda
2026-08-15 20:03:06 +02:00
parent dbff0f8d49
commit 134aac7bcf
28 changed files with 3539 additions and 83 deletions
@@ -51,6 +51,9 @@ public sealed class CvProcessingOperationTests
Assert.Equal(CvProcessingQueue.TaskType, operation.TaskType);
Assert.Equal(CvProcessingQueue.SubjectType, operation.SubjectType);
Assert.DoesNotContain("Ada", operation.SubjectId ?? string.Empty, StringComparison.OrdinalIgnoreCase);
var usage = Assert.Single(await db.AiUsageRecords.IgnoreQueryFilters().ToListAsync());
Assert.Equal(operation.Id.ToString("D"), usage.SourceId);
Assert.Equal(AiUsageMeter.ReservationFor(CvProcessingQueue.TaskType).EstimatedTokens, usage.EstimatedTokenCount);
}
Assert.True(await fixture.Provider.GetRequiredService<AiOperationWorker>().RunOnceAsync(default));
@@ -68,6 +71,8 @@ public sealed class CvProcessingOperationTests
Assert.Null(user.ProfileCvStructureJson);
Assert.Null(user.CurrentCvExtractionRunId);
Assert.Equal("operation_succeeded", (await db.UserNotifications.IgnoreQueryFilters().SingleAsync()).Kind);
Assert.Equal(AiUsageMeter.ReservationFor(CvProcessingQueue.TaskType).EstimatedTokens,
(await db.AiUsageRecords.IgnoreQueryFilters().SingleAsync()).EstimatedTokenCount);
}
}
@@ -263,6 +268,7 @@ public sealed class CvProcessingOperationTests
services.AddSingleton<AiPrivacyPolicy>();
services.AddSingleton<AiOperationExecutionScope>();
services.AddScoped<UserOperationStore>();
services.AddScoped<AiUsageMeter>();
services.AddScoped<AiOperationAdmission>();
services.AddScoped<ICvProcessingQueue, CvProcessingQueue>();
services.AddTransient<ProfileCvController>();