From 492950bdd044bdcedc02e8a57578d97ac565c341 Mon Sep 17 00:00:00 2001 From: Rustam Khalimov Date: Fri, 11 Sep 2026 13:22:56 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D1=82=D1=84=D0=BE=D1=80=D0=BC=D0=B0?= =?UTF-8?q?=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D1=81=D0=BF?= =?UTF-8?q?=D0=B8=D1=81=D0=BA=D0=B8=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=B2=20=D0=BF=D0=BE=20=D0=BA=D0=BE=D0=B4-?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D0=B9=D0=BB=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Больше двух параметров — каждый на отдельной строке (закрывающая скобка в конце последнего); два и меньше — в одну строку. Правило добавлено в docs/spec/Код-стайл-Дейл.md; применено к 628 сигнатурам в 253 файлах. --- docs/spec/Код-стайл-Дейл.md | 15 ++++ .../Deal.Ai.Tests/AiServiceHostTests.cs | 10 ++- src/ai-service/Deal.Ai.Tests/AiTestHost.cs | 4 +- .../Deal.Ai.Tests/StubHttpMessageHandler.cs | 8 +- src/ai-service/Deal.Ai/AiServiceImpl.cs | 22 +++-- .../Deal.Ai/Llm/LlmCallException.cs | 5 +- src/ai-service/Deal.Ai/Llm/LlmHttpClient.cs | 15 +++- src/ai-service/Deal.Ai/Llm/TokenEstimator.cs | 5 +- .../Deal.Api/Endpoints/AiCheckEndpoint.cs | 5 +- .../Endpoints/CardDetailsEndpoints.cs | 65 ++++++++++++--- src/core/Deal.Api/Endpoints/CardsEndpoints.cs | 73 +++++++++++++---- .../Deal.Api/Endpoints/ContainersEndpoints.cs | 37 +++++++-- .../Deal.Api/Endpoints/DiscoveryEndpoints.cs | 57 ++++++++++--- src/core/Deal.Api/Endpoints/EventsEndpoint.cs | 5 +- .../Endpoints/FilterTesterEndpoints.cs | 5 +- src/core/Deal.Api/Endpoints/MlEndpoints.cs | 15 +++- .../Endpoints/OperatorAuthEndpoints.cs | 5 +- .../Deal.Api/Endpoints/PipelineEndpoints.cs | 23 +++++- .../Deal.Api/Endpoints/TelegramEndpoints.cs | 36 +++++++-- src/core/Deal.Api/Events/SseBroker.cs | 5 +- src/core/Deal.Api/Http/AuditAppender.cs | 12 ++- src/core/Deal.Api/Http/LoginAttemptGuard.cs | 20 ++++- src/core/Deal.Api/Http/SessionCookieWriter.cs | 5 +- .../Middleware/HttpAccessLogMiddleware.cs | 5 +- .../Middleware/OperatorSessionMiddleware.cs | 4 +- .../Deal.Api/Middleware/RateLimitPolicies.cs | 10 ++- .../Services/TelegramBackfillScheduler.cs | 4 +- .../Telegram/RpcCallLoggingInterceptor.cs | 5 +- .../Telegram/TelegramIngressService.cs | 15 +++- .../Deal.Api/Telegram/TelegramKeysService.cs | 5 +- .../Deal.Contracts/Integrations/IAiTools.cs | 5 +- .../Integrations/IFileStorage.cs | 6 +- .../Deal.Contracts/Integrations/IMlClient.cs | 6 +- .../Integrations/ITelegramGateway.cs | 36 +++++++-- .../Integrations/Models/MtlsCertificates.cs | 16 +++- .../Services/AiConnectionChecker.cs | 16 +++- .../Integrations/Services/BudgetedAiTools.cs | 5 +- .../Integrations/Services/GrpcAiTools.cs | 5 +- .../Integrations/Services/GrpcMlClient.cs | 11 ++- .../Services/GrpcTelegramClient.cs | 45 ++++++++--- .../Integrations/Services/LocalAiTools.cs | 5 +- .../Integrations/Services/LocalMlClient.cs | 6 +- .../Services/LocalTelegramGateway.cs | 36 +++++++-- .../Integrations/Services/MlOutboxQueue.cs | 7 +- .../Integrations/Services/MlStatusCache.cs | 5 +- .../Services/TokenUsageRecorder.cs | 12 ++- .../Storage/Services/LocalFileStorage.cs | 6 +- .../Storage/Services/MinioFileStorage.cs | 6 +- .../Persistence/Repositories/AuthStore.cs | 5 +- .../Repositories/DiscoveryStore.Blacklist.cs | 6 +- .../Repositories/DiscoveryStore.Candidates.cs | 20 ++++- .../Repositories/DiscoveryStore.Logs.cs | 17 +++- .../Repositories/DiscoveryStore.Tasks.cs | 22 ++++- .../Repositories/GlobalSettingsStore.cs | 5 +- .../Persistence/Repositories/InviteStore.cs | 11 ++- .../Repositories/KanbanStore.Cards.cs | 15 +++- .../Repositories/KanbanStore.Comments.cs | 7 +- .../Repositories/KanbanStore.Containers.cs | 5 +- .../Repositories/KanbanStore.Selected.cs | 36 +++++++-- .../Repositories/KanbanStore.StorageRules.cs | 12 ++- .../Repositories/MlLearningStore.cs | 7 +- .../Persistence/Repositories/PipelineStore.cs | 37 +++++++-- .../Repositories/RateLimitCounterStore.cs | 22 ++++- .../Persistence/Repositories/SettingsStore.cs | 5 +- .../Persistence/Repositories/TelegramStore.cs | 33 ++++++-- .../Repositories/TenantLimitStore.cs | 20 ++++- .../Repositories/TenantRepository.cs | 5 +- .../Repositories/TokenUsageEventStore.cs | 6 +- .../ServiceCollectionExtensions.cs | 10 ++- .../Deal.Infrastructure/Services/CardMover.cs | 5 +- .../Tenancy/DefaultContainerProvisioner.cs | 17 +++- .../Tenancy/TenantProvisioningService.cs | 5 +- .../Application/Abstractions/ICardMover.cs | 5 +- .../Application/Models/DefaultContainer.cs | 8 +- .../Abstractions/IDiscoveryStore.cs | 65 ++++++++++++--- .../Application/Services/DiscoveryBanGuard.cs | 5 +- .../Services/DiscoveryBlacklistService.cs | 6 +- .../Services/DiscoveryCandidatesService.cs | 33 ++++++-- .../Services/DiscoveryEvaluator.cs | 19 ++++- .../Services/DiscoveryLogService.cs | 11 ++- .../Services/DiscoveryPlanGuard.cs | 5 +- .../Services/DiscoveryTasksService.cs | 16 +++- .../DiscoveryWorkerService.Evaluate.cs | 4 +- .../Services/DiscoveryWorkerService.Join.cs | 4 +- .../Application/Abstractions/ICardStore.cs | 76 ++++++++++++++---- .../Abstractions/IMlLearningStore.cs | 7 +- .../Application/ColumnRules/AmountParser.cs | 12 ++- .../Application/ColumnRules/BudgetInRange.cs | 5 +- .../Application/ColumnRules/ColumnMatcher.cs | 5 +- .../Application/ColumnRules/ColumnRules.cs | 15 +++- .../ColumnRules/MatchHitBuilder.cs | 10 ++- .../Application/ColumnRules/RulesDescriber.cs | 5 +- .../Application/Services/BudgetNormalizer.cs | 6 +- .../Services/CardsService.Files.cs | 15 +++- .../Services/CardsService.Helpers.cs | 24 +++++- .../Services/CardsService.Operations.cs | 20 ++++- .../Services/CardsService.Reminders.cs | 5 +- .../Services/CardsService.Selected.cs | 21 ++++- .../Application/Services/CardsService.cs | 6 +- .../Application/Services/ContainersService.cs | 15 +++- .../Application/Services/SuggestHeuristics.cs | 4 +- .../Abstractions/IPipelineStore.cs | 37 +++++++-- .../Application/Parse/LocalFieldsParser.cs | 5 +- .../Application/Parse/MessageTextCleaner.cs | 5 +- .../Application/Parse/SummaryComposer.cs | 5 +- .../Services/AiClassifyContextBuilder.cs | 5 +- .../Application/Services/CardComposer.cs | 5 +- .../Application/Services/CardReclassifier.cs | 22 ++++- .../Application/Services/MlReviewService.cs | 29 +++++-- .../Services/PipelineProcessingService.cs | 11 ++- .../Services/PipelineWorkerService.Checks.cs | 6 +- .../PipelineWorkerService.Decisions.cs | 5 +- .../PipelineWorkerService.Learning.cs | 6 +- .../Services/PipelineWorkerService.Pump.cs | 10 ++- .../PipelineWorkerService.Rejections.cs | 19 ++++- .../Abstractions/IGlobalSettingsStore.cs | 5 +- .../Abstractions/ISettingsStore.cs | 5 +- .../Application/Models/IncomingRules.cs | 5 +- .../Application/Services/PromptFiller.cs | 5 +- .../SettingsService.PatchMyPrompts.cs | 5 +- .../SettingsService.PatchScalarKeys.cs | 14 ++-- .../Services/SettingsService.ReadMerge.cs | 29 +++++-- .../Application/DialogsService.cs | 28 +++++-- .../Application/ITelegramStore.cs | 33 ++++++-- .../Application/Abstractions/IAuthStore.cs | 5 +- .../Application/Abstractions/IInviteStore.cs | 11 ++- .../Abstractions/IRateLimitCounterStore.cs | 11 ++- .../Abstractions/ITenantLimitStore.cs | 16 +++- .../Abstractions/ITenantRepository.cs | 5 +- .../Application/Services/AnalyticsService.cs | 16 +++- .../Application/Services/AuthService.cs | 11 ++- .../Application/Services/InvitesService.cs | 5 +- .../Services/OperatorAuthService.cs | 5 +- .../Services/SuspiciousActivityService.cs | 5 +- .../Services/TenantAdminService.cs | 10 ++- .../Application/Services/TenantService.cs | 5 +- .../Services/TokenBudgetService.cs | 5 +- .../AdminTickOrchestratorTests.cs | 11 ++- .../AiClassifyContextBuilderTests.cs | 6 +- .../Deal.Tests.Unit/AuditLogStoreTests.cs | 6 +- .../tests/Deal.Tests.Unit/AuthStoreTests.cs | 5 +- .../Deal.Tests.Unit/CardReclassifierTests.cs | 12 ++- .../CardsServiceRemindersTests.cs | 6 +- .../Deal.Tests.Unit/CardsServiceTests.cs | 5 +- .../ConversionRecomputerTests.cs | 14 +++- .../DataRetentionSchedulerTests.cs | 4 +- .../Deal.Tests.Unit/DialogsServiceTests.cs | 14 +++- .../DiscoveryEvaluatorTests.cs | 8 +- .../DiscoveryWorkerSchedulerTests.cs | 20 ++++- .../DiscoveryWorkerServiceTests.cs | 19 ++++- src/core/tests/Deal.Tests.Unit/FakeAiTools.cs | 5 +- .../tests/Deal.Tests.Unit/FakeAuthStore.cs | 5 +- .../Deal.Tests.Unit/FakeDiscoveryGateway.cs | 36 +++++++-- .../Deal.Tests.Unit/FakeDiscoveryStore.cs | 70 +++++++++++++--- .../tests/Deal.Tests.Unit/FakeFileStorage.cs | 6 +- .../FakeGlobalSettingsStore.cs | 5 +- .../tests/Deal.Tests.Unit/FakeInviteStore.cs | 11 ++- .../tests/Deal.Tests.Unit/FakeKanjStore.cs | 75 +++++++++++++---- .../tests/Deal.Tests.Unit/FakeMlClient.cs | 6 +- .../Deal.Tests.Unit/FakeMlLearningStore.cs | 13 ++- .../Deal.Tests.Unit/FakePipelineStore.cs | 42 ++++++++-- .../FakeRateLimitCounterStore.cs | 11 ++- .../Deal.Tests.Unit/FakeSettingsStore.cs | 5 +- .../Deal.Tests.Unit/FakeTelegramGateway.cs | 36 +++++++-- .../Deal.Tests.Unit/FakeTelegramStore.cs | 33 ++++++-- .../Deal.Tests.Unit/FakeTenantLimitStore.cs | 16 +++- .../Deal.Tests.Unit/FakeTenantRegistry.cs | 5 +- .../Deal.Tests.Unit/FakeTenantRepository.cs | 5 +- .../tests/Deal.Tests.Unit/FakeTenantStore.cs | 5 +- .../FakeTokenUsageEventStore.cs | 3 +- .../Deal.Tests.Unit/FileKindDetectorTests.cs | 6 +- .../ForwardedHeadersHttpTests.cs | 5 +- .../Deal.Tests.Unit/GrpcAiClassifierTests.cs | 10 ++- .../tests/Deal.Tests.Unit/GrpcAiToolsTests.cs | 5 +- .../Deal.Tests.Unit/IntegrationsDiTests.cs | 4 +- .../Deal.Tests.Unit/JoinEndpointHttpTests.cs | 16 +++- .../tests/Deal.Tests.Unit/JoinFlowTests.cs | 5 +- .../LocalColumnSuggesterTests.cs | 6 +- .../LoginAttemptEndpointHttpTests.cs | 5 +- .../MlOutboxFlushSchedulerTests.cs | 5 +- .../Deal.Tests.Unit/MlReviewServiceTests.cs | 16 +++- .../Deal.Tests.Unit/MtlsCertificatesTests.cs | 5 +- .../OperatorAnalyticsEndpointsHttpTests.cs | 12 ++- .../OperatorAuditEndpointsHttpTests.cs | 5 +- .../OperatorAuthEndpointsHttpTests.cs | 5 +- .../OperatorBootstrapHostedServiceTests.cs | 7 +- .../OperatorHealthEndpointsHttpTests.cs | 8 +- .../OperatorInvitesEndpointsHttpTests.cs | 10 ++- .../OperatorLimitsEndpointsHelpersTests.cs | 5 +- .../OperatorLimitsEndpointsHttpTests.cs | 14 ++-- .../OperatorMaintenanceEndpointsHttpTests.cs | 4 +- .../OperatorSettingsEndpointsHttpTests.cs | 14 ++-- .../OperatorTenantsEndpointsHttpTests.cs | 20 ++++- .../Deal.Tests.Unit/OriginGuardHttpTests.cs | 6 +- .../PipelineCardWriterTests.cs | 5 +- .../PipelineProcessingServiceTests.cs | 11 ++- .../PipelineWorkerGrpcAiTests.cs | 5 +- .../PipelineWorkerSchedulerTests.cs | 17 +++- .../PipelineWorkerServiceTests.cs | 6 +- .../Deal.Tests.Unit/PromptDefaultsTests.cs | 5 +- .../Deal.Tests.Unit/RatesServiceTests.cs | 6 +- .../Deal.Tests.Unit/RecordingAiService.cs | 3 +- .../ServiceHealthProbeTests.cs | 4 +- .../StorageTickSchedulerTests.cs | 21 ++++- .../StorageTickServiceTests.cs | 5 +- .../Deal.Tests.Unit/SuggestHeuristicsTests.cs | 8 +- .../SuspiciousActivityServiceTests.cs | 6 +- .../TelegramIngressServiceTests.cs | 16 +++- .../Deal.Tests.Unit/TenantLimitStoreTests.cs | 15 +++- .../TenantSchemaMigrationServiceTests.cs | 4 +- .../Deal.Tests.Unit/TgStatusServiceTests.cs | 11 ++- .../Deal.Grpc.Hosting/MtlsCertificates.cs | 16 +++- .../RpcCallLoggingInterceptor.cs | 5 +- src/ml-service/Deal.Ml.Tests/LearningData.cs | 10 ++- .../Deal.Ml.Tests/MlServiceHostTests.cs | 10 ++- src/ml-service/Deal.Ml/MlServiceImpl.cs | 5 +- .../Deal.Ml/Model/OnlineNaiveBayes.cs | 5 +- src/ml-service/Deal.Ml/Model/TenantModel.cs | 10 ++- src/ml-service/Deal.Ml/Storage/MlDb.cs | 10 ++- .../BackfillServiceTests.cs | 10 ++- .../Deal.Telegram.Tests/DialogHueTests.cs | 5 +- .../Deal.Telegram.Tests/DiscoveryOpsTests.cs | 6 +- .../Deal.Telegram.Tests/DiscoveryRpcTests.cs | 10 ++- .../Deal.Telegram.Tests/FakeSessionClient.cs | 25 ++++-- .../Deal.Telegram.Tests/FarmHarness.cs | 6 +- .../RealtimeListenerTests.cs | 5 +- .../Deal.Telegram.Tests/RealtimeSweepTests.cs | 5 +- .../Deal.Telegram.Tests/SessionHarness.cs | 6 +- .../TelegramServiceHostTests.cs | 10 ++- .../Deal.Telegram.Tests/TenantSessionTests.cs | 6 +- .../Deal.Telegram.Tests/TestDoubles.cs | 15 +++- .../TlMessageMapperTests.cs | 5 +- .../Deal.Telegram/Core/CoreIngressClient.cs | 15 +++- .../Deal.Telegram/Core/ICoreIngressClient.cs | 10 ++- .../Deal.Telegram/Dialogs/BackfillService.cs | 11 ++- .../Deal.Telegram/Dialogs/DialogCatalog.cs | 5 +- .../Deal.Telegram/Dialogs/IBackfillPacer.cs | 5 +- .../Dialogs/RandomBackfillPacer.cs | 5 +- .../Deal.Telegram/Dialogs/RealtimeSweep.cs | 11 ++- .../Deal.Telegram/Discovery/DiscoveryOps.cs | 32 ++++++-- .../Sessions/SessionException.cs | 5 +- .../Deal.Telegram/Sessions/SessionFarm.cs | 66 ++++++++++++--- .../Deal.Telegram/Sessions/SessionStore.cs | 10 ++- .../Deal.Telegram/Sessions/TenantSession.cs | 36 +++++++-- .../Deal.Telegram/Telegram/ClientFactory.cs | 5 +- .../Telegram/DiscoveryMessage.cs | 7 +- .../Telegram/DiscoveryReadResult.cs | 5 +- .../Deal.Telegram/Telegram/ISessionClient.cs | 15 +++- .../Telegram/ITelegramClientFactory.cs | 5 +- .../Deal.Telegram/Telegram/TelegramDialog.cs | 8 +- .../Deal.Telegram/Telegram/TelegramMessage.cs | 8 +- .../Telegram/TelegramSourceInfo.cs | 8 +- .../Deal.Telegram/Telegram/TlMessageMapper.cs | 21 ++++- .../Telegram/WTelegramSessionClient.cs | 80 +++++++++++++++---- .../Deal.Telegram/TelegramServiceImpl.cs | 5 +- 255 files changed, 2672 insertions(+), 668 deletions(-) diff --git a/docs/spec/Код-стайл-Дейл.md b/docs/spec/Код-стайл-Дейл.md index 329e5f2..0e4e80b 100644 --- a/docs/spec/Код-стайл-Дейл.md +++ b/docs/spec/Код-стайл-Дейл.md @@ -165,6 +165,21 @@ - Функции, возвращающие массив/коллекцию, всегда возвращают массив/коллекцию: если данных нет — пустой экземпляр, но не `null`. - Не более 7 параметров у функции. Больше — объединять в класс/DTO. +- **Перенос параметров:** если параметров **больше двух** — каждый на **отдельной строке** (открывающая `(` — в конце первой строки, закрывающая `)` — на отдельной строке с отступом объявления); если **два или меньше** — все параметры **в одну строку**. + + Больше двух: + ```csharp + public async Task MoveAsync( + string cardId, + string toContainerId, + TransitionContext ctx, + CancellationToken ct) + ``` + + Два или меньше: + ```csharp + public User FindUser(string login, CancellationToken ct) { } + ``` ## 8. Управление выполнением программы diff --git a/src/ai-service/Deal.Ai.Tests/AiServiceHostTests.cs b/src/ai-service/Deal.Ai.Tests/AiServiceHostTests.cs index 48126b1..ceb63d5 100644 --- a/src/ai-service/Deal.Ai.Tests/AiServiceHostTests.cs +++ b/src/ai-service/Deal.Ai.Tests/AiServiceHostTests.cs @@ -128,7 +128,10 @@ public sealed class AiServiceHostTests // serviceToken: Ожидаемый токен хоста (env DEAL_SERVICE_TOKEN). // tokenHeader: Значение metadata «service-token» запроса либо null (нет заголовка). // expected: Ожидаемый StatusCode ответа. - private static async Task AssertDealRpcRejectedAsync(string? serviceToken, string? tokenHeader, StatusCode expected) + private static async Task AssertDealRpcRejectedAsync( + string? serviceToken, + string? tokenHeader, + StatusCode expected) { await RunHostScenarioAsync( serviceToken, @@ -141,7 +144,10 @@ public sealed class AiServiceHostTests // channel: Канал к хосту ai-service. // tokenHeader: Значение metadata «service-token» либо null (нет заголовка). // expected: Ожидаемый StatusCode. - private static async Task AssertRejectedAsync(GrpcChannel channel, string? tokenHeader, StatusCode expected) + private static async Task AssertRejectedAsync( + GrpcChannel channel, + string? tokenHeader, + StatusCode expected) { var client = new AiService.AiServiceClient(channel); Metadata metadata = new() { { "tenant-id", TenantId } }; diff --git a/src/ai-service/Deal.Ai.Tests/AiTestHost.cs b/src/ai-service/Deal.Ai.Tests/AiTestHost.cs index f409485..00c00d3 100644 --- a/src/ai-service/Deal.Ai.Tests/AiTestHost.cs +++ b/src/ai-service/Deal.Ai.Tests/AiTestHost.cs @@ -52,9 +52,7 @@ internal static class AiTestHost /// /// Настройка DI сценария (фейк-провайдер, мгновенные ретраи). /// Сценарий с gRPC-каналом к хосту. - public static Task RunAsync( - Action configureServices, - Func scenario) + public static Task RunAsync(Action configureServices, Func scenario) => RunAsync(DefaultToken, configureServices, scenario); /// diff --git a/src/ai-service/Deal.Ai.Tests/StubHttpMessageHandler.cs b/src/ai-service/Deal.Ai.Tests/StubHttpMessageHandler.cs index 8fd106e..26c9464 100644 --- a/src/ai-service/Deal.Ai.Tests/StubHttpMessageHandler.cs +++ b/src/ai-service/Deal.Ai.Tests/StubHttpMessageHandler.cs @@ -26,9 +26,7 @@ internal sealed class StubHttpMessageHandler : HttpMessageHandler /// /// Ответчик: request → response. /// Необязательная задержка ответа (тест таймаута). - public StubHttpMessageHandler( - Func responder, - TimeSpan? delay = null) + public StubHttpMessageHandler(Func responder, TimeSpan? delay = null) { _responder = responder; _delay = delay; @@ -45,9 +43,7 @@ internal sealed class StubHttpMessageHandler : HttpMessageHandler public CapturedHttpRequest LastRequest => Requests[^1]; /// - protected override async Task SendAsync( - HttpRequestMessage request, - CancellationToken cancellationToken) + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { string? body = request.Content is null ? null diff --git a/src/ai-service/Deal.Ai/AiServiceImpl.cs b/src/ai-service/Deal.Ai/AiServiceImpl.cs index ef665c8..c4e5d1b 100644 --- a/src/ai-service/Deal.Ai/AiServiceImpl.cs +++ b/src/ai-service/Deal.Ai/AiServiceImpl.cs @@ -239,8 +239,7 @@ public sealed class AiServiceImpl : AiService.AiServiceBase /// L36–47 + описание): ответ {keywords}. Очистку (_clean_keywords: ≤30, ≤60 симв., дедуп) и мягкую /// ошибку для UI делает ядро (Ruling 11); недоступность провайдера — UNAVAILABLE. /// - public override async Task GenerateKeywords( - GenerateKeywordsRequest request, ServerCallContext context) + public override async Task GenerateKeywords(GenerateKeywordsRequest request, ServerCallContext context) { string tenantId = RequireTenantId(context); EnsureLengthAtMost(request.Description, MaxDescriptionLength, DescriptionTooLongDetail); @@ -277,8 +276,7 @@ public sealed class AiServiceImpl : AiService.AiServiceBase /// текст + описание + ключи задачи): ответ {fit, reason}. Ядро зовёт только при aiEnabled; /// сбой — фолбэк на эвристику (Ruling 10). /// - public override async Task EvaluateFit( - EvaluateFitRequest request, ServerCallContext context) + public override async Task EvaluateFit(EvaluateFitRequest request, ServerCallContext context) { string tenantId = RequireTenantId(context); EnsureLengthAtMost(request.Text, MaxTextLength, MessageTextTooLongDetail); @@ -333,7 +331,10 @@ public sealed class AiServiceImpl : AiService.AiServiceBase // value: Значение поля запроса (в proto строка не бывает null). // maxLength: Допустимый максимум символов. // detail: Текст отказа (detail RPC). - private static void EnsureLengthAtMost(string value, int maxLength, string detail) + private static void EnsureLengthAtMost( + string value, + int maxLength, + string detail) { if (value.Length > maxLength) { @@ -393,7 +394,11 @@ public sealed class AiServiceImpl : AiService.AiServiceBase // tenantId: Id тенанта. // config: Конфиг провайдера вызова. // callError: Итоговая ошибка фасада. - private void LogAiUnavailable(string method, string tenantId, LlmConfig config, LlmCallException callError) + private void LogAiUnavailable( + string method, + string tenantId, + LlmConfig config, + LlmCallException callError) => _logger.LogWarning( "Аудит: tenant {TenantId} {Method} → ИИ ({Provider}) недоступен ({Kind})", tenantId, @@ -412,7 +417,10 @@ public sealed class AiServiceImpl : AiService.AiServiceBase // json: Корневой объект ответа модели. // fieldName: Имя поля. // defaultValue: Значение при отсутствии/неразбираемости поля. - private static bool ReadBoolField(JsonObject json, string fieldName, bool defaultValue) + private static bool ReadBoolField( + JsonObject json, + string fieldName, + bool defaultValue) { if (json[fieldName] is not JsonValue value) { diff --git a/src/ai-service/Deal.Ai/Llm/LlmCallException.cs b/src/ai-service/Deal.Ai/Llm/LlmCallException.cs index a264226..b7a17be 100644 --- a/src/ai-service/Deal.Ai/Llm/LlmCallException.cs +++ b/src/ai-service/Deal.Ai/Llm/LlmCallException.cs @@ -16,7 +16,10 @@ public sealed class LlmCallException : Exception /// Причина исчерпания (см. ). /// Отображаемое имя провайдера (без ключей). /// Usage последней ответившей попытки (для Kind=AnswerNotJson; иначе null). - public LlmCallException(LlmCallFailureKind kind, string providerDisplayName, LlmUsage? usage = null) + public LlmCallException( + LlmCallFailureKind kind, + string providerDisplayName, + LlmUsage? usage = null) : base(string.Format(DetailTemplate, providerDisplayName)) { Kind = kind; diff --git a/src/ai-service/Deal.Ai/Llm/LlmHttpClient.cs b/src/ai-service/Deal.Ai/Llm/LlmHttpClient.cs index 0c3fd3a..9fddfae 100644 --- a/src/ai-service/Deal.Ai/Llm/LlmHttpClient.cs +++ b/src/ai-service/Deal.Ai/Llm/LlmHttpClient.cs @@ -58,7 +58,10 @@ public sealed class LlmHttpClient : IProviderClient // httpClient: HttpClient над фейковым обработчиком. // openAiCallTimeout: Таймаут OpenAI-совместимой попытки. // anthropicCallTimeout: Таймаут Anthropic-попытки. - internal LlmHttpClient(HttpClient httpClient, TimeSpan openAiCallTimeout, TimeSpan anthropicCallTimeout) + internal LlmHttpClient( + HttpClient httpClient, + TimeSpan openAiCallTimeout, + TimeSpan anthropicCallTimeout) { _httpClient = httpClient; _openAiCallTimeout = openAiCallTimeout; @@ -110,7 +113,10 @@ public sealed class LlmHttpClient : IProviderClient // config: Конфиг провайдера. // systemPrompt: Системный промпт. // userText: Пользовательское сообщение. - private static HttpRequestMessage BuildOpenAiRequest(LlmConfig config, string systemPrompt, string userText) + private static HttpRequestMessage BuildOpenAiRequest( + LlmConfig config, + string systemPrompt, + string userText) { var request = new HttpRequestMessage(HttpMethod.Post, NormalizeBaseUrl(config.BaseUrl) + OpenAiCompletionsPath); if (!string.IsNullOrEmpty(config.ApiKey)) @@ -137,7 +143,10 @@ public sealed class LlmHttpClient : IProviderClient // config: Конфиг провайдера. // systemPrompt: Системный промпт. // userText: Пользовательское сообщение. - private static HttpRequestMessage BuildAnthropicRequest(LlmConfig config, string systemPrompt, string userText) + private static HttpRequestMessage BuildAnthropicRequest( + LlmConfig config, + string systemPrompt, + string userText) { var request = new HttpRequestMessage(HttpMethod.Post, NormalizeBaseUrl(config.BaseUrl) + AnthropicMessagesPath); request.Headers.TryAddWithoutValidation(AnthropicApiKeyHeader, config.ApiKey ?? string.Empty); diff --git a/src/ai-service/Deal.Ai/Llm/TokenEstimator.cs b/src/ai-service/Deal.Ai/Llm/TokenEstimator.cs index 35fab16..bc54f4b 100644 --- a/src/ai-service/Deal.Ai/Llm/TokenEstimator.cs +++ b/src/ai-service/Deal.Ai/Llm/TokenEstimator.cs @@ -16,7 +16,10 @@ public static class TokenEstimator /// Usage из API-ответа (null — провайдер его не вернул). /// Полный текст запроса (система + пользователь) для оценки. /// Текст ответа модели для оценки. - public static LlmUsage Resolve(ProviderUsage? providerUsage, string promptText, string completionText) + public static LlmUsage Resolve( + ProviderUsage? providerUsage, + string promptText, + string completionText) { if (providerUsage is not null) { diff --git a/src/core/Deal.Api/Endpoints/AiCheckEndpoint.cs b/src/core/Deal.Api/Endpoints/AiCheckEndpoint.cs index 720cd14..281c8b3 100644 --- a/src/core/Deal.Api/Endpoints/AiCheckEndpoint.cs +++ b/src/core/Deal.Api/Endpoints/AiCheckEndpoint.cs @@ -128,7 +128,10 @@ public static class AiCheckEndpoint // providerId: Активный провайдер (id из каталога). // ct: Токен отмены. // Возвращает: Конфиг {apiKey, baseUrl, model}; повреждённая строка aiConfigs — дефолт. - private static async Task ReadEffectiveConfigAsync(ISettingsStore store, string providerId, CancellationToken ct) + private static async Task ReadEffectiveConfigAsync( + ISettingsStore store, + string providerId, + CancellationToken ct) { AiConfigSetting defaults = SettingsDefaults.AiConfigs[providerId]; SettingValue? row = await store.GetAsync(SettingsKeys.AiConfigs, ct); diff --git a/src/core/Deal.Api/Endpoints/CardDetailsEndpoints.cs b/src/core/Deal.Api/Endpoints/CardDetailsEndpoints.cs index 9a15fa0..b2815e0 100644 --- a/src/core/Deal.Api/Endpoints/CardDetailsEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/CardDetailsEndpoints.cs @@ -117,7 +117,10 @@ public static class CardDetailsEndpoints } // POST /api/cards: ручное создание «локальной» карточки. Ответ — созданная карточка. - private static async Task CreateCardAsync(CreateCardRequest body, HttpContext context, CancellationToken ct) + private static async Task CreateCardAsync( + CreateCardRequest body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -133,7 +136,10 @@ public static class CardDetailsEndpoints } // POST /api/cards/take {cardId}: «взять в работу» — перенос карточки в planned. Ответ — карточка. - private static async Task TakeCardAsync(TakeCardRequest body, HttpContext context, CancellationToken ct) + private static async Task TakeCardAsync( + TakeCardRequest body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -163,7 +169,10 @@ public static class CardDetailsEndpoints // PATCH /api/cards/{cardId}: точечная правка полей (title/summary/contact/tzText/stack/budget). // Тело читается как произвольный JSON-объект (presence-aware): явный null чистящих полей // (budget:null, stack:null) не теряется типизированным биндингом. Ответ — обновлённая карточка. - private static async Task PatchCardAsync(string cardId, HttpContext context, CancellationToken ct) + private static async Task PatchCardAsync( + string cardId, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -184,7 +193,11 @@ public static class CardDetailsEndpoints } // POST /api/cards/{cardId}/links {name?,url}: добавить ссылку. Ответ — карточка. - private static async Task AddLinkAsync(string cardId, CardLinkRequest body, HttpContext context, CancellationToken ct) + private static async Task AddLinkAsync( + string cardId, + CardLinkRequest body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -208,7 +221,11 @@ public static class CardDetailsEndpoints } // DELETE /api/cards/{cardId}/links/{linkId}: удалить ссылку. Ответ — карточка. - private static async Task RemoveLinkAsync(string cardId, string linkId, HttpContext context, CancellationToken ct) + private static async Task RemoveLinkAsync( + string cardId, + string linkId, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -225,7 +242,10 @@ public static class CardDetailsEndpoints // POST /api/cards/{cardId}/files: загрузка вложений (multipart/form-data, поле files). // Ответ — обновлённая карточка (с новыми files). Карточки нет → 404 до записи объектов. Каждый файл: // имя/ContentType/поток/длина → CardsService.AddFileAsync. Ранний null — гонка (404). - private static async Task UploadFilesAsync(string cardId, HttpContext context, CancellationToken ct) + private static async Task UploadFilesAsync( + string cardId, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -265,7 +285,11 @@ public static class CardDetailsEndpoints // GET /api/cards/{cardId}/files/{fileId}/download: поток содержимого вложения. // Карточки/записи нет → 404; пустой objectKey → 410; объекта нет в хранилище/сбой → 404. Ответ — поток // с Content-Length/Content-Type из дескриптора; Content-Disposition attachment, имя без кавычек. - private static async Task DownloadFileAsync(string cardId, string fileId, HttpContext context, CancellationToken ct) + private static async Task DownloadFileAsync( + string cardId, + string fileId, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -314,7 +338,11 @@ public static class CardDetailsEndpoints } // DELETE /api/cards/{cardId}/files/{fileId}: открепить файл. Ответ — карточка. - private static async Task RemoveFileAsync(string cardId, string fileId, HttpContext context, CancellationToken ct) + private static async Task RemoveFileAsync( + string cardId, + string fileId, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -329,7 +357,11 @@ public static class CardDetailsEndpoints } // POST /api/cards/{cardId}/reminder {at: epoch-ms}: установить напоминание. Ответ — карточка. - private static async Task SetReminderAsync(string cardId, ReminderSetRequest body, HttpContext context, CancellationToken ct) + private static async Task SetReminderAsync( + string cardId, + ReminderSetRequest body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -354,7 +386,10 @@ public static class CardDetailsEndpoints } // DELETE /api/cards/{cardId}/reminder: снять напоминание. Ответ — карточка. - private static async Task ClearReminderAsync(string cardId, HttpContext context, CancellationToken ct) + private static async Task ClearReminderAsync( + string cardId, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -368,7 +403,10 @@ public static class CardDetailsEndpoints } // POST /api/cards/{cardId}/reminder/snooze: «напомнить позже» (now + 24 ч). Ответ — карточка. - private static async Task SnoozeReminderAsync(string cardId, HttpContext context, CancellationToken ct) + private static async Task SnoozeReminderAsync( + string cardId, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -386,7 +424,10 @@ public static class CardDetailsEndpoints // cardId: Id карточки. // ct: Токен отмены. // Возвращает: 200 с единой карточкой либо 404. - private static async Task ReadCardAsync(HttpContext context, string cardId, CancellationToken ct) + private static async Task ReadCardAsync( + HttpContext context, + string cardId, + CancellationToken ct) { CardsService cardsService = context.RequestServices.GetRequiredService(); CardDto? card = await cardsService.GetCardAsync(cardId, ct); diff --git a/src/core/Deal.Api/Endpoints/CardsEndpoints.cs b/src/core/Deal.Api/Endpoints/CardsEndpoints.cs index 5633ee5..eb2ca1d 100644 --- a/src/core/Deal.Api/Endpoints/CardsEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/CardsEndpoints.cs @@ -100,7 +100,11 @@ public static class CardsEndpoints // GET /api/cards?containerId=: карточки контейнера (или все карточки дашборда); 400 «Неизвестный контейнер». // Параметр col принят как алиас containerId (совместимость со старым фронтом). - private static async Task ListCardsAsync(string? containerId, string? col, HttpContext context, CancellationToken ct) + private static async Task ListCardsAsync( + string? containerId, + string? col, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -143,7 +147,10 @@ public static class CardsEndpoints } // GET /api/cards/{cardId}: одна карточка; 404 «Карточка не найдена» (L166–168). - private static async Task GetCardAsync(string cardId, HttpContext context, CancellationToken ct) + private static async Task GetCardAsync( + string cardId, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -171,7 +178,10 @@ public static class CardsEndpoints } // POST /api/cards/mark-col-seen: снять «новое» с колонки (L187–191); ответ {ok:true}. - private static async Task MarkColSeenAsync(MarkColBody body, HttpContext context, CancellationToken ct) + private static async Task MarkColSeenAsync( + MarkColBody body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -195,7 +205,11 @@ public static class CardsEndpoints // доменный механизм перехода ICardMover: стадия — запись истории и сброс напоминания // (move_stage), дашборд-контейнер — журнал/обучение ML. Ответ — обновлённая карточка; 400 при // несуществующем контейнере, 404 — карточки нет. - private static async Task MoveAsync(string cardId, MoveBody body, HttpContext context, CancellationToken ct) + private static async Task MoveAsync( + string cardId, + MoveBody body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -225,7 +239,10 @@ public static class CardsEndpoints } // POST /api/cards/{cardId}/trash: в корзину + обучение ML spam (L203–207); ответ {ok:true}; 404. - private static async Task TrashAsync(string cardId, HttpContext context, CancellationToken ct) + private static async Task TrashAsync( + string cardId, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -245,7 +262,10 @@ public static class CardsEndpoints } // POST /api/cards/{cardId}/restore: возврат из архив/корзины на канбан (L210–214); ответ {ok, col}; 404. - private static async Task RestoreAsync(string cardId, HttpContext context, CancellationToken ct) + private static async Task RestoreAsync( + string cardId, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -265,7 +285,10 @@ public static class CardsEndpoints } // DELETE /api/cards/{cardId}: удалить навсегда (Cards + комментарии; L217–221); ответ {ok:true}; 404. - private static async Task DeleteAsync(string cardId, HttpContext context, CancellationToken ct) + private static async Task DeleteAsync( + string cardId, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -285,7 +308,10 @@ public static class CardsEndpoints } // POST /api/cards/clear-col {col}: очистить корзину/архив (L228–235); ответ {ok, cleared}; 400. - private static async Task ClearColAsync(ClearColBody body, HttpContext context, CancellationToken ct) + private static async Task ClearColAsync( + ClearColBody body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -300,7 +326,11 @@ public static class CardsEndpoints } // POST /api/cards/{cardId}/comments {text}: добавить комментарий (L238–242); ответ {comments}; 400 «Пустой комментарий»; 404. - private static async Task AddCommentAsync(string cardId, CommentBody body, HttpContext context, CancellationToken ct) + private static async Task AddCommentAsync( + string cardId, + CommentBody body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -332,7 +362,10 @@ public static class CardsEndpoints // body: Тело запроса (ids — опционально). // context: Контекст запроса. // ct: Токен отмены. - private static async Task ReclassifyAsync(ReclassifyBody? body, HttpContext context, CancellationToken ct) + private static async Task ReclassifyAsync( + ReclassifyBody? body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -350,7 +383,10 @@ public static class CardsEndpoints // cardId: Id карточки (c_...). // context: Контекст запроса. // ct: Токен отмены. - private static async Task ReclassifyOneAsync(string cardId, HttpContext context, CancellationToken ct) + private static async Task ReclassifyOneAsync( + string cardId, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -412,7 +448,10 @@ public static class CardsEndpoints // context: Контекст запроса. // result: Итог прохода. // ct: Токен отмены. - private static Task AppendReclassifyAuditAsync(HttpContext context, ReclassifyResultDto result, CancellationToken ct) + private static Task AppendReclassifyAuditAsync( + HttpContext context, + ReclassifyResultDto result, + CancellationToken ct) { if (result.Reclassified == 0) { @@ -427,7 +466,10 @@ public static class CardsEndpoints } // GET /api/search?q=: поиск карточек (FTS + LIKE, Ruling 6/Task 12; dashboard_routes L254–256). Ответ {leads, messages: []}. - private static async Task SearchAsync(string? q, HttpContext context, CancellationToken ct) + private static async Task SearchAsync( + string? q, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -448,7 +490,10 @@ public static class CardsEndpoints // context: Контекст запроса (для резолва ContainersService). // ct: Токен отмены. // Возвращает: True — контейнер допустим для фильтра. - private static async Task IsKnownContainerAsync(string col, HttpContext context, CancellationToken ct) + private static async Task IsKnownContainerAsync( + string col, + HttpContext context, + CancellationToken ct) { ContainersService containers = context.RequestServices.GetRequiredService(); return await containers.GetAsync(col, ct) is not null; diff --git a/src/core/Deal.Api/Endpoints/ContainersEndpoints.cs b/src/core/Deal.Api/Endpoints/ContainersEndpoints.cs index 0807d48..18ccdca 100644 --- a/src/core/Deal.Api/Endpoints/ContainersEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/ContainersEndpoints.cs @@ -69,7 +69,10 @@ public static class ContainersEndpoints } // GET /api/containers?space=: список контейнеров пространства (или всех) со счётчиками. - private static async Task ListContainersAsync(string? space, HttpContext context, CancellationToken ct) + private static async Task ListContainersAsync( + string? space, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -81,7 +84,10 @@ public static class ContainersEndpoints } // POST /api/containers: создать контейнер; ответ {id}. - private static async Task CreateContainerAsync(ContainerCreateRequest body, HttpContext context, CancellationToken ct) + private static async Task CreateContainerAsync( + ContainerCreateRequest body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -112,7 +118,11 @@ public static class ContainersEndpoints } // PATCH /api/containers/{id}: частичное обновление; ответ {id}; 404 «Контейнер не найден». - private static async Task PatchContainerAsync(string containerId, JsonElement body, HttpContext context, CancellationToken ct) + private static async Task PatchContainerAsync( + string containerId, + JsonElement body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -172,7 +182,10 @@ public static class ContainersEndpoints } // POST /api/containers/{id}/accept: принять ИИ-предложение (suggested=false). - private static async Task AcceptSuggestedAsync(string containerId, HttpContext context, CancellationToken ct) + private static async Task AcceptSuggestedAsync( + string containerId, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -192,7 +205,10 @@ public static class ContainersEndpoints } // DELETE /api/containers/{id}: удалить контейнер; карточки → «Неразобранное» новыми. - private static async Task DeleteContainerAsync(string containerId, HttpContext context, CancellationToken ct) + private static async Task DeleteContainerAsync( + string containerId, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -208,7 +224,10 @@ public static class ContainersEndpoints } // POST /api/containers/reorder: порядок контейнеров пространства; ответ {ok:true}. - private static async Task ReorderContainersAsync(OrderBody body, HttpContext context, CancellationToken ct) + private static async Task ReorderContainersAsync( + OrderBody body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -246,7 +265,11 @@ public static class ContainersEndpoints } // PATCH /api/containers/{id}/state: merge патча в состояние колонки; ответ — состояние этой колонки. - private static async Task PatchColumnStateAsync(string containerId, ColStateBody body, HttpContext context, CancellationToken ct) + private static async Task PatchColumnStateAsync( + string containerId, + ColStateBody body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { diff --git a/src/core/Deal.Api/Endpoints/DiscoveryEndpoints.cs b/src/core/Deal.Api/Endpoints/DiscoveryEndpoints.cs index 6951266..c84f691 100644 --- a/src/core/Deal.Api/Endpoints/DiscoveryEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/DiscoveryEndpoints.cs @@ -146,7 +146,10 @@ public static class DiscoveryEndpoints } // POST /api/discovery/tasks: создать задачу поиска (create_task L146–151; дефолты — в сервисе). - private static async Task CreateTaskAsync(DiscoveryTaskCreateBody body, HttpContext context, CancellationToken ct) + private static async Task CreateTaskAsync( + DiscoveryTaskCreateBody body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -166,7 +169,11 @@ public static class DiscoveryEndpoints } // PATCH /api/discovery/tasks/{task_id}: обновить задачу (patch_task L154–161; 404/400). - private static async Task PatchTaskAsync(string task_id, DiscoveryTaskPatchBody body, HttpContext context, CancellationToken ct) + private static async Task PatchTaskAsync( + string task_id, + DiscoveryTaskPatchBody body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -186,7 +193,10 @@ public static class DiscoveryEndpoints } // DELETE /api/discovery/tasks/{task_id}: удалить задачу с кандидатами и логом (delete_task L164–168). - private static async Task DeleteTaskAsync(string task_id, HttpContext context, CancellationToken ct) + private static async Task DeleteTaskAsync( + string task_id, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -199,7 +209,10 @@ public static class DiscoveryEndpoints } // POST /api/discovery/tasks/{task_id}/start: запуск поиска (start_task L171–179; пустые ключи → 400). - private static async Task StartTaskAsync(string task_id, HttpContext context, CancellationToken ct) + private static async Task StartTaskAsync( + string task_id, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -219,7 +232,10 @@ public static class DiscoveryEndpoints } // POST /api/discovery/tasks/{task_id}/pause: пауза поиска (pause_task L181–187). - private static async Task PauseTaskAsync(string task_id, HttpContext context, CancellationToken ct) + private static async Task PauseTaskAsync( + string task_id, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -236,7 +252,10 @@ public static class DiscoveryEndpoints // Очистка ключей ответа — CleanKeywords (python _clean_keywords L111–128: ≤30, ≤60 // символов, дедуп casefold); описание режет до 4000 сам адаптер (GrpcAiTools.MaxDescriptionCodePoints). // Локальный режим (LocalAiTools, UseLocal=true) — NotSupportedException → та же мягкая ветка с текстом причины. - private static async Task GenerateKeywordsAsync(string task_id, HttpContext context, CancellationToken ct) + private static async Task GenerateKeywordsAsync( + string task_id, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -283,7 +302,11 @@ public static class DiscoveryEndpoints // GET /api/discovery/tasks/{task_id}/candidates?status=: кандидаты задачи с фильтром // new|review|joined|rejected (list_candidates L216–224; невалидный статус — пустой список). - private static async Task ListCandidatesAsync(string task_id, string? status, HttpContext context, CancellationToken ct) + private static async Task ListCandidatesAsync( + string task_id, + string? status, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -305,7 +328,10 @@ public static class DiscoveryEndpoints // POST /api/discovery/candidates/{dialog_id}/join: ручное вступление вне квот (join_candidate L226–251). // RPC Join → строка каталога Dialogs (монитор on) + зеркало → фоновый первый разбор → снятие чёрного списка → // mark_joined(auto:false). Ошибка Telegram → 400 с текстом причины. - private static async Task JoinCandidateAsync(string dialog_id, HttpContext context, CancellationToken ct) + private static async Task JoinCandidateAsync( + string dialog_id, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -361,7 +387,10 @@ public static class DiscoveryEndpoints // POST /api/discovery/candidates/{dialog_id}/reject: отклонить кандидата — в чёрный список // (reject_candidate L253–264; уже вступившего — нельзя, 400). - private static async Task RejectCandidateAsync(string dialog_id, HttpContext context, CancellationToken ct) + private static async Task RejectCandidateAsync( + string dialog_id, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -405,7 +434,10 @@ public static class DiscoveryEndpoints } // DELETE /api/discovery/blacklist/{dialog_id}: снять источник с чёрного списка (remove_blacklist L274–277). - private static async Task RemoveBlacklistAsync(string dialog_id, HttpContext context, CancellationToken ct) + private static async Task RemoveBlacklistAsync( + string dialog_id, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -418,7 +450,10 @@ public static class DiscoveryEndpoints } // GET /api/discovery/tasks/{task_id}/log: лог задачи (task_log L282–285), события от новых к старым. - private static async Task TaskLogAsync(string task_id, HttpContext context, CancellationToken ct) + private static async Task TaskLogAsync( + string task_id, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { diff --git a/src/core/Deal.Api/Endpoints/EventsEndpoint.cs b/src/core/Deal.Api/Endpoints/EventsEndpoint.cs index a85de6c..ee18cd2 100644 --- a/src/core/Deal.Api/Endpoints/EventsEndpoint.cs +++ b/src/core/Deal.Api/Endpoints/EventsEndpoint.cs @@ -110,7 +110,10 @@ public static class EventsEndpoint // response: Ответ (stream уже начат). // frame: Frame протокола SSE. // ct: Токен отмены запроса. - private static async Task WriteFrameAsync(HttpResponse response, string frame, CancellationToken ct) + private static async Task WriteFrameAsync( + HttpResponse response, + string frame, + CancellationToken ct) { await response.WriteAsync(frame, ct); await response.Body.FlushAsync(ct); diff --git a/src/core/Deal.Api/Endpoints/FilterTesterEndpoints.cs b/src/core/Deal.Api/Endpoints/FilterTesterEndpoints.cs index 361187b..0d9ecd4 100644 --- a/src/core/Deal.Api/Endpoints/FilterTesterEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/FilterTesterEndpoints.cs @@ -47,7 +47,10 @@ public static class FilterTesterEndpoints } // POST /api/admin/check-message: этап-1 правила + этап-2 (skipped) для тестера (dashboard_routes.py L267–284). - private static async Task CheckAsync(CheckMessageRequest body, HttpContext context, CancellationToken ct) + private static async Task CheckAsync( + CheckMessageRequest body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { diff --git a/src/core/Deal.Api/Endpoints/MlEndpoints.cs b/src/core/Deal.Api/Endpoints/MlEndpoints.cs index 6db4cee..c063fa4 100644 --- a/src/core/Deal.Api/Endpoints/MlEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/MlEndpoints.cs @@ -104,7 +104,10 @@ public static class MlEndpoints } // POST /api/ml/predict: проверка ML на тексте (ml_routes.py L84–90). - private static async Task PredictAsync(MlPredictRequest body, HttpContext context, CancellationToken ct) + private static async Task PredictAsync( + MlPredictRequest body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -139,7 +142,10 @@ public static class MlEndpoints } // POST /api/ml/candidates: последние сообщения канала + мнение ML (ml_routes.py L112–134). - private static async Task CandidatesAsync(MlCandidatesRequest body, HttpContext context, CancellationToken ct) + private static async Task CandidatesAsync( + MlCandidatesRequest body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -152,7 +158,10 @@ public static class MlEndpoints } // POST /api/ml/apply: ручное решение по сообщению (ml_routes.py L137–171). - private static async Task ApplyAsync(MlApplyRequest body, HttpContext context, CancellationToken ct) + private static async Task ApplyAsync( + MlApplyRequest body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { diff --git a/src/core/Deal.Api/Endpoints/OperatorAuthEndpoints.cs b/src/core/Deal.Api/Endpoints/OperatorAuthEndpoints.cs index ae198e5..5202f38 100644 --- a/src/core/Deal.Api/Endpoints/OperatorAuthEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/OperatorAuthEndpoints.cs @@ -143,7 +143,10 @@ public static class OperatorAuthEndpoints // context: Контекст запроса. // options: Настройки куки из конфигурации (секция OperatorCookies). // rawToken: Raw-токен операторской сессии. - private static void SetOperatorSessionCookie(HttpContext context, OperatorCookieOptions options, string rawToken) + private static void SetOperatorSessionCookie( + HttpContext context, + OperatorCookieOptions options, + string rawToken) { // MaxAge — OperatorCookies:Hours; код-дефолт значения ссылается на // OperatorAuthService.SessionLifetimeHours (единый источник «12 часов», см. OperatorCookieOptions). diff --git a/src/core/Deal.Api/Endpoints/PipelineEndpoints.cs b/src/core/Deal.Api/Endpoints/PipelineEndpoints.cs index 050e586..f1eb701 100644 --- a/src/core/Deal.Api/Endpoints/PipelineEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/PipelineEndpoints.cs @@ -93,7 +93,10 @@ public static class PipelineEndpoints // GET /api/pipeline/queue?limit=: сырые сообщения очереди + счётчики + число отсева (processing_routes.py L23–31). // Ответ {items, counts:{new,ai,total}, rejected} 1:1 с list_queue L218–241 + queue_counts L207–215 + // rejected_count L201–202. limit — дефолт 100, clamp 1..500 делает сервис (ListQueueAsync). - private static async Task QueueAsync(int? limit, HttpContext context, CancellationToken ct) + private static async Task QueueAsync( + int? limit, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -110,7 +113,12 @@ public static class PipelineEndpoints // GET /api/pipeline/rejected?q=&offset=&limit=: страница отсева (processing_routes.py L34–42, list_rejected L246–312). // q — поиск по тексту/причине/фразе/имени канала (FTS ∪ LIKE, Ruling 6), пустой q — весь отсев свежими // первыми; offset ≥ 0, limit 1..500 (clamp в сервисе), значения эхом в ответе {items,total,offset,limit}. - private static async Task RejectedAsync(string? q, int? offset, int? limit, HttpContext context, CancellationToken ct) + private static async Task RejectedAsync( + string? q, + int? offset, + int? limit, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -137,7 +145,10 @@ public static class PipelineEndpoints // DELETE /api/pipeline/rejected/{rejId}: удалить запись отсева; ответ {ok:true} всегда (delete_one L196–198, Ruling 10). // Прототип не проверяет наличие записи — 404 не шлём (план Task 9 L444; Ruling 10 «always ok»). - private static async Task DeleteAsync(string rejId, HttpContext context, CancellationToken ct) + private static async Task DeleteAsync( + string rejId, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -153,7 +164,11 @@ public static class PipelineEndpoints // Успех — {id, returned:true, returnedAt} (запись помечается returned, НЕ удаляется — аудит Ruling 10); // причины 400 (уже возвращено/повтор-dup/нет текста) — константы PipelineProcessingService (строки 1:1 с // прототипом); записи нет — 404 «Запись не найдена» (текст 404 — слой эндпоинтов). - private static async Task ReturnAsync(string rejId, ReturnReasonRequest body, HttpContext context, CancellationToken ct) + private static async Task ReturnAsync( + string rejId, + ReturnReasonRequest body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { diff --git a/src/core/Deal.Api/Endpoints/TelegramEndpoints.cs b/src/core/Deal.Api/Endpoints/TelegramEndpoints.cs index 8c7bc6a..f2d905a 100644 --- a/src/core/Deal.Api/Endpoints/TelegramEndpoints.cs +++ b/src/core/Deal.Api/Endpoints/TelegramEndpoints.cs @@ -140,7 +140,10 @@ public static class TelegramEndpoints } // POST /api/tg/start-phone: запросить код по номеру (tg_routes.py L68–74; python L134–147). - private static async Task StartPhoneAsync(TgStartPhoneRequest body, HttpContext context, CancellationToken ct) + private static async Task StartPhoneAsync( + TgStartPhoneRequest body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -209,7 +212,10 @@ public static class TelegramEndpoints } // POST /api/tg/send-code: отправить SMS-код (tg_routes.py L86–94; python submit_code L149–166). - private static async Task SendCodeAsync(TgSendCodeRequest body, HttpContext context, CancellationToken ct) + private static async Task SendCodeAsync( + TgSendCodeRequest body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -235,7 +241,10 @@ public static class TelegramEndpoints } // POST /api/tg/send-password: облачный пароль 2FA (tg_routes.py L97–103; python submit_password L168–176). - private static async Task SendPasswordAsync(TgSendPasswordRequest body, HttpContext context, CancellationToken ct) + private static async Task SendPasswordAsync( + TgSendPasswordRequest body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -355,7 +364,10 @@ public static class TelegramEndpoints } // POST /api/tg/dialogs/monitor-all: мониторинг всех каналов (tg_routes.py L125–129; L548–567). - private static async Task MonitorAllAsync(TgMonitorBody body, HttpContext context, CancellationToken ct) + private static async Task MonitorAllAsync( + TgMonitorBody body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -406,7 +418,11 @@ public static class TelegramEndpoints } // POST /api/tg/dialogs/{dialog_id}/monitor: вкл/выкл мониторинг канала (tg_routes.py L139–142; L536–546). - private static async Task SetMonitorAsync(string dialog_id, TgMonitorBody body, HttpContext context, CancellationToken ct) + private static async Task SetMonitorAsync( + string dialog_id, + TgMonitorBody body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -439,7 +455,10 @@ public static class TelegramEndpoints // POST /api/tg/dialogs/{dialog_id}/backfill: разбор одного диалога (tg_routes.py L145–148; L349–390). // Сервер-only эндпоинт (фронт не вызывает, api-map §3.3 L139/п.9): первый разбор/догон одного канала. - private static async Task BackfillDialogAsync(string dialog_id, HttpContext context, CancellationToken ct) + private static async Task BackfillDialogAsync( + string dialog_id, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { @@ -459,7 +478,10 @@ public static class TelegramEndpoints } // POST /api/tg/dialogs/preview: последние сообщения диалога (tg_routes.py L151–153; dialog_messages L583–620). - private static async Task PreviewAsync(TgPreviewBody body, HttpContext context, CancellationToken ct) + private static async Task PreviewAsync( + TgPreviewBody body, + HttpContext context, + CancellationToken ct) { if (!context.HasUser()) { diff --git a/src/core/Deal.Api/Events/SseBroker.cs b/src/core/Deal.Api/Events/SseBroker.cs index b59308f..91d3033 100644 --- a/src/core/Deal.Api/Events/SseBroker.cs +++ b/src/core/Deal.Api/Events/SseBroker.cs @@ -96,7 +96,10 @@ public sealed class SseBroker /// Тенант-получатель; без подписчиков — no-op, не падает. /// Тип события (new_card/toast этапа 3; api.js L78–79). /// Полезная нагрузка — сериализуется в JSON (camelCase, без \u). - public void Publish(Guid tenantId, string eventType, object payload) => + public void Publish( + Guid tenantId, + string eventType, + object payload) => Publish(tenantId, new SseEvent(eventType, JsonSerializer.Serialize(payload, PublishJsonOptions))); /// diff --git a/src/core/Deal.Api/Http/AuditAppender.cs b/src/core/Deal.Api/Http/AuditAppender.cs index ba8cb9c..185f8ed 100644 --- a/src/core/Deal.Api/Http/AuditAppender.cs +++ b/src/core/Deal.Api/Http/AuditAppender.cs @@ -28,7 +28,11 @@ public static class AuditAppender /// Тип события — константа . /// Минимальные детали события (обычно анонимный объект) или null. /// Токен отмены. - public static async Task AppendTenantAsync(HttpContext context, string eventType, object? details, CancellationToken ct) + public static async Task AppendTenantAsync( + HttpContext context, + string eventType, + object? details, + CancellationToken ct) { CurrentUser? user = context.GetCurrentUser(); if (user is null) @@ -55,7 +59,11 @@ public static class AuditAppender /// Тип события — константа . /// Минимальные детали события (обычно анонимный объект) или null. /// Токен отмены. - public static async Task AppendOperatorAsync(HttpContext context, string eventType, object? details, CancellationToken ct) + public static async Task AppendOperatorAsync( + HttpContext context, + string eventType, + object? details, + CancellationToken ct) { CurrentOperator? operatorIdentity = context.GetCurrentOperator(); if (operatorIdentity is null) diff --git a/src/core/Deal.Api/Http/LoginAttemptGuard.cs b/src/core/Deal.Api/Http/LoginAttemptGuard.cs index 448a7e8..4f6c75b 100644 --- a/src/core/Deal.Api/Http/LoginAttemptGuard.cs +++ b/src/core/Deal.Api/Http/LoginAttemptGuard.cs @@ -64,7 +64,10 @@ public sealed class LoginAttemptGuard /// Настройки rate limiting (секция RateLimit). /// Хранилище счётчиков фиксированного окна (public.rate_limit_counters). /// Источник «сейчас». - public LoginAttemptGuard(RateLimitOptions options, IRateLimitCounterStore store, Func clock) + public LoginAttemptGuard( + RateLimitOptions options, + IRateLimitCounterStore store, + Func clock) { ArgumentNullException.ThrowIfNull(options); ArgumentNullException.ThrowIfNull(store); @@ -84,7 +87,10 @@ public sealed class LoginAttemptGuard /// Нормализованный логин; пустой/пробельный — блокировке не подлежит. /// Токен отмены. /// true — следующий вход ключа отклоняется 429 до проверки учётных данных. - public async Task IsBlockedAsync(string? ip, string? login, CancellationToken ct) + public async Task IsBlockedAsync( + string? ip, + string? login, + CancellationToken ct) { if (!_enabled || string.IsNullOrWhiteSpace(login)) { @@ -101,7 +107,10 @@ public sealed class LoginAttemptGuard /// IP клиента (null/пустой — фолбэк unknown). /// Нормализованный логин; пустой/пробельный — не записывается. /// Токен отмены. - public async Task RecordFailureAsync(string? ip, string? login, CancellationToken ct) + public async Task RecordFailureAsync( + string? ip, + string? login, + CancellationToken ct) { if (!_enabled || string.IsNullOrWhiteSpace(login)) { @@ -118,7 +127,10 @@ public sealed class LoginAttemptGuard /// IP клиента (null/пустой — фолбэк unknown). /// Логин успешно вошедшего (нормализованный). /// Токен отмены. - public async Task ResetAsync(string? ip, string? login, CancellationToken ct) + public async Task ResetAsync( + string? ip, + string? login, + CancellationToken ct) { if (!_enabled || string.IsNullOrWhiteSpace(login)) { diff --git a/src/core/Deal.Api/Http/SessionCookieWriter.cs b/src/core/Deal.Api/Http/SessionCookieWriter.cs index d4d132d..cbef70d 100644 --- a/src/core/Deal.Api/Http/SessionCookieWriter.cs +++ b/src/core/Deal.Api/Http/SessionCookieWriter.cs @@ -23,7 +23,10 @@ public static class SessionCookieWriter /// Контекст запроса. /// Настройки куки из конфигурации (секция Cookies). /// Raw-токен сессии. - public static void Append(HttpContext context, CookieOptions options, string rawToken) + public static void Append( + HttpContext context, + CookieOptions options, + string rawToken) { context.Response.Cookies.Append( options.Name, diff --git a/src/core/Deal.Api/Middleware/HttpAccessLogMiddleware.cs b/src/core/Deal.Api/Middleware/HttpAccessLogMiddleware.cs index d05c47a..c387696 100644 --- a/src/core/Deal.Api/Middleware/HttpAccessLogMiddleware.cs +++ b/src/core/Deal.Api/Middleware/HttpAccessLogMiddleware.cs @@ -70,7 +70,10 @@ public sealed class HttpAccessLogMiddleware // context: Контекст запроса (метод/путь/статус ответа). // startedAt: Метка времени старта запроса (Stopwatch.GetTimestamp). // exception: Необработанное исключение (null — запрос завершился штатно). - private void LogCall(HttpContext context, long startedAt, Exception? exception) + private void LogCall( + HttpContext context, + long startedAt, + Exception? exception) { long elapsedMs = (long)Stopwatch.GetElapsedTime(startedAt).TotalMilliseconds; string method = context.Request.Method; diff --git a/src/core/Deal.Api/Middleware/OperatorSessionMiddleware.cs b/src/core/Deal.Api/Middleware/OperatorSessionMiddleware.cs index 5521cb7..c5eefc9 100644 --- a/src/core/Deal.Api/Middleware/OperatorSessionMiddleware.cs +++ b/src/core/Deal.Api/Middleware/OperatorSessionMiddleware.cs @@ -30,9 +30,7 @@ public sealed class OperatorSessionMiddleware private readonly RequestDelegate _next; private readonly IOptionsMonitor _cookieOptions; - public OperatorSessionMiddleware( - RequestDelegate next, - IOptionsMonitor cookieOptions) + public OperatorSessionMiddleware(RequestDelegate next, IOptionsMonitor cookieOptions) { _next = next; _cookieOptions = cookieOptions; diff --git a/src/core/Deal.Api/Middleware/RateLimitPolicies.cs b/src/core/Deal.Api/Middleware/RateLimitPolicies.cs index ba837e3..b91e191 100644 --- a/src/core/Deal.Api/Middleware/RateLimitPolicies.cs +++ b/src/core/Deal.Api/Middleware/RateLimitPolicies.cs @@ -102,7 +102,10 @@ public static class RateLimitPolicies // scope: Пространство ключей (имя политики или "global" для глобального лимитера). // permitsPerMinute: Разрешено запросов в минуту на партицию (RateLimit:ApiPerMinute). // Возвращает: Партиция лимитера с ключом tenant:{id} либо IP. - private static RateLimitPartition ApiPartition(HttpContext context, string scope, int permitsPerMinute) + private static RateLimitPartition ApiPartition( + HttpContext context, + string scope, + int permitsPerMinute) { string partitionKey = context.GetCurrentUser() is { } user ? TenantKeyPrefix + user.TenantId.ToString("N") @@ -117,7 +120,10 @@ public static class RateLimitPolicies // permitsPerMinute: Разрешено запросов в минуту. // Возвращает: Партиция store-backed лимитера (ленивое создание и кеш — менеджер партиций). private static RateLimitPartition StorePartition( - HttpContext context, string scope, string partitionKey, int permitsPerMinute) + HttpContext context, + string scope, + string partitionKey, + int permitsPerMinute) { string storeKey = HttpKeyPrefix + scope + ":" + partitionKey; return RateLimitPartition.Get(storeKey, key => new StoreBackedFixedWindowRateLimiter( diff --git a/src/core/Deal.Api/Services/TelegramBackfillScheduler.cs b/src/core/Deal.Api/Services/TelegramBackfillScheduler.cs index a6620da..419c975 100644 --- a/src/core/Deal.Api/Services/TelegramBackfillScheduler.cs +++ b/src/core/Deal.Api/Services/TelegramBackfillScheduler.cs @@ -102,7 +102,9 @@ public sealed class TelegramBackfillScheduler( // «Перечитать» всех включённых каналов в фоне (ReadRecentAsync продолжает при частичном падении). private static async Task RunReadRecentAsync( - IServiceProvider provider, ILogger runLogger, CancellationToken ct) + IServiceProvider provider, + ILogger runLogger, + CancellationToken ct) { DialogsService dialogs = provider.GetRequiredService(); int count = await dialogs.ReadRecentAsync(ct); diff --git a/src/core/Deal.Api/Telegram/RpcCallLoggingInterceptor.cs b/src/core/Deal.Api/Telegram/RpcCallLoggingInterceptor.cs index d753688..b21c2c7 100644 --- a/src/core/Deal.Api/Telegram/RpcCallLoggingInterceptor.cs +++ b/src/core/Deal.Api/Telegram/RpcCallLoggingInterceptor.cs @@ -79,7 +79,10 @@ public sealed class RpcCallLoggingInterceptor : Interceptor // context: Контекст вызова (метод). // startedAt: Метка времени старта вызова (Stopwatch.GetTimestamp). // statusCode: Итоговый gRPC-статус; null — успех (OK). - private void LogCall(ServerCallContext context, long startedAt, StatusCode? statusCode) + private void LogCall( + ServerCallContext context, + long startedAt, + StatusCode? statusCode) { long elapsedMs = (long)Stopwatch.GetElapsedTime(startedAt).TotalMilliseconds; _logger.LogInformation( diff --git a/src/core/Deal.Api/Telegram/TelegramIngressService.cs b/src/core/Deal.Api/Telegram/TelegramIngressService.cs index 2ae0304..e327372 100644 --- a/src/core/Deal.Api/Telegram/TelegramIngressService.cs +++ b/src/core/Deal.Api/Telegram/TelegramIngressService.cs @@ -328,7 +328,10 @@ public sealed class TelegramIngressService( // request: Сообщение PushMessage. // ct: Токен отмены. private async Task SavePreviewSafelyAsync( - AsyncServiceScope tenantScope, TenantRecordDto tenant, PushMessageRequest request, CancellationToken ct) + AsyncServiceScope tenantScope, + TenantRecordDto tenant, + PushMessageRequest request, + CancellationToken ct) { try { @@ -356,7 +359,10 @@ public sealed class TelegramIngressService( // tenantId: Тенант канала (реестровый Guid). // previous: Предыдущий снимок из KV (null — первый репорт). // current: Текущий снимок репорта. - private void PublishStatusEvents(Guid tenantId, TgReportedStatus? previous, TgReportedStatus current) + private void PublishStatusEvents( + Guid tenantId, + TgReportedStatus? previous, + TgReportedStatus current) { broker.Publish(tenantId, SystemStatusEventType, current); if (previous is null) @@ -379,7 +385,10 @@ public sealed class TelegramIngressService( // tenantId: Тенант-получатель. // text: Текст тоста. // icon: Иконка тоста (набор Icon.vue фронта). - private void PublishToast(Guid tenantId, string text, string icon) + private void PublishToast( + Guid tenantId, + string text, + string icon) { broker.Publish(tenantId, ToastEventType, new { text, icon }); } diff --git a/src/core/Deal.Api/Telegram/TelegramKeysService.cs b/src/core/Deal.Api/Telegram/TelegramKeysService.cs index 25dd705..5f7d5ec 100644 --- a/src/core/Deal.Api/Telegram/TelegramKeysService.cs +++ b/src/core/Deal.Api/Telegram/TelegramKeysService.cs @@ -96,7 +96,10 @@ public sealed class TelegramKeysService(IGlobalSettingsStore store, ISecretCiphe /// api_hash приложения (непустой секрет). /// Токен отмены. /// Значения не прошли валидацию (см. /). - public async Task SaveAsync(string apiId, string apiHash, CancellationToken ct) + public async Task SaveAsync( + string apiId, + string apiHash, + CancellationToken ct) { string trimmedApiId = (apiId ?? string.Empty).Trim(); string trimmedApiHash = (apiHash ?? string.Empty).Trim(); diff --git a/src/core/Deal.Contracts/Integrations/IAiTools.cs b/src/core/Deal.Contracts/Integrations/IAiTools.cs index 56c5d6d..72ae040 100644 --- a/src/core/Deal.Contracts/Integrations/IAiTools.cs +++ b/src/core/Deal.Contracts/Integrations/IAiTools.cs @@ -43,5 +43,8 @@ public interface IAiTools /// Токен отмены. /// Решение fit + краткая причина (потолок причины 200, как _AI_REASON_LIMIT L43). public Task EvaluateFitAsync( - string text, string description, IReadOnlyCollection keywords, CancellationToken ct); + string text, + string description, + IReadOnlyCollection keywords, + CancellationToken ct); } diff --git a/src/core/Deal.Contracts/Integrations/IFileStorage.cs b/src/core/Deal.Contracts/Integrations/IFileStorage.cs index cf9a7f3..f4fb07c 100644 --- a/src/core/Deal.Contracts/Integrations/IFileStorage.cs +++ b/src/core/Deal.Contracts/Integrations/IFileStorage.cs @@ -39,7 +39,11 @@ public interface IFileStorage /// MIME-тип (например, image/png); может быть пустым. /// Токен отмены. /// Сохранённый objectKey (как передан). - public Task PutAsync(string objectKey, Stream content, string contentType, CancellationToken ct); + public Task PutAsync( + string objectKey, + Stream content, + string contentType, + CancellationToken ct); /// /// Возвращает содержимое объекта потоком (get object_store.py L82–93). diff --git a/src/core/Deal.Contracts/Integrations/IMlClient.cs b/src/core/Deal.Contracts/Integrations/IMlClient.cs index d9765b1..a10e4ba 100644 --- a/src/core/Deal.Contracts/Integrations/IMlClient.cs +++ b/src/core/Deal.Contracts/Integrations/IMlClient.cs @@ -51,5 +51,9 @@ public interface IMlClient /// Вес сигнала: 1.0 — действие пользователя, −1.0 — снять метку; ИИ-сигналы (0.4/0.6) — этапы 4/6. /// Токен отмены. /// Задача завершается после записи строки в outbox (отправка в ML-сервис — фоновый воркер этапа 6). - public Task PushAsync(string text, string label, double delta, CancellationToken ct); + public Task PushAsync( + string text, + string label, + double delta, + CancellationToken ct); } diff --git a/src/core/Deal.Contracts/Integrations/ITelegramGateway.cs b/src/core/Deal.Contracts/Integrations/ITelegramGateway.cs index 43c7a31..795326f 100644 --- a/src/core/Deal.Contracts/Integrations/ITelegramGateway.cs +++ b/src/core/Deal.Contracts/Integrations/ITelegramGateway.cs @@ -31,7 +31,11 @@ public interface ITelegramGateway /// api_hash приложения Telegram (глобальные ключи, задаёт оператор). /// Токен отмены. /// Новая фаза ("code"); нет ключей — ядро отвечает 400 «Ключи Telegram не заданы оператором» до вызова. - public Task StartPhoneAsync(string phone, int apiId, string apiHash, CancellationToken ct); + public Task StartPhoneAsync( + string phone, + int apiId, + string apiHash, + CancellationToken ct); /// /// Начать QR-вход (qr_start python L286–300). @@ -40,7 +44,10 @@ public interface ITelegramGateway /// api_hash приложения Telegram (глобальные ключи, задаёт оператор). /// Токен отмены. /// Фаза + qrUrl; аккаунт уже авторизован — фаза "ready", qrUrl пуст. - public Task StartQrAsync(int apiId, string apiHash, CancellationToken ct); + public Task StartQrAsync( + int apiId, + string apiHash, + CancellationToken ct); /// /// Отправить SMS-код (submit_code python L149–166). @@ -79,7 +86,10 @@ public interface ITelegramGateway /// True — мониторить (сообщения → PushMessage в ядро), false — выключить. /// Токен отмены. /// Завершается после обновления зеркала сервиса. - public Task SetMonitorAsync(string dialogId, bool enabled, CancellationToken ct); + public Task SetMonitorAsync( + string dialogId, + bool enabled, + CancellationToken ct); /// /// Мониторинг всех диалогов сразу (set_monitor_all python L548–567): зеркало сервиса = каталог. @@ -96,7 +106,10 @@ public interface ITelegramGateway /// True — перечитать, даже если диалог уже разобран (кнопка «Перечитать»). /// Токен отмены. /// Сколько сообщений отправлено в ядро потоком PushMessage. - public Task BackfillAsync(string dialogId, bool force, CancellationToken ct); + public Task BackfillAsync( + string dialogId, + bool force, + CancellationToken ct); /// /// Последние сообщения диалога для превью (dialog_messages python L583–620), свежие из Telegram. @@ -105,7 +118,10 @@ public interface ITelegramGateway /// Сколько последних сообщений (1..50; api-map /dialogs/preview). /// Токен отмены. /// Сообщения от новых к старым; признак lead и фолбэк на БД добавляет ядро (Ruling 7). - public Task> ReadRecentAsync(string dialogId, int limit, CancellationToken ct); + public Task> ReadRecentAsync( + string dialogId, + int limit, + CancellationToken ct); /// /// Глобальный поиск каналов/групп по ключу (discovery_search python L624–664). @@ -114,7 +130,10 @@ public interface ITelegramGateway /// Верхняя граница результатов (прототип: default 30). /// Токен отмены. /// Найденные источники (каналы/группы; личные чаты/ботов отсеивает ядро — Ruling 10). - public Task> SearchAsync(string query, int limit, CancellationToken ct); + public Task> SearchAsync( + string query, + int limit, + CancellationToken ct); /// /// Инфо об источнике для оценки (discovery_info python L666–716). @@ -131,7 +150,10 @@ public interface ITelegramGateway /// Размер выборки (прототип discovery_read: limit сообщений/тем). /// Токен отмены. /// ok + сообщения (форумы — по активным темам) либо ok=false + error="no_history". - public Task ReadForEvalAsync(string dialogId, int limit, CancellationToken ct); + public Task ReadForEvalAsync( + string dialogId, + int limit, + CancellationToken ct); /// /// Вступить в канал/группу по @username (discovery_join python L818–839). diff --git a/src/core/Deal.Infrastructure/Integrations/Models/MtlsCertificates.cs b/src/core/Deal.Infrastructure/Integrations/Models/MtlsCertificates.cs index 2e3a7de..8459aeb 100644 --- a/src/core/Deal.Infrastructure/Integrations/Models/MtlsCertificates.cs +++ b/src/core/Deal.Infrastructure/Integrations/Models/MtlsCertificates.cs @@ -89,7 +89,10 @@ public sealed class MtlsCertificates /// Клиентский сертификат из рукопожатия (null — RequireCertificate не выполнен). /// Цепочка стандартной проверки (игнорируется — пересобирается на нашу CA). /// Ошибки стандартной проверки TLS. - public bool ValidateClientCertificate(X509Certificate2? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) + public bool ValidateClientCertificate( + X509Certificate2? certificate, + X509Chain? chain, + SslPolicyErrors sslPolicyErrors) { if (certificate is null) { @@ -132,7 +135,11 @@ public sealed class MtlsCertificates // certificate: Сертификат сервера из рукопожатия. // chain: Цепочка стандартной проверки (игнорируется — пересобирается на нашу CA). // sslPolicyErrors: Ошибки стандартной проверки TLS. - private bool ValidateServerCertificate(object? sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) + private bool ValidateServerCertificate( + object? sender, + X509Certificate? certificate, + X509Chain? chain, + SslPolicyErrors sslPolicyErrors) { if (certificate is null) { @@ -222,7 +229,10 @@ public sealed class MtlsCertificates // configuredPath: Путь из env. // envKey: Env-ключ пути (для сообщения об ошибке). // role: Роль сертификата (для сообщения об ошибке). - private static string RequireExistingFile(string configuredPath, string envKey, string role) + private static string RequireExistingFile( + string configuredPath, + string envKey, + string role) { if (string.IsNullOrWhiteSpace(configuredPath)) { diff --git a/src/core/Deal.Infrastructure/Integrations/Services/AiConnectionChecker.cs b/src/core/Deal.Infrastructure/Integrations/Services/AiConnectionChecker.cs index bddba0e..c9bae00 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/AiConnectionChecker.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/AiConnectionChecker.cs @@ -179,7 +179,11 @@ public sealed class AiConnectionChecker : IAiConnectionChecker // ok: Результат подключения. // message: Сообщение ветки. // Возвращает: DTO ответа (наружу — camelCase). - private static AiCheckResultDto BuildResult(AiCheckRequest request, string name, bool ok, string message) + private static AiCheckResultDto BuildResult( + AiCheckRequest request, + string name, + bool ok, + string message) { return new AiCheckResultDto( Ok: ok, @@ -216,7 +220,10 @@ public sealed class AiConnectionChecker : IAiConnectionChecker // apiStyle: Стиль API провайдера (null — OpenAI-совместимый). // modelsUri: URL списка моделей (валиден только при возврате true). // Возвращает: True — URL построен; False — base URL не абсолютный http(s) (SSRF-гейт). - private static bool TryBuildModelsUri(string baseUrl, string? apiStyle, [NotNullWhen(true)] out Uri? modelsUri) + private static bool TryBuildModelsUri( + string baseUrl, + string? apiStyle, + [NotNullWhen(true)] out Uri? modelsUri) { modelsUri = null; if (!Uri.TryCreate(baseUrl, UriKind.Absolute, out Uri? parsed) @@ -241,7 +248,10 @@ public sealed class AiConnectionChecker : IAiConnectionChecker // httpRequest: Запрос списка моделей. // apiKey: Ключ открытым текстом (непустой — ветка ключа пройдена). // apiStyle: Стиль API провайдера. - private static void AddAuthHeaders(HttpRequestMessage httpRequest, string apiKey, string? apiStyle) + private static void AddAuthHeaders( + HttpRequestMessage httpRequest, + string apiKey, + string? apiStyle) { if (apiStyle == AnthropicApiStyle) { diff --git a/src/core/Deal.Infrastructure/Integrations/Services/BudgetedAiTools.cs b/src/core/Deal.Infrastructure/Integrations/Services/BudgetedAiTools.cs index 097fd6a..03df614 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/BudgetedAiTools.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/BudgetedAiTools.cs @@ -98,7 +98,10 @@ public sealed class BudgetedAiTools : IAiTools /// public async Task EvaluateFitAsync( - string text, string description, IReadOnlyCollection keywords, CancellationToken ct) + string text, + string description, + IReadOnlyCollection keywords, + CancellationToken ct) { BudgetStateDto state = await GateStateAsync(ct); if (state.Allowed) diff --git a/src/core/Deal.Infrastructure/Integrations/Services/GrpcAiTools.cs b/src/core/Deal.Infrastructure/Integrations/Services/GrpcAiTools.cs index 21d4725..a662353 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/GrpcAiTools.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/GrpcAiTools.cs @@ -128,7 +128,10 @@ public sealed class GrpcAiTools : IAiTools /// public async Task EvaluateFitAsync( - string text, string description, IReadOnlyCollection keywords, CancellationToken ct) + string text, + string description, + IReadOnlyCollection keywords, + CancellationToken ct) { TenantId tenantId = RequireTenant(); try diff --git a/src/core/Deal.Infrastructure/Integrations/Services/GrpcMlClient.cs b/src/core/Deal.Infrastructure/Integrations/Services/GrpcMlClient.cs index b7a508e..8801dbf 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/GrpcMlClient.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/GrpcMlClient.cs @@ -210,7 +210,11 @@ public sealed class GrpcMlClient : IMlClient, IMlTrainClient } /// - public async Task PushAsync(string text, string label, double delta, CancellationToken ct) + public async Task PushAsync( + string text, + string label, + double delta, + CancellationToken ct) { // Обучение гарантированно и локально (Ruling 6): сигнал всегда пишется в MlOutbox, отправку батчами // делает MlOutboxFlushScheduler — и в Local-, и в gRPC-режиме (ml_client.py L6–7). @@ -332,7 +336,10 @@ public sealed class GrpcMlClient : IMlClient, IMlTrainClient // deadline: Лимит времени вызова. // ct: Токен отмены вызова. // Возвращает: Опции вызова с заголовками, deadline и отменой. - private CallOptions CallOptions(string tenantId, TimeSpan deadline, CancellationToken ct) + private CallOptions CallOptions( + string tenantId, + TimeSpan deadline, + CancellationToken ct) => new( headers: _connection.CreateMetadata(tenantId), deadline: DateTime.UtcNow.Add(deadline), diff --git a/src/core/Deal.Infrastructure/Integrations/Services/GrpcTelegramClient.cs b/src/core/Deal.Infrastructure/Integrations/Services/GrpcTelegramClient.cs index 0472b19..31eeb4d 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/GrpcTelegramClient.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/GrpcTelegramClient.cs @@ -102,7 +102,11 @@ public sealed class GrpcTelegramClient : ITelegramGateway } /// - public async Task StartPhoneAsync(string phone, int apiId, string apiHash, CancellationToken ct) + public async Task StartPhoneAsync( + string phone, + int apiId, + string apiHash, + CancellationToken ct) { TenantId tenantId = RequireTenant(); try @@ -121,7 +125,10 @@ public sealed class GrpcTelegramClient : ITelegramGateway } /// - public async Task StartQrAsync(int apiId, string apiHash, CancellationToken ct) + public async Task StartQrAsync( + int apiId, + string apiHash, + CancellationToken ct) { TenantId tenantId = RequireTenant(); try @@ -211,7 +218,10 @@ public sealed class GrpcTelegramClient : ITelegramGateway } /// - public async Task SetMonitorAsync(string dialogId, bool enabled, CancellationToken ct) + public async Task SetMonitorAsync( + string dialogId, + bool enabled, + CancellationToken ct) { TenantId tenantId = RequireTenant(); try @@ -245,7 +255,10 @@ public sealed class GrpcTelegramClient : ITelegramGateway } /// - public async Task BackfillAsync(string dialogId, bool force, CancellationToken ct) + public async Task BackfillAsync( + string dialogId, + bool force, + CancellationToken ct) { TenantId tenantId = RequireTenant(); try @@ -263,7 +276,10 @@ public sealed class GrpcTelegramClient : ITelegramGateway } /// - public async Task> ReadRecentAsync(string dialogId, int limit, CancellationToken ct) + public async Task> ReadRecentAsync( + string dialogId, + int limit, + CancellationToken ct) { TenantId tenantId = RequireTenant(); try @@ -283,7 +299,10 @@ public sealed class GrpcTelegramClient : ITelegramGateway } /// - public async Task> SearchAsync(string query, int limit, CancellationToken ct) + public async Task> SearchAsync( + string query, + int limit, + CancellationToken ct) { TenantId tenantId = RequireTenant(); try @@ -327,7 +346,10 @@ public sealed class GrpcTelegramClient : ITelegramGateway } /// - public async Task ReadForEvalAsync(string dialogId, int limit, CancellationToken ct) + public async Task ReadForEvalAsync( + string dialogId, + int limit, + CancellationToken ct) { TenantId tenantId = RequireTenant(); try @@ -404,7 +426,9 @@ public sealed class GrpcTelegramClient : ITelegramGateway // call: Вызов клиента (принимает клиент и CallOptions). // Возвращает: Ответ RPC. private async Task CallAsync( - TenantId tenantId, TimeSpan deadline, CancellationToken ct, + TenantId tenantId, + TimeSpan deadline, + CancellationToken ct, Func> call) where TReply : class { @@ -424,7 +448,10 @@ public sealed class GrpcTelegramClient : ITelegramGateway // tenantId: Id тенанта (лог). // operation: Имя RPC (лог-аудит). // Возвращает: Исключение для проброса: транспортный сбой — нормализованный RpcException. - private Exception TranslateTransportFailure(Exception exception, TenantId tenantId, string operation) + private Exception TranslateTransportFailure( + Exception exception, + TenantId tenantId, + string operation) { // Отмена по токену вызывающего — не ошибка сервиса (пробрасываем как обычно). if (exception is OperationCanceledException) diff --git a/src/core/Deal.Infrastructure/Integrations/Services/LocalAiTools.cs b/src/core/Deal.Infrastructure/Integrations/Services/LocalAiTools.cs index 6bddb91..fa8d7f1 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/LocalAiTools.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/LocalAiTools.cs @@ -31,6 +31,9 @@ public sealed class LocalAiTools : IAiTools /// public Task EvaluateFitAsync( - string text, string description, IReadOnlyCollection keywords, CancellationToken ct) + string text, + string description, + IReadOnlyCollection keywords, + CancellationToken ct) => throw new NotSupportedException(NotSupportedMessage); } diff --git a/src/core/Deal.Infrastructure/Integrations/Services/LocalMlClient.cs b/src/core/Deal.Infrastructure/Integrations/Services/LocalMlClient.cs index 8e3a1b1..980b1a9 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/LocalMlClient.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/LocalMlClient.cs @@ -103,7 +103,11 @@ public sealed class LocalMlClient(ISettingsStore store, IMlLearningStore learnin } /// - public async Task PushAsync(string text, string label, double delta, CancellationToken ct) + public async Task PushAsync( + string text, + string label, + double delta, + CancellationToken ct) { // Обучение гарантированно и локально (ml_client.push L40–49): действие пользователя — строка // очереди MlOutbox (отправку в ML-сервис делает воркер этапа 6). Общая логика (trim text/label, diff --git a/src/core/Deal.Infrastructure/Integrations/Services/LocalTelegramGateway.cs b/src/core/Deal.Infrastructure/Integrations/Services/LocalTelegramGateway.cs index 4b54194..003da3a 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/LocalTelegramGateway.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/LocalTelegramGateway.cs @@ -34,11 +34,18 @@ public sealed class LocalTelegramGateway : ITelegramGateway } /// - public Task StartPhoneAsync(string phone, int apiId, string apiHash, CancellationToken ct) + public Task StartPhoneAsync( + string phone, + int apiId, + string apiHash, + CancellationToken ct) => Task.FromResult(new TelegramAuthResultDto(IdlePhase, null)); /// - public Task StartQrAsync(int apiId, string apiHash, CancellationToken ct) + public Task StartQrAsync( + int apiId, + string apiHash, + CancellationToken ct) => Task.FromResult(new TelegramAuthResultDto(IdlePhase, null)); /// @@ -55,20 +62,32 @@ public sealed class LocalTelegramGateway : ITelegramGateway => Task.FromResult>([]); /// - public Task SetMonitorAsync(string dialogId, bool enabled, CancellationToken ct) => Task.CompletedTask; + public Task SetMonitorAsync( + string dialogId, + bool enabled, + CancellationToken ct) => Task.CompletedTask; /// public Task SetMonitorAllAsync(bool enabled, CancellationToken ct) => Task.CompletedTask; /// - public Task BackfillAsync(string dialogId, bool force, CancellationToken ct) => Task.FromResult(0); + public Task BackfillAsync( + string dialogId, + bool force, + CancellationToken ct) => Task.FromResult(0); /// - public Task> ReadRecentAsync(string dialogId, int limit, CancellationToken ct) + public Task> ReadRecentAsync( + string dialogId, + int limit, + CancellationToken ct) => Task.FromResult>([]); /// - public Task> SearchAsync(string query, int limit, CancellationToken ct) + public Task> SearchAsync( + string query, + int limit, + CancellationToken ct) => Task.FromResult>([]); /// @@ -76,7 +95,10 @@ public sealed class LocalTelegramGateway : ITelegramGateway => Task.FromResult(new TelegramChannelInfoDto(dialogId, string.Empty, string.Empty, string.Empty, SourceDefaults.DefaultHue, null, false)); /// - public Task ReadForEvalAsync(string dialogId, int limit, CancellationToken ct) + public Task ReadForEvalAsync( + string dialogId, + int limit, + CancellationToken ct) => Task.FromResult(new TelegramEvalReadDto(false, "no_history", [])); /// diff --git a/src/core/Deal.Infrastructure/Integrations/Services/MlOutboxQueue.cs b/src/core/Deal.Infrastructure/Integrations/Services/MlOutboxQueue.cs index 383a334..71cc198 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/MlOutboxQueue.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/MlOutboxQueue.cs @@ -35,7 +35,12 @@ internal static class MlOutboxQueue /// Вес сигнала (1.0 — учить, −1.0 — снять метку). /// Токен отмены. /// Задача завершается после записи строки (отправку делает фоновый флашер). - public static async Task PushAsync(IMlLearningStore learningStore, string text, string label, double delta, CancellationToken ct) + public static async Task PushAsync( + IMlLearningStore learningStore, + string text, + string label, + double delta, + CancellationToken ct) { string trimmedText = (text ?? string.Empty).Trim(); string trimmedLabel = (label ?? string.Empty).Trim(); diff --git a/src/core/Deal.Infrastructure/Integrations/Services/MlStatusCache.cs b/src/core/Deal.Infrastructure/Integrations/Services/MlStatusCache.cs index 82a45a9..84efd8b 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/MlStatusCache.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/MlStatusCache.cs @@ -91,7 +91,10 @@ public sealed class MlStatusCache /// Id тенанта (формат N). /// Статус модели. /// Доступность сервиса. - public void Set(string tenantId, MlServiceStatusDto service, bool reachable) + public void Set( + string tenantId, + MlServiceStatusDto service, + bool reachable) { _entries[tenantId] = new Snapshot(service, reachable, _utcNow().ToUnixTimeMilliseconds()); } diff --git a/src/core/Deal.Infrastructure/Integrations/Services/TokenUsageRecorder.cs b/src/core/Deal.Infrastructure/Integrations/Services/TokenUsageRecorder.cs index 909ca22..3420c0b 100644 --- a/src/core/Deal.Infrastructure/Integrations/Services/TokenUsageRecorder.cs +++ b/src/core/Deal.Infrastructure/Integrations/Services/TokenUsageRecorder.cs @@ -89,7 +89,11 @@ public sealed class TokenUsageRecorder /// Id активного провайдера (deepseek/openai/anthropic/…; событие истории). /// Модель провайдера (событие истории). /// Токен отмены. - public async Task AddAsync(Usage? usage, string provider, string model, CancellationToken ct) + public async Task AddAsync( + Usage? usage, + string provider, + string model, + CancellationToken ct) { if (usage is null) { @@ -124,7 +128,11 @@ public sealed class TokenUsageRecorder /// Модель/вид локального ML-вызова (событие истории). /// Токен отмены. /// Оценка токенов (для тестов/наблюдаемости). - public async Task AddEstimatedAsync(string? text, string provider, string model, CancellationToken ct) + public async Task AddEstimatedAsync( + string? text, + string provider, + string model, + CancellationToken ct) { long promptTokens = EstimateTokens(text); // Прикладная метрика (этап 12, пакет A): вызов локального ML + оценка токенов (та же точка, diff --git a/src/core/Deal.Infrastructure/Integrations/Storage/Services/LocalFileStorage.cs b/src/core/Deal.Infrastructure/Integrations/Storage/Services/LocalFileStorage.cs index f816221..45fb7e5 100644 --- a/src/core/Deal.Infrastructure/Integrations/Storage/Services/LocalFileStorage.cs +++ b/src/core/Deal.Infrastructure/Integrations/Storage/Services/LocalFileStorage.cs @@ -47,7 +47,11 @@ public sealed class LocalFileStorage : IFileStorage public override string ToString() => $"LocalFileStorage (root: {_rootPath})"; /// - public async Task PutAsync(string objectKey, Stream content, string contentType, CancellationToken ct) + public async Task PutAsync( + string objectKey, + Stream content, + string contentType, + CancellationToken ct) { ArgumentNullException.ThrowIfNull(content); diff --git a/src/core/Deal.Infrastructure/Integrations/Storage/Services/MinioFileStorage.cs b/src/core/Deal.Infrastructure/Integrations/Storage/Services/MinioFileStorage.cs index 47a6789..5865665 100644 --- a/src/core/Deal.Infrastructure/Integrations/Storage/Services/MinioFileStorage.cs +++ b/src/core/Deal.Infrastructure/Integrations/Storage/Services/MinioFileStorage.cs @@ -83,7 +83,11 @@ public sealed class MinioFileStorage : IFileStorage public override string ToString() => $"MinioFileStorage (endpoint: {_endpoint}; bucket: {_bucket})"; /// - public async Task PutAsync(string objectKey, Stream content, string contentType, CancellationToken ct) + public async Task PutAsync( + string objectKey, + Stream content, + string contentType, + CancellationToken ct) { ArgumentNullException.ThrowIfNull(content); diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/AuthStore.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/AuthStore.cs index 28d6718..929d1b0 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/AuthStore.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/AuthStore.cs @@ -105,7 +105,10 @@ public sealed class AuthStore(DealDbContext dbContext) : IAuthStore } /// - public async Task UpdatePasswordHashAsync(Guid userId, string passwordHash, CancellationToken ct) + public async Task UpdatePasswordHashAsync( + Guid userId, + string passwordHash, + CancellationToken ct) { await dbContext.Users .Where(u => u.Id == userId) diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/DiscoveryStore.Blacklist.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/DiscoveryStore.Blacklist.cs index 2af4daf..ecdd4f1 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/DiscoveryStore.Blacklist.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/DiscoveryStore.Blacklist.cs @@ -18,7 +18,11 @@ namespace Deal.Infrastructure.Persistence.Repositories; public sealed partial class DiscoveryStore { /// - public async Task UpsertBlacklistAsync(string dialogId, string name, string reason, CancellationToken ct) + public async Task UpsertBlacklistAsync( + string dialogId, + string name, + string reason, + CancellationToken ct) { DiscBlacklistEntity? row = await _dbContext.DiscBlacklist .FirstOrDefaultAsync(entry => entry.DialogId == dialogId, ct); diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/DiscoveryStore.Candidates.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/DiscoveryStore.Candidates.cs index 3af0f99..a13d151 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/DiscoveryStore.Candidates.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/DiscoveryStore.Candidates.cs @@ -19,7 +19,10 @@ namespace Deal.Infrastructure.Persistence.Repositories; public sealed partial class DiscoveryStore { /// - public async Task> ListCandidatesAsync(string taskId, string? status, CancellationToken ct) + public async Task> ListCandidatesAsync( + string taskId, + string? status, + CancellationToken ct) { IQueryable query = _dbContext.DiscCandidates.AsNoTracking().Where(candidate => candidate.TaskId == taskId); if (status is not null) @@ -78,7 +81,10 @@ public sealed partial class DiscoveryStore } /// - public async Task PatchCandidateAsync(string dialogId, DiscoveryCandidatePatch patch, CancellationToken ct) + public async Task PatchCandidateAsync( + string dialogId, + DiscoveryCandidatePatch patch, + CancellationToken ct) { DiscCandidateEntity? row = await _dbContext.DiscCandidates .FirstOrDefaultAsync(candidate => candidate.DialogId == dialogId, ct); @@ -94,7 +100,10 @@ public sealed partial class DiscoveryStore } /// - public async Task SetCandidateStatusAsync(string dialogId, string status, CancellationToken ct) + public async Task SetCandidateStatusAsync( + string dialogId, + string status, + CancellationToken ct) { DiscCandidateEntity? row = await _dbContext.DiscCandidates .FirstOrDefaultAsync(candidate => candidate.DialogId == dialogId, ct); @@ -110,7 +119,10 @@ public sealed partial class DiscoveryStore } /// - public async Task SetCandidateJoinedAsync(string dialogId, bool autoJoined, CancellationToken ct) + public async Task SetCandidateJoinedAsync( + string dialogId, + bool autoJoined, + CancellationToken ct) { DiscCandidateEntity? row = await _dbContext.DiscCandidates .FirstOrDefaultAsync(candidate => candidate.DialogId == dialogId, ct); diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/DiscoveryStore.Logs.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/DiscoveryStore.Logs.cs index 45b656e..5523a35 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/DiscoveryStore.Logs.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/DiscoveryStore.Logs.cs @@ -18,7 +18,12 @@ namespace Deal.Infrastructure.Persistence.Repositories; public sealed partial class DiscoveryStore { /// - public async Task AddLogAsync(string logId, string taskId, string logEvent, string text, CancellationToken ct) + public async Task AddLogAsync( + string logId, + string taskId, + string logEvent, + string text, + CancellationToken ct) { _dbContext.DiscLog.Add(new DiscLogEntity { @@ -32,14 +37,20 @@ public sealed partial class DiscoveryStore } /// - public async Task CountLogEventAsync(string logEvent, DateTimeOffset sinceUtc, CancellationToken ct) + public async Task CountLogEventAsync( + string logEvent, + DateTimeOffset sinceUtc, + CancellationToken ct) { // ban_guard.joins_today_auto L29–35: число событий лога по типу с начала UTC-суток (счётчик квоты). return await _dbContext.DiscLog.CountAsync(row => row.Event == logEvent && row.CreatedAt >= sinceUtc, ct); } /// - public async Task> ListTaskLogAsync(string taskId, int limit, CancellationToken ct) + public async Task> ListTaskLogAsync( + string taskId, + int limit, + CancellationToken ct) { List rows = await _dbContext.DiscLog .AsNoTracking() diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/DiscoveryStore.Tasks.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/DiscoveryStore.Tasks.cs index eb0b2fb..d23f8f8 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/DiscoveryStore.Tasks.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/DiscoveryStore.Tasks.cs @@ -61,7 +61,10 @@ public sealed partial class DiscoveryStore } /// - public async Task PatchTaskAsync(string taskId, DiscoveryTaskPatch patch, CancellationToken ct) + public async Task PatchTaskAsync( + string taskId, + DiscoveryTaskPatch patch, + CancellationToken ct) { DiscTaskEntity? row = await _dbContext.DiscTasks .FirstOrDefaultAsync(task => task.Id == taskId, ct); @@ -95,7 +98,10 @@ public sealed partial class DiscoveryStore } /// - public async Task SetTaskRunningAsync(string taskId, bool resetProgress, CancellationToken ct) + public async Task SetTaskRunningAsync( + string taskId, + bool resetProgress, + CancellationToken ct) { DiscTaskEntity? row = await _dbContext.DiscTasks .FirstOrDefaultAsync(task => task.Id == taskId, ct); @@ -155,7 +161,11 @@ public sealed partial class DiscoveryStore } /// - public async Task BumpTaskCounterAsync(string taskId, DiscoveryCounterField field, int n, CancellationToken ct) + public async Task BumpTaskCounterAsync( + string taskId, + DiscoveryCounterField field, + int n, + CancellationToken ct) { DiscTaskEntity? row = await _dbContext.DiscTasks .FirstOrDefaultAsync(task => task.Id == taskId, ct); @@ -189,7 +199,11 @@ public sealed partial class DiscoveryStore } /// - public async Task AdvanceSearchAsync(string taskId, int nextIndex, bool searchDone, CancellationToken ct) + public async Task AdvanceSearchAsync( + string taskId, + int nextIndex, + bool searchDone, + CancellationToken ct) { DiscTaskEntity? row = await _dbContext.DiscTasks .FirstOrDefaultAsync(task => task.Id == taskId, ct); diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/GlobalSettingsStore.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/GlobalSettingsStore.cs index fb608d4..526f184 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/GlobalSettingsStore.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/GlobalSettingsStore.cs @@ -29,7 +29,10 @@ public sealed class GlobalSettingsStore(DealDbContext dbContext) : IGlobalSettin } /// - public async Task SetAsync(string key, string valueJson, CancellationToken ct) + public async Task SetAsync( + string key, + string valueJson, + CancellationToken ct) { // Upsert по ключу (PK): существующая строка обновляется, отсутствующая — добавляется. GlobalSettingEntity? entity = await dbContext.GlobalSettings diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/InviteStore.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/InviteStore.cs index 6e174e7..107e63f 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/InviteStore.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/InviteStore.cs @@ -57,7 +57,11 @@ public sealed class InviteStore(DealDbContext dbContext) : IInviteStore } /// - public async Task UpdateStatusAsync(string code, string status, DateTimeOffset? activatedAt, CancellationToken ct) + public async Task UpdateStatusAsync( + string code, + string status, + DateTimeOffset? activatedAt, + CancellationToken ct) { var entity = await dbContext.Invites.SingleOrDefaultAsync(i => i.Code == code, ct); if (entity is null) @@ -72,7 +76,10 @@ public sealed class InviteStore(DealDbContext dbContext) : IInviteStore } /// - public async Task TryActivateAsync(string code, DateTimeOffset activatedAt, CancellationToken ct) + public async Task TryActivateAsync( + string code, + DateTimeOffset activatedAt, + CancellationToken ct) { // CAS (Task 6): атомарный условный UPDATE — pending → activated только если статус всё ещё pending, // иначе параллельный отзыв/активация не перезаписываются (ExecuteUpdate выполняется одним оператором SQL). diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.Cards.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.Cards.cs index dfd3b09..cd613a5 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.Cards.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.Cards.cs @@ -39,7 +39,10 @@ public sealed partial class KanbanStore } /// - public async Task> SearchCardsAsync(string q, int limit, CancellationToken ct) + public async Task> SearchCardsAsync( + string q, + int limit, + CancellationToken ct) { string query = q.Trim(); if (query.Length == 0) @@ -87,7 +90,10 @@ public sealed partial class KanbanStore } /// - public async Task GetCardBySourceAsync(string dialogId, long msgId, CancellationToken ct) + public async Task GetCardBySourceAsync( + string dialogId, + long msgId, + CancellationToken ct) { if (string.IsNullOrEmpty(dialogId)) { @@ -192,7 +198,10 @@ public sealed partial class KanbanStore } /// - public async Task UpdateSeenAsync(string? cardId, string? col, CancellationToken ct) + public async Task UpdateSeenAsync( + string? cardId, + string? col, + CancellationToken ct) { // Три режима leads.py mark_seen L250–256: одна карточка | колонка | все карточки. IQueryable queryable = _dbContext.Cards; diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.Comments.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.Comments.cs index 7420e24..11c9f70 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.Comments.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.Comments.cs @@ -33,7 +33,12 @@ public sealed partial class KanbanStore } /// - public async Task AddCommentAsync(string commentId, string cardId, string by, string text, CancellationToken ct) + public async Task AddCommentAsync( + string commentId, + string cardId, + string by, + string text, + CancellationToken ct) { // CreatedAt — UTC-now (leads.py add_comment L259–265). Целостность ссылки на карточку держит FK: // комментарий без карточки не запишется (404-семантику несуществующей карточки отдаёт сервис, diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.Containers.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.Containers.cs index 6ffff8d..6b1900b 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.Containers.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.Containers.cs @@ -93,7 +93,10 @@ public sealed partial class KanbanStore } /// - public async Task ReorderContainersAsync(string space, IReadOnlyList containerIds, CancellationToken ct) + public async Task ReorderContainersAsync( + string space, + IReadOnlyList containerIds, + CancellationToken ct) { // Позиции 0..N-1 — одна транзакция: при сбое середины порядок не остаётся частично проставленным. await using var transaction = await _dbContext.Database.BeginTransactionAsync(ct); diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.Selected.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.Selected.cs index 7828ca3..5c3bb89 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.Selected.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.Selected.cs @@ -33,7 +33,10 @@ public sealed partial class KanbanStore } /// - public async Task PatchCardAsync(string cardId, CardPatch patch, CancellationToken ct) + public async Task PatchCardAsync( + string cardId, + CardPatch patch, + CancellationToken ct) { // Точечная правка по присутствующим полям патча: null-поле не меняется, JSON-поля заменяются // целиком, в конце — bump UpdatedAt (patch_card projects.py L159–187). @@ -71,7 +74,10 @@ public sealed partial class KanbanStore } /// - public async Task AddLinkAsync(string cardId, CardLinkDto link, CancellationToken ct) + public async Task AddLinkAsync( + string cardId, + CardLinkDto link, + CancellationToken ct) { // Один UPDATE — jsonb-append в конец массива links (add_link L133–143 + bump UpdatedAt). return await ExecuteJsonMutationAsync( @@ -86,7 +92,10 @@ public sealed partial class KanbanStore } /// - public async Task RemoveLinkAsync(string cardId, string linkId, CancellationToken ct) + public async Task RemoveLinkAsync( + string cardId, + string linkId, + CancellationToken ct) { // Один UPDATE — jsonb-фильтрация массива по id элемента (remove_link L146–150 + bump UpdatedAt). return await ExecuteJsonMutationAsync( @@ -105,7 +114,10 @@ public sealed partial class KanbanStore } /// - public async Task AddFileAsync(string cardId, CardFileDto file, CancellationToken ct) + public async Task AddFileAsync( + string cardId, + CardFileDto file, + CancellationToken ct) { // Один UPDATE — jsonb-append в конец массива files (files.py add_file L57–75 + bump UpdatedAt). return await ExecuteJsonMutationAsync( @@ -120,7 +132,10 @@ public sealed partial class KanbanStore } /// - public async Task RemoveFileAsync(string cardId, string fileId, CancellationToken ct) + public async Task RemoveFileAsync( + string cardId, + string fileId, + CancellationToken ct) { // Один UPDATE — jsonb-фильтрация массива files по id записи (files.py remove_file L86–94 + bump). return await ExecuteJsonMutationAsync( @@ -140,7 +155,11 @@ public sealed partial class KanbanStore /// public async Task MoveCardStageAsync( - string cardId, string containerId, CardHistoryDto historyEntry, long atMs, CancellationToken ct) + string cardId, + string containerId, + CardHistoryDto historyEntry, + long atMs, + CancellationToken ct) { // Смена контейнера-стадии: Col=containerId, сброс напоминания, updated_at=atMs, история + запись // (move_stage projects.py L202–216; Ruling 7). Нетрекинговое чтение + ExecuteUpdate. @@ -169,7 +188,10 @@ public sealed partial class KanbanStore } /// - public async Task SetReminderAsync(string cardId, long atMs, CancellationToken ct) + public async Task SetReminderAsync( + string cardId, + long atMs, + CancellationToken ct) { await _dbContext.Cards .Where(card => card.Id == cardId) diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.StorageRules.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.StorageRules.cs index 8fe03fe..c5e418a 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.StorageRules.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/KanbanStore.StorageRules.cs @@ -41,7 +41,10 @@ public sealed partial class KanbanStore } /// - public async Task ArchiveAsync(IReadOnlyList cardIds, DateTimeOffset archivedAt, CancellationToken ct) + public async Task ArchiveAsync( + IReadOnlyList cardIds, + DateTimeOffset archivedAt, + CancellationToken ct) { // Автоархив тика пачкой (tick_storage L462–473): один UPDATE вместо N по-карточных — как по-карточный // UpdateColumnAsync автоархива: col=archive, is_new=false, archived_at=now, matchHits пусто; @@ -121,7 +124,12 @@ public sealed partial class KanbanStore } /// - public async Task UpdateConversionAsync(string cardId, double? convFrom, double? convTo, string convCur, CancellationToken ct) + public async Task UpdateConversionAsync( + string cardId, + double? convFrom, + double? convTo, + string convCur, + CancellationToken ct) { // Пишутся только conv-поля карточки (Ruling 7); convCur пуст — конверсия снята. await _dbContext.Cards diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/MlLearningStore.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/MlLearningStore.cs index 473b948..7263939 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/MlLearningStore.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/MlLearningStore.cs @@ -29,7 +29,12 @@ public sealed class MlLearningStore(TenantDbContext dbContext) : IMlLearningStor public Task CountOutboxAsync(CancellationToken ct) => dbContext.MlOutbox.CountAsync(ct); /// - public async Task AddOutboxAsync(string id, string text, string label, double delta, CancellationToken ct) + public async Task AddOutboxAsync( + string id, + string text, + string label, + double delta, + CancellationToken ct) { dbContext.MlOutbox.Add(new MlOutboxEntity { diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/PipelineStore.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/PipelineStore.cs index 08a77be..8309aaf 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/PipelineStore.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/PipelineStore.cs @@ -36,7 +36,10 @@ public sealed class PipelineStore(TenantDbContext dbContext) : IPipelineStore // ── Очередь (QueueItems) ─────────────────────────────────────────────── /// - public Task ExistsDuplicateAsync(string dialogId, long? msgId, CancellationToken ct) + public Task ExistsDuplicateAsync( + string dialogId, + long? msgId, + CancellationToken ct) { // Дубль-гвард приёма (Ruling 2, enqueue L70–77): msgId null — проверять нечего. return msgId is null @@ -52,7 +55,10 @@ public sealed class PipelineStore(TenantDbContext dbContext) : IPipelineStore } /// - public async Task> ListAsync(string? status, int limit, CancellationToken ct) + public async Task> ListAsync( + string? status, + int limit, + CancellationToken ct) { IQueryable queryable = dbContext.QueueItems.AsNoTracking(); if (status is not null) @@ -72,7 +78,10 @@ public sealed class PipelineStore(TenantDbContext dbContext) : IPipelineStore => dbContext.QueueItems.CountAsync(item => item.Status == status, ct); /// - public async Task SetStatusAsync(string id, string status, CancellationToken ct) + public async Task SetStatusAsync( + string id, + string status, + CancellationToken ct) { // Смена статуса строки очереди (new → filtered в воркере): UpdatedAt = UTC-now (порт). await dbContext.QueueItems @@ -131,7 +140,10 @@ public sealed class PipelineStore(TenantDbContext dbContext) : IPipelineStore } /// - public async Task> ListPageAsync(int offset, int limit, CancellationToken ct) + public async Task> ListPageAsync( + int offset, + int limit, + CancellationToken ct) { List entities = await dbContext.RejectedItems .AsNoTracking() @@ -143,7 +155,11 @@ public sealed class PipelineStore(TenantDbContext dbContext) : IPipelineStore } /// - public async Task> SearchAsync(string q, int limitFts, int limitLike, CancellationToken ct) + public async Task> SearchAsync( + string q, + int limitFts, + int limitLike, + CancellationToken ct) { string query = q.Trim(); if (query.Length == 0) @@ -231,7 +247,11 @@ public sealed class PipelineStore(TenantDbContext dbContext) : IPipelineStore } /// - public async Task MarkReturnedAsync(string id, string reason, DateTimeOffset returnedAt, CancellationToken ct) + public async Task MarkReturnedAsync( + string id, + string reason, + DateTimeOffset returnedAt, + CancellationToken ct) { // Возврат из отсева: аудит-поля записи, строка не удаляется (return_to_queue L156–159). await dbContext.RejectedItems @@ -277,7 +297,10 @@ public sealed class PipelineStore(TenantDbContext dbContext) : IPipelineStore } /// - public async Task LinkAsync(string hash, string cardId, CancellationToken ct) + public async Task LinkAsync( + string hash, + string cardId, + CancellationToken ct) { // Связь заявки с созданной карточкой (Ruling 4, порядок AddCard → LinkDedup L512–513). await dbContext.DedupEntries diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/RateLimitCounterStore.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/RateLimitCounterStore.cs index 1a166e2..7265d3e 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/RateLimitCounterStore.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/RateLimitCounterStore.cs @@ -46,7 +46,11 @@ public sealed class RateLimitCounterStore(DealDbContext dbContext) : IRateLimitC /// public async Task IncrementAsync( - string key, DateTimeOffset windowStart, DateTimeOffset windowEnd, int amount, CancellationToken ct) + string key, + DateTimeOffset windowStart, + DateTimeOffset windowEnd, + int amount, + CancellationToken ct) { ArgumentException.ThrowIfNullOrEmpty(key); ArgumentOutOfRangeException.ThrowIfNegative(amount); @@ -94,7 +98,11 @@ public sealed class RateLimitCounterStore(DealDbContext dbContext) : IRateLimitC // ct: Токен отмены. // Возвращает: Значение счётчика после операции. private async Task IncrementNpgsqlAsync( - string key, DateTimeOffset windowStart, DateTimeOffset windowEnd, int amount, CancellationToken ct) + string key, + DateTimeOffset windowStart, + DateTimeOffset windowEnd, + int amount, + CancellationToken ct) { DbConnection connection = dbContext.Database.GetDbConnection(); bool openedHere = connection.State != ConnectionState.Open; @@ -127,7 +135,10 @@ public sealed class RateLimitCounterStore(DealDbContext dbContext) : IRateLimitC // command: Команда upsert. // name: Имя параметра (с префиксом @). // value: Значение. - private static void AddParameter(DbCommand command, string name, object value) + private static void AddParameter( + DbCommand command, + string name, + object value) { DbParameter parameter = command.CreateParameter(); parameter.ParameterName = name; @@ -136,7 +147,10 @@ public sealed class RateLimitCounterStore(DealDbContext dbContext) : IRateLimitC } /// - public async Task GetCountAsync(string key, DateTimeOffset windowStart, CancellationToken ct) + public async Task GetCountAsync( + string key, + DateTimeOffset windowStart, + CancellationToken ct) { ArgumentException.ThrowIfNullOrEmpty(key); diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/SettingsStore.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/SettingsStore.cs index 173dc71..be3e83f 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/SettingsStore.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/SettingsStore.cs @@ -41,7 +41,10 @@ public sealed class SettingsStore(TenantDbContext dbContext) : ISettingsStore } /// - public async Task SetAsync(string key, string valueJson, CancellationToken ct) + public async Task SetAsync( + string key, + string valueJson, + CancellationToken ct) { // Upsert по ключу (PK): существующая строка обновляется, отсутствующая — добавляется. TenantSettingEntity? entity = await dbContext.Settings diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/TelegramStore.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/TelegramStore.cs index c30a887..b47f0eb 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/TelegramStore.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/TelegramStore.cs @@ -22,7 +22,9 @@ public sealed class TelegramStore(TenantDbContext dbContext) : ITelegramStore { /// public async Task SyncFromTelegramAsync( - IReadOnlyCollection entries, bool autoMonitorNew, CancellationToken ct) + IReadOnlyCollection entries, + bool autoMonitorNew, + CancellationToken ct) { if (entries.Count == 0) { @@ -95,7 +97,10 @@ public sealed class TelegramStore(TenantDbContext dbContext) : ITelegramStore } /// - public Task SetMonitorAsync(string dialogId, bool enabled, CancellationToken ct) + public Task SetMonitorAsync( + string dialogId, + bool enabled, + CancellationToken ct) { return dbContext.Dialogs .Where(dialog => dialog.Id == dialogId) @@ -148,7 +153,12 @@ public sealed class TelegramStore(TenantDbContext dbContext) : ITelegramStore /// public async Task UpsertDiscoveredMonitoredAsync( - string dialogId, string name, string handle, string kind, string hue, CancellationToken ct) + string dialogId, + string name, + string handle, + string kind, + string hue, + CancellationToken ct) { // 1:1 python add_dialog_monitored L850–873 (INSERT/ON CONFLICT DO UPDATE): строка каталога после // discovery-вступления — метаданные источника, monitor=TRUE, backfilled=FALSE (разбор подхватит @@ -185,7 +195,11 @@ public sealed class TelegramStore(TenantDbContext dbContext) : ITelegramStore /// public async Task SavePreviewAsync( - string messageId, string dialogId, string text, DateTimeOffset msgAt, CancellationToken ct) + string messageId, + string dialogId, + string text, + DateTimeOffset msgAt, + CancellationToken ct) { bool exists = await dbContext.TgMessages.AnyAsync(message => message.Id == messageId, ct); if (exists) @@ -205,7 +219,11 @@ public sealed class TelegramStore(TenantDbContext dbContext) : ITelegramStore } /// - public Task TouchDialogLastAsync(string dialogId, string text, DateTimeOffset at, CancellationToken ct) + public Task TouchDialogLastAsync( + string dialogId, + string text, + DateTimeOffset at, + CancellationToken ct) { // python _on_message L270–273: last_text/last_at/updated_at одним моментом приёма. return dbContext.Dialogs @@ -219,7 +237,10 @@ public sealed class TelegramStore(TenantDbContext dbContext) : ITelegramStore } /// - public async Task> ListMessagesAsync(string dialogId, int limit, CancellationToken ct) + public async Task> ListMessagesAsync( + string dialogId, + int limit, + CancellationToken ct) { List rows = await dbContext.TgMessages .Where(message => message.DialogId == dialogId) diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/TenantLimitStore.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/TenantLimitStore.cs index a0049d8..19775a1 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/TenantLimitStore.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/TenantLimitStore.cs @@ -75,7 +75,9 @@ public sealed class TenantLimitStore : ITenantLimitStore /// public async Task GetOrCreateAsync( - Guid tenantId, CancellationToken ct, TokenLimitDefaults? defaults = null) + Guid tenantId, + CancellationToken ct, + TokenLimitDefaults? defaults = null) { TenantLimitEntity entity = await LoadOrCreateAsync(tenantId, defaults ?? _defaults, ct); return ToLimitDto(entity); @@ -90,7 +92,10 @@ public sealed class TenantLimitStore : ITenantLimitStore } /// - public async Task AddUsageAsync(Guid tenantId, long tokens, CancellationToken ct) + public async Task AddUsageAsync( + Guid tenantId, + long tokens, + CancellationToken ct) { TenantLimitEntity entity = await LoadOrCreateAsync(tenantId, _defaults, ct); await ResetIfPeriodExpiredAsync(entity, ct); @@ -120,7 +125,11 @@ public sealed class TenantLimitStore : ITenantLimitStore } /// - public async Task UpdateBudgetAsync(Guid tenantId, long budgetTokens, string period, CancellationToken ct) + public async Task UpdateBudgetAsync( + Guid tenantId, + long budgetTokens, + string period, + CancellationToken ct) { ArgumentOutOfRangeException.ThrowIfNegative(budgetTokens); if (period != TenantLimitPeriods.Month && period != TenantLimitPeriods.Day) @@ -175,7 +184,10 @@ public sealed class TenantLimitStore : ITenantLimitStore // defaults: Дефолт-параметры создаваемой строки. // ct: Токен отмены. // Возвращает: Отслеживаемая строка лимита. - private async Task LoadOrCreateAsync(Guid tenantId, TokenLimitDefaults defaults, CancellationToken ct) + private async Task LoadOrCreateAsync( + Guid tenantId, + TokenLimitDefaults defaults, + CancellationToken ct) { TenantLimitEntity? entity = await _dbContext.TenantLimits .SingleOrDefaultAsync(x => x.TenantId == tenantId, ct); diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/TenantRepository.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/TenantRepository.cs index 927909a..796ad3d 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/TenantRepository.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/TenantRepository.cs @@ -49,7 +49,10 @@ public sealed class TenantRepository(DealDbContext dbContext) : ITenantRepositor } /// - public async Task UpdateStatusAsync(Guid id, string status, CancellationToken ct) + public async Task UpdateStatusAsync( + Guid id, + string status, + CancellationToken ct) { // Отслеживаемая запись + SaveChanges (не ExecuteUpdateAsync): операция редкая (операторская админка), // зато семантика проверяема на InMemory-провайдере в unit-тестах. diff --git a/src/core/Deal.Infrastructure/Persistence/Repositories/TokenUsageEventStore.cs b/src/core/Deal.Infrastructure/Persistence/Repositories/TokenUsageEventStore.cs index 6e3fa1c..f85a460 100644 --- a/src/core/Deal.Infrastructure/Persistence/Repositories/TokenUsageEventStore.cs +++ b/src/core/Deal.Infrastructure/Persistence/Repositories/TokenUsageEventStore.cs @@ -97,8 +97,7 @@ public sealed class TokenUsageEventStore(DealDbContext dbContext) : ITokenUsageE // source: Отфильтрованный запрос. // ct: Токен отмены. // Возвращает: Строки агрегатов по дням. - private static async Task> AggregateByDayAsync( - IQueryable source, CancellationToken ct) + private static async Task> AggregateByDayAsync(IQueryable source, CancellationToken ct) { var rows = await source .GroupBy(e => new { e.At.Year, e.At.Month, e.At.Day }) @@ -131,8 +130,7 @@ public sealed class TokenUsageEventStore(DealDbContext dbContext) : ITokenUsageE // source: Отфильтрованный запрос. // ct: Токен отмены. // Возвращает: Строки агрегатов по тенантам. - private static async Task> AggregateByTenantAsync( - IQueryable source, CancellationToken ct) + private static async Task> AggregateByTenantAsync(IQueryable source, CancellationToken ct) { var rows = await source .GroupBy(e => e.TenantId) diff --git a/src/core/Deal.Infrastructure/ServiceCollectionExtensions.cs b/src/core/Deal.Infrastructure/ServiceCollectionExtensions.cs index 384a92f..fc50b79 100644 --- a/src/core/Deal.Infrastructure/ServiceCollectionExtensions.cs +++ b/src/core/Deal.Infrastructure/ServiceCollectionExtensions.cs @@ -62,8 +62,7 @@ public static class ServiceCollectionExtensions /// Дефолт-бюджет лениво создаваемых строк tenant_limits (Ruling 3; null — константа TokenBudgetDefaults). /// Передаётся из конфигурации/env DEAL_DEFAULT_AI_BUDGET в Program.cs (Task 8). /// Коллекция сервисов для цепочки вызовов. - public static IServiceCollection AddDealPersistence( - this IServiceCollection services, TokenLimitDefaults? tenantLimitDefaults = null) + public static IServiceCollection AddDealPersistence(this IServiceCollection services, TokenLimitDefaults? tenantLimitDefaults = null) { services.AddScoped(); @@ -168,8 +167,11 @@ public static class ServiceCollectionExtensions /// см. Program.cs (Tasks 6/8). /// public static IServiceCollection AddDealIntegrations( - this IServiceCollection services, MlServiceOptions mlOptions, AiServiceOptions aiOptions, - TelegramServiceOptions telegramOptions, MtlsCertificates? mtlsCertificates = null) + this IServiceCollection services, + MlServiceOptions mlOptions, + AiServiceOptions aiOptions, + TelegramServiceOptions telegramOptions, + MtlsCertificates? mtlsCertificates = null) { // Сертификаты mTLS-каналов (Ruling 6, Task 13): null (флаг DEAL_MTLS_ENABLED выключен) — каналы // остаются plaintext + service-token (dev); при включённом флаге каждый транспорт подписывает запрос diff --git a/src/core/Deal.Infrastructure/Services/CardMover.cs b/src/core/Deal.Infrastructure/Services/CardMover.cs index eb37acb..f917f2e 100644 --- a/src/core/Deal.Infrastructure/Services/CardMover.cs +++ b/src/core/Deal.Infrastructure/Services/CardMover.cs @@ -26,7 +26,10 @@ public sealed class CardMover(CardsService cardsService) : ICardMover { /// public async Task MoveAsync( - string cardId, string toContainerId, TransitionContext ctx, CancellationToken ct) + string cardId, + string toContainerId, + TransitionContext ctx, + CancellationToken ct) { // Контекст перехода (инициатор/обучение) учтён внутри маршрутов: дашборд-перенос обучает ML по // цели пользователя, переход по стадии пишет историю и сбрасывает напоминание. Отдельного diff --git a/src/core/Deal.Infrastructure/Tenancy/DefaultContainerProvisioner.cs b/src/core/Deal.Infrastructure/Tenancy/DefaultContainerProvisioner.cs index 867084e..c12e7cf 100644 --- a/src/core/Deal.Infrastructure/Tenancy/DefaultContainerProvisioner.cs +++ b/src/core/Deal.Infrastructure/Tenancy/DefaultContainerProvisioner.cs @@ -52,7 +52,10 @@ public static class DefaultContainerProvisioner /// Контекст схемы тенанта. /// Текущее UTC-время (CreatedAt новых строк). /// Токен отмены. - public static async Task EnsureAsync(TenantDbContext db, DateTimeOffset now, CancellationToken ct) + public static async Task EnsureAsync( + TenantDbContext db, + DateTimeOffset now, + CancellationToken ct) { HashSet existing = (await db.Containers .AsNoTracking() @@ -104,7 +107,12 @@ public static class DefaultContainerProvisioner // now: Время создания строки. // Возвращает: Строка контейнера. private static ContainerEntity BuildService( - string id, string name, string color, int order, ContainerPolicyDto policy, DateTimeOffset now) + string id, + string name, + string color, + int order, + ContainerPolicyDto policy, + DateTimeOffset now) { return new ContainerEntity { @@ -125,7 +133,10 @@ public static class DefaultContainerProvisioner // db: Контекст тенанта. // existing: Множество существующих id (пополняется). // container: Строка контейнера. - private static void AddIfMissing(TenantDbContext db, HashSet existing, ContainerEntity container) + private static void AddIfMissing( + TenantDbContext db, + HashSet existing, + ContainerEntity container) { if (existing.Add(container.Id)) { diff --git a/src/core/Deal.Infrastructure/Tenancy/TenantProvisioningService.cs b/src/core/Deal.Infrastructure/Tenancy/TenantProvisioningService.cs index 2a70d86..c6cfc51 100644 --- a/src/core/Deal.Infrastructure/Tenancy/TenantProvisioningService.cs +++ b/src/core/Deal.Infrastructure/Tenancy/TenantProvisioningService.cs @@ -66,7 +66,10 @@ public sealed class TenantProvisioningService(ConnectionStringProvider connectio // tenantId: Идентификатор тенанта. // schemaName: Имя схемы тенанта (для MigrationsHistoryTable). // ct: Токен отмены. - private async Task ApplyTenantMigrationsAsync(TenantId tenantId, string schemaName, CancellationToken ct) + private async Task ApplyTenantMigrationsAsync( + TenantId tenantId, + string schemaName, + CancellationToken ct) { // Строка уже с Search Path=tenant_<id> (ForSchemaDdl) — таблицы бессхемной модели TenantDbContext // лягут в схему тенанта. DDL применяется мигратор-ролью при её наличии (см. ConnectionStringProvider). diff --git a/src/core/Deal.Modules.Cards/Application/Abstractions/ICardMover.cs b/src/core/Deal.Modules.Cards/Application/Abstractions/ICardMover.cs index 9c3a9f4..15b9565 100644 --- a/src/core/Deal.Modules.Cards/Application/Abstractions/ICardMover.cs +++ b/src/core/Deal.Modules.Cards/Application/Abstractions/ICardMover.cs @@ -24,5 +24,8 @@ public interface ICardMover /// Токен отмены. /// Результат: Error (400-текст отказа) | Exists=false (карточки нет, 404) | успех (Exists=true). public Task MoveAsync( - string cardId, string toContainerId, TransitionContext ctx, CancellationToken ct); + string cardId, + string toContainerId, + TransitionContext ctx, + CancellationToken ct); } diff --git a/src/core/Deal.Modules.Cards/Application/Models/DefaultContainer.cs b/src/core/Deal.Modules.Cards/Application/Models/DefaultContainer.cs index 67756d3..f50258d 100644 --- a/src/core/Deal.Modules.Cards/Application/Models/DefaultContainer.cs +++ b/src/core/Deal.Modules.Cards/Application/Models/DefaultContainer.cs @@ -17,7 +17,13 @@ public sealed record DefaultContainer /// Позиция в канбане. /// Терминальная зона (только ручная очистка, без возврата). /// Возврат из контейнера разрешён. - public DefaultContainer(string id, string name, string color, int order, bool terminal, bool restore) + public DefaultContainer( + string id, + string name, + string color, + int order, + bool terminal, + bool restore) { Id = id; Name = name; diff --git a/src/core/Deal.Modules.Discovery/Application/Abstractions/IDiscoveryStore.cs b/src/core/Deal.Modules.Discovery/Application/Abstractions/IDiscoveryStore.cs index 305136f..c462b50 100644 --- a/src/core/Deal.Modules.Discovery/Application/Abstractions/IDiscoveryStore.cs +++ b/src/core/Deal.Modules.Discovery/Application/Abstractions/IDiscoveryStore.cs @@ -52,7 +52,10 @@ public interface IDiscoveryStore /// Изменяемые поля (null — поле не меняется; keywords — полная замена JSON-массива). /// Токен отмены. /// True — строка обновлена; false — задачи нет (404-семантика сервиса). - public Task PatchTaskAsync(string taskId, DiscoveryTaskPatch patch, CancellationToken ct); + public Task PatchTaskAsync( + string taskId, + DiscoveryTaskPatch patch, + CancellationToken ct); /// /// Удаляет задачу вместе с её кандидатами и логом (delete_task L314–318; чёрный список общий — не трогается). @@ -70,7 +73,10 @@ public interface IDiscoveryStore /// счётчики found/evaluated/joined/rejected=0 (L333–339); False — draft/paused: только статус (L342–344). /// Токен отмены. /// True — задача обновлена; false — строки нет. - public Task SetTaskRunningAsync(string taskId, bool resetProgress, CancellationToken ct); + public Task SetTaskRunningAsync( + string taskId, + bool resetProgress, + CancellationToken ct); /// /// Переводит задачу в paused (pause_task L349–356; прогресс поиска сохраняется). @@ -96,7 +102,11 @@ public interface IDiscoveryStore /// Приращение (вызывающий передаёт ≥1; ≤0 — no-op, как max(0, n) python). /// Токен отмены. /// True — задача обновлена; false — строки нет. - public Task BumpTaskCounterAsync(string taskId, DiscoveryCounterField field, int n, CancellationToken ct); + public Task BumpTaskCounterAsync( + string taskId, + DiscoveryCounterField field, + int n, + CancellationToken ct); /// /// Продвигает индекс поиска (advance_search L371–380: search_idx = nextIndex, search_done, bump UpdatedAt). @@ -106,7 +116,11 @@ public interface IDiscoveryStore /// search_done = nextIndex ≥ keywords.Count (считает сервис). /// Токен отмены. /// True — задача обновлена; false — строки нет. - public Task AdvanceSearchAsync(string taskId, int nextIndex, bool searchDone, CancellationToken ct); + public Task AdvanceSearchAsync( + string taskId, + int nextIndex, + bool searchDone, + CancellationToken ct); /// /// Сумма plan_joins активных задач (python _active_plan_sum L85–91: status NOT IN done/failed). @@ -125,7 +139,10 @@ public interface IDiscoveryStore /// Статус-фильтр (см. ); null — все статусы. /// Токен отмены. /// Кандидаты задачи (marks/topics — типизированными списками); пусто — кандидатов нет. - public Task> ListCandidatesAsync(string taskId, string? status, CancellationToken ct); + public Task> ListCandidatesAsync( + string taskId, + string? status, + CancellationToken ct); /// /// Кандидат по dialog_id (python _get_candidate L400–402; ключ — источник, кандидат один). @@ -143,7 +160,10 @@ public interface IDiscoveryStore /// Нижняя граница CreatedAt (UTC; бан-гард передаёт начало текущих суток). /// Токен отмены. /// Число событий с начала суток (0 — событий нет). - public Task CountLogEventAsync(string logEvent, DateTimeOffset sinceUtc, CancellationToken ct); + public Task CountLogEventAsync( + string logEvent, + DateTimeOffset sinceUtc, + CancellationToken ct); /// /// Увеличивает join_failures кандидата, если запись жива и в статусе review (воркер L411–416; bump UpdatedAt). @@ -191,7 +211,10 @@ public interface IDiscoveryStore /// Изменяемые поля (null — поле не меняется; marks/topics — полная замена JSON). /// Токен отмены. /// True — строка обновлена; false — кандидата нет. - public Task PatchCandidateAsync(string dialogId, DiscoveryCandidatePatch patch, CancellationToken ct); + public Task PatchCandidateAsync( + string dialogId, + DiscoveryCandidatePatch patch, + CancellationToken ct); /// /// Переводит кандидата в new/review (set_candidate_status L497–515: только эти статусы; bump UpdatedAt). @@ -200,7 +223,10 @@ public interface IDiscoveryStore /// Новый статус: new|review (валидирует сервис). /// Токен отмены. /// True — строка обновлена; false — кандидата нет. - public Task SetCandidateStatusAsync(string dialogId, string status, CancellationToken ct); + public Task SetCandidateStatusAsync( + string dialogId, + string status, + CancellationToken ct); /// /// Ставит кандидату joined + auto_joined (mark_joined L531–534; bump UpdatedAt). @@ -209,7 +235,10 @@ public interface IDiscoveryStore /// True — авто-вступление воркера; false — ручное (join из UI). /// Токен отмены. /// True — строка обновлена; false — кандидата нет. - public Task SetCandidateJoinedAsync(string dialogId, bool autoJoined, CancellationToken ct); + public Task SetCandidateJoinedAsync( + string dialogId, + bool autoJoined, + CancellationToken ct); /// /// Ставит кандидату rejected (mark_rejected L555–558; bump UpdatedAt; счётчик/лог/чёрный список — сервис). @@ -228,7 +257,11 @@ public interface IDiscoveryStore /// Имя источника (вызывающий передаёт нормализованное: пусто → DialogId). /// Причина добавления. /// Токен отмены. - public Task UpsertBlacklistAsync(string dialogId, string name, string reason, CancellationToken ct); + public Task UpsertBlacklistAsync( + string dialogId, + string name, + string reason, + CancellationToken ct); /// /// Снимает источник с чёрного списка (remove_blacklist L581–582; нет строки — no-op). @@ -262,7 +295,12 @@ public interface IDiscoveryStore /// Событие (см. ). /// Текст/детали события. /// Токен отмены. - public Task AddLogAsync(string logId, string taskId, string logEvent, string text, CancellationToken ct); + public Task AddLogAsync( + string logId, + string taskId, + string logEvent, + string text, + CancellationToken ct); /// /// Последние события задачи, новые сверху (task_log L601–608: ORDER BY created_at DESC LIMIT). @@ -271,5 +309,8 @@ public interface IDiscoveryStore /// Сколько последних записей (кламп 1..500 выполняет сервис; дефолт 100). /// Токен отмены. /// События задачи от новых к старым; пусто — лога нет. - public Task> ListTaskLogAsync(string taskId, int limit, CancellationToken ct); + public Task> ListTaskLogAsync( + string taskId, + int limit, + CancellationToken ct); } diff --git a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryBanGuard.cs b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryBanGuard.cs index 632cef6..2183e0e 100644 --- a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryBanGuard.cs +++ b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryBanGuard.cs @@ -40,7 +40,10 @@ public sealed class DiscoveryBanGuard /// KV-настройки тенанта (discJoinLimit/discFloodDay/discPaused). /// Источник текущего времени (UTC; тесты передают фиксированные «часы», эталон /// FakeDiscoveryStore). По умолчанию — . - public DiscoveryBanGuard(IDiscoveryStore store, ISettingsStore settings, Func? utcNow = null) + public DiscoveryBanGuard( + IDiscoveryStore store, + ISettingsStore settings, + Func? utcNow = null) { ArgumentNullException.ThrowIfNull(store); ArgumentNullException.ThrowIfNull(settings); diff --git a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryBlacklistService.cs b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryBlacklistService.cs index f21c877..8eaf234 100644 --- a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryBlacklistService.cs +++ b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryBlacklistService.cs @@ -26,7 +26,11 @@ public sealed class DiscoveryBlacklistService(IDiscoveryStore store) /// Причина добавления («отклонено вручную», метка воркера). /// Токен отмены. /// Запись чёрного списка (echo после upsert; CreatedAt — первое добавление). - public async Task AddAsync(string dialogId, string name, string reason, CancellationToken ct) + public async Task AddAsync( + string dialogId, + string name, + string reason, + CancellationToken ct) { string normalizedName = (name ?? string.Empty).Trim(); if (normalizedName.Length == 0) diff --git a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryCandidatesService.cs b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryCandidatesService.cs index 3f32f2a..98b4beb 100644 --- a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryCandidatesService.cs +++ b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryCandidatesService.cs @@ -58,7 +58,10 @@ public sealed class DiscoveryCandidatesService( /// Статус-фильтр (new|review|joined|rejected); null — все. /// Токен отмены. /// Кандидаты задачи (marks/topics — списками). - public Task> ListAsync(string taskId, string? status, CancellationToken ct) + public Task> ListAsync( + string taskId, + string? status, + CancellationToken ct) { return store.ListCandidatesAsync(taskId, status, ct); } @@ -91,7 +94,13 @@ public sealed class DiscoveryCandidatesService( /// Токен отмены. /// Новый кандидат либо null — источник пропущен (в лог записан skip). public async Task AddAsync( - string taskId, string dialogId, string name, string username, string kind, string hue, CancellationToken ct) + string taskId, + string dialogId, + string name, + string username, + string kind, + string hue, + CancellationToken ct) { DiscoveryTaskDto? task = await store.GetTaskAsync(taskId, ct).ConfigureAwait(false); if (task is null) @@ -156,7 +165,10 @@ public sealed class DiscoveryCandidatesService( /// Токен отмены. /// Обновлённый кандидат либо null — задачи/кандидата нет (или кандидат другой задачи). public async Task SetAsync( - string taskId, string dialogId, DiscoveryCandidatePatch patch, CancellationToken ct) + string taskId, + string dialogId, + DiscoveryCandidatePatch patch, + CancellationToken ct) { DiscoveryTaskDto? task = await store.GetTaskAsync(taskId, ct).ConfigureAwait(false); DiscoveryCandidateDto? current = await store.GetCandidateAsync(dialogId, ct).ConfigureAwait(false); @@ -190,7 +202,10 @@ public sealed class DiscoveryCandidatesService( /// Токен отмены. /// Обновлённый кандидат либо null (кандидата нет). /// Статус не new/review. - public async Task SetStatusAsync(string dialogId, string status, CancellationToken ct) + public async Task SetStatusAsync( + string dialogId, + string status, + CancellationToken ct) { if (!DiscoveryCandidateStatuses.IsTransitionAllowed(status)) { @@ -223,7 +238,10 @@ public sealed class DiscoveryCandidatesService( /// True — авто-вступление воркера; false — ручное (join из UI, Task 19). /// Токен отмены. /// Кандидат в joined либо null (кандидата нет). Повторный вызов для joined — идемпотентен (L528–529). - public async Task MarkJoinedAsync(string dialogId, bool auto, CancellationToken ct) + public async Task MarkJoinedAsync( + string dialogId, + bool auto, + CancellationToken ct) { DiscoveryCandidateDto? current = await store.GetCandidateAsync(dialogId, ct).ConfigureAwait(false); if (current is null) @@ -251,7 +269,10 @@ public sealed class DiscoveryCandidatesService( /// Токен отмены. /// Кандидат в rejected либо null (кандидата нет). Повторный вызов для rejected — идемпотентен (L552–553). /// Источник уже joined — отклонить нельзя. - public async Task MarkRejectedAsync(string dialogId, string reason, CancellationToken ct) + public async Task MarkRejectedAsync( + string dialogId, + string reason, + CancellationToken ct) { DiscoveryCandidateDto? current = await store.GetCandidateAsync(dialogId, ct).ConfigureAwait(false); if (current is null) diff --git a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryEvaluator.cs b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryEvaluator.cs index 1638bdc..8d50c2f 100644 --- a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryEvaluator.cs +++ b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryEvaluator.cs @@ -68,7 +68,10 @@ public sealed class DiscoveryEvaluator /// KV-настройки тенанта (mlEnabled/aiEnabled — ветки каскада). /// ML-порт (спам-отсев при mlEnabled; сбой — «не уверен»). /// ИИ-порт (фит при aiEnabled; сбой — эвристика, Ruling 10). - public DiscoveryEvaluator(ISettingsStore settings, IMlClient mlClient, IAiTools aiTools) + public DiscoveryEvaluator( + ISettingsStore settings, + IMlClient mlClient, + IAiTools aiTools) { ArgumentNullException.ThrowIfNull(settings); ArgumentNullException.ThrowIfNull(mlClient); @@ -88,7 +91,9 @@ public sealed class DiscoveryEvaluator /// Токен отмены. /// Агрегат выборки: fit_count, total, fit_ratio, per-message. public async Task EvaluateSampleAsync( - DiscoveryTaskDto task, IReadOnlyList texts, CancellationToken ct) + DiscoveryTaskDto task, + IReadOnlyList texts, + CancellationToken ct) { // Флаги веток (mlEnabled/aiEnabled) не меняются в пределах выборки: один типизированный снимок // настроек на выборку (C30) вместо двух GetAsync на каждое сообщение. @@ -121,7 +126,10 @@ public sealed class DiscoveryEvaluator /// Текст сообщения. /// Токен отмены. /// Вердикт: fit + причина + источник (heuristic|ml|ai). - public async Task EvaluateMessageAsync(DiscoveryTaskDto task, string text, CancellationToken ct) + public async Task EvaluateMessageAsync( + DiscoveryTaskDto task, + string text, + CancellationToken ct) { TenantSettingsSnapshot settingsSnapshot = await TenantSettingsSnapshot.LoadAsync(_settings, ct).ConfigureAwait(false); return await EvaluateMessageCoreAsync(task, text, settingsSnapshot, ct).ConfigureAwait(false); @@ -134,7 +142,10 @@ public sealed class DiscoveryEvaluator // ct: Токен отмены. // Возвращает: Вердикт: fit + причина + источник (heuristic|ml|ai). private async Task EvaluateMessageCoreAsync( - DiscoveryTaskDto task, string text, TenantSettingsSnapshot settingsSnapshot, CancellationToken ct) + DiscoveryTaskDto task, + string text, + TenantSettingsSnapshot settingsSnapshot, + CancellationToken ct) { string raw = text ?? string.Empty; if (raw.Trim().Length < MinTextLength) diff --git a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryLogService.cs b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryLogService.cs index 0d6a310..9cdade7 100644 --- a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryLogService.cs +++ b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryLogService.cs @@ -34,7 +34,11 @@ public sealed class DiscoveryLogService(IDiscoveryStore store) /// Текст/детали события (русская строка 1:1 с прототипом). /// Токен отмены. /// Завершается после записи. - public Task AddAsync(string taskId, string logEvent, string text, CancellationToken ct) + public Task AddAsync( + string taskId, + string logEvent, + string text, + CancellationToken ct) { return store.AddLogAsync(DiscoveryIdPrefixes.NewLogId(), taskId, logEvent, text ?? string.Empty, ct); } @@ -57,7 +61,10 @@ public sealed class DiscoveryLogService(IDiscoveryStore store) /// Запрошенный размер выборки. /// Токен отмены. /// События задачи от новых к старым. - public Task> TaskLogAsync(string taskId, int limit, CancellationToken ct) + public Task> TaskLogAsync( + string taskId, + int limit, + CancellationToken ct) { int clamped = Math.Max(MinLogLimit, Math.Min(MaxLogLimit, limit)); return store.ListTaskLogAsync(taskId, clamped, ct); diff --git a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryPlanGuard.cs b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryPlanGuard.cs index 3dcbae5..8c3f78e 100644 --- a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryPlanGuard.cs +++ b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryPlanGuard.cs @@ -77,7 +77,10 @@ public sealed class DiscoveryPlanGuard(IDiscoveryStore store, ISettingsStore set /// Id задачи, исключаемой из занятого бюджета (patch-рост плана); null — создание. /// Токен отмены. /// Бюджет исчерпан (текст python L108–110). - public async Task AssertBudgetAsync(int planJoins, string? excludeTaskId, CancellationToken ct) + public async Task AssertBudgetAsync( + int planJoins, + string? excludeTaskId, + CancellationToken ct) { int limit = await PlanLimitAsync(ct).ConfigureAwait(false); int used = await store.SumActivePlanAsync(excludeTaskId, ct).ConfigureAwait(false); diff --git a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryTasksService.cs b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryTasksService.cs index b61b4bc..cd7a986 100644 --- a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryTasksService.cs +++ b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryTasksService.cs @@ -128,7 +128,10 @@ public sealed class DiscoveryTasksService(IDiscoveryStore store, DiscoveryPlanGu /// Токен отмены. /// Обновлённая задача либо null (404 «Задача не найдена»). /// Новый план вне границ / бюджет исчерпан. - public async Task PatchAsync(string taskId, DiscoveryTaskPatch patch, CancellationToken ct) + public async Task PatchAsync( + string taskId, + DiscoveryTaskPatch patch, + CancellationToken ct) { DiscoveryTaskDto? current = await store.GetTaskAsync(taskId, ct).ConfigureAwait(false); if (current is null) @@ -226,7 +229,11 @@ public sealed class DiscoveryTasksService(IDiscoveryStore store, DiscoveryPlanGu /// Приращение (по умолчанию 1). /// Токен отмены. /// True — счётчик увеличен; false — задачи нет. - public Task BumpCounterAsync(string taskId, DiscoveryCounterField field, int n, CancellationToken ct) + public Task BumpCounterAsync( + string taskId, + DiscoveryCounterField field, + int n, + CancellationToken ct) { return store.BumpTaskCounterAsync(taskId, field, Math.Max(0, n), ct); } @@ -286,7 +293,10 @@ public sealed class DiscoveryTasksService(IDiscoveryStore store, DiscoveryPlanGu } // Кламп значения в границы [min, max] (эталон clamp python). - private static int Clamp(int value, int min, int max) + private static int Clamp( + int value, + int min, + int max) { return Math.Max(min, Math.Min(max, value)); } diff --git a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Evaluate.cs b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Evaluate.cs index 46020cf..a8765a7 100644 --- a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Evaluate.cs +++ b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Evaluate.cs @@ -18,7 +18,9 @@ public sealed partial class DiscoveryWorkerService // ct: Токен отмены. // Возвращает: review — кандидат оценён; skip — пропущен (delete); error — сбой шага. private async Task EvalStepAsync( - DiscoveryTaskDto task, DiscoveryCandidateDto candidate, CancellationToken ct) + DiscoveryTaskDto task, + DiscoveryCandidateDto candidate, + CancellationToken ct) { string taskId = task.Id; string dialogId = candidate.DialogId; diff --git a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Join.cs b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Join.cs index 2ec3970..665a934 100644 --- a/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Join.cs +++ b/src/core/Deal.Modules.Discovery/Application/Services/DiscoveryWorkerService.Join.cs @@ -18,7 +18,9 @@ public sealed partial class DiscoveryWorkerService // ct: Токен отмены. // Возвращает: join/reject/flood/error/skip — действие; none — выход без вступления (изменения за паузу). private async Task JoinStepAsync( - DiscoveryTaskDto task, DiscoveryCandidateDto candidate, CancellationToken ct) + DiscoveryTaskDto task, + DiscoveryCandidateDto candidate, + CancellationToken ct) { string taskId = task.Id; string dialogId = candidate.DialogId; diff --git a/src/core/Deal.Modules.Kanban/Application/Abstractions/ICardStore.cs b/src/core/Deal.Modules.Kanban/Application/Abstractions/ICardStore.cs index 07616d9..15224aa 100644 --- a/src/core/Deal.Modules.Kanban/Application/Abstractions/ICardStore.cs +++ b/src/core/Deal.Modules.Kanban/Application/Abstractions/ICardStore.cs @@ -72,7 +72,10 @@ public interface ICardStore /// Пространство переставляемых контейнеров. /// Id контейнеров в новом порядке. /// Токен отмены. - public Task ReorderContainersAsync(string space, IReadOnlyList containerIds, CancellationToken ct); + public Task ReorderContainersAsync( + string space, + IReadOnlyList containerIds, + CancellationToken ct); // ── Карточки ────────────────────────────────────────────────────────── @@ -100,7 +103,10 @@ public interface ICardStore /// Токен отмены. /// Полные карточки (комментарии приложены, time посчитан): FTS-кандидаты по /// убыванию ts_rank (SearchTsv @@ plainto_tsquery), затем LIKE-дополнение, внутри — ReceivedAt DESC. - public Task> SearchCardsAsync(string q, int limit, CancellationToken ct); + public Task> SearchCardsAsync( + string q, + int limit, + CancellationToken ct); /// /// Одна карточка по id (GET /api/cards/{id}, а также перечитывание после переноса). @@ -119,7 +125,10 @@ public interface ICardStore /// Id исходного сообщения в Telegram. /// Токен отмены. /// Карточка или null, если сообщение не становилось карточкой. - public Task GetCardBySourceAsync(string dialogId, long msgId, CancellationToken ct); + public Task GetCardBySourceAsync( + string dialogId, + long msgId, + CancellationToken ct); /// /// Создаёт карточку из готового снимка (пайплайн этапа 4, ручное создание; CreatedAt — UTC-now). @@ -135,7 +144,10 @@ public interface ICardStore /// Изменяемые поля (null — поле не меняется; JSON-поля — полная замена). /// Токен отмены. /// True — строка обновлена; false — карточки нет (404-семантика сервиса). - public Task PatchCardAsync(string cardId, CardPatch patch, CancellationToken ct); + public Task PatchCardAsync( + string cardId, + CardPatch patch, + CancellationToken ct); /// /// Атомарно дописывает ссылку в JSON-массив links ОДНИМ UPDATE (jsonb-append) + bump UpdatedAt. @@ -144,7 +156,10 @@ public interface ICardStore /// Готовая ссылка {id,name,url} (id сгенерирован модулем). /// Токен отмены. /// True — строка обновлена; false — карточки нет (404-семантика сервиса). - public Task AddLinkAsync(string cardId, CardLinkDto link, CancellationToken ct); + public Task AddLinkAsync( + string cardId, + CardLinkDto link, + CancellationToken ct); /// /// Атомарно убирает из JSON-массива links элемент с указанным id ОДНИМ UPDATE (jsonb-фильтрация) + bump UpdatedAt. @@ -153,7 +168,10 @@ public interface ICardStore /// Id удаляемой ссылки (pl_...). /// Токен отмены. /// True — строка обновлена; false — карточки нет (404-семантика сервиса). - public Task RemoveLinkAsync(string cardId, string linkId, CancellationToken ct); + public Task RemoveLinkAsync( + string cardId, + string linkId, + CancellationToken ct); /// /// Атомарно дописывает метаданные файла в JSON-массив files ОДНИМ UPDATE (jsonb-append) + bump UpdatedAt. @@ -162,7 +180,10 @@ public interface ICardStore /// Готовые метаданные {id,name,size,kind,label,objectKey} (id сгенерирован модулем). /// Токен отмены. /// True — строка обновлена; false — карточки нет (404-семантика сервиса). - public Task AddFileAsync(string cardId, CardFileDto file, CancellationToken ct); + public Task AddFileAsync( + string cardId, + CardFileDto file, + CancellationToken ct); /// /// Атомарно убирает из JSON-массива files элемент с указанным id ОДНИМ UPDATE (jsonb-фильтрация) + bump UpdatedAt. @@ -171,7 +192,10 @@ public interface ICardStore /// Id удаляемой записи файла (pf_...). /// Токен отмены. /// True — строка обновлена; false — карточки нет (404-семантика сервиса). - public Task RemoveFileAsync(string cardId, string fileId, CancellationToken ct); + public Task RemoveFileAsync( + string cardId, + string fileId, + CancellationToken ct); /// /// Смена контейнера-стадии: один UPDATE (col, reminder_at=NULL, reminder_fired=false, updated_at=atMs) @@ -183,7 +207,12 @@ public interface ICardStore /// Время переноса, epoch-ms (пишется в updated_at и в запись истории). /// Токен отмены. /// True — строка обновлена; false — карточки нет (404-семантика сервиса). - public Task MoveCardStageAsync(string cardId, string containerId, CardHistoryDto historyEntry, long atMs, CancellationToken ct); + public Task MoveCardStageAsync( + string cardId, + string containerId, + CardHistoryDto historyEntry, + long atMs, + CancellationToken ct); /// /// Устанавливает напоминание: reminder_at, reminder_fired=false + bump UpdatedAt (set_reminder projects.py L236–243). @@ -191,7 +220,10 @@ public interface ICardStore /// Id карточки (c_...). /// Время напоминания, epoch-ms. /// Токен отмены. - public Task SetReminderAsync(string cardId, long atMs, CancellationToken ct); + public Task SetReminderAsync( + string cardId, + long atMs, + CancellationToken ct); /// /// Снимает напоминание: reminder_at=NULL, reminder_fired=false (clear_reminder projects.py L246–247). @@ -253,7 +285,10 @@ public interface ICardStore /// Id карточки либо null. /// Колонка либо null. /// Токен отмены. - public Task UpdateSeenAsync(string? cardId, string? col, CancellationToken ct); + public Task UpdateSeenAsync( + string? cardId, + string? col, + CancellationToken ct); /// /// Удаляет карточку навсегда: Cards + комментарии (FK cascade) + строки дедупа карточки @@ -297,7 +332,12 @@ public interface ICardStore /// Автор («Вы» — свои комментарии). /// Текст (непустой — валидирует сервис, 400 «Пустой комментарий»). /// Токен отмены. - public Task AddCommentAsync(string commentId, string cardId, string by, string text, CancellationToken ct); + public Task AddCommentAsync( + string commentId, + string cardId, + string by, + string text, + CancellationToken ct); // ── Журнал действий (CardMoves = learning_log) ──────────────────────── @@ -348,7 +388,10 @@ public interface ICardStore /// Момент архивации (один «now» тика). /// Токен отмены. /// Сколько карточек реально архивировано (0 — кандидатов не было/уже не в рабочих колонках). - public Task ArchiveAsync(IReadOnlyList cardIds, DateTimeOffset archivedAt, CancellationToken ct); + public Task ArchiveAsync( + IReadOnlyList cardIds, + DateTimeOffset archivedAt, + CancellationToken ct); /// /// Кандидаты на очистку архива: col='archive' и ArchivedAt старше срока (tick_storage L475–478). @@ -392,7 +435,12 @@ public interface ICardStore /// Сконвертированная верхняя граница, либо null. /// Валюта конверсии (целевая валюта тенанта); пусто — конверсия снята. /// Токен отмены. - public Task UpdateConversionAsync(string cardId, double? convFrom, double? convTo, string convCur, CancellationToken ct); + public Task UpdateConversionAsync( + string cardId, + double? convFrom, + double? convTo, + string convCur, + CancellationToken ct); // ── Эвристика ИИ-предложений (Ruling 3, Task 14) ─────────────────────── diff --git a/src/core/Deal.Modules.Kanban/Application/Abstractions/IMlLearningStore.cs b/src/core/Deal.Modules.Kanban/Application/Abstractions/IMlLearningStore.cs index 57e8b86..29f6d1c 100644 --- a/src/core/Deal.Modules.Kanban/Application/Abstractions/IMlLearningStore.cs +++ b/src/core/Deal.Modules.Kanban/Application/Abstractions/IMlLearningStore.cs @@ -41,7 +41,12 @@ public interface IMlLearningStore /// Метка обучения: id доски, spam либо t:hire|t:order. /// Вес сигнала (1.0 — учить, −1.0 — снять метку). /// Токен отмены. - public Task AddOutboxAsync(string id, string text, string label, double delta, CancellationToken ct); + public Task AddOutboxAsync( + string id, + string text, + string label, + double delta, + CancellationToken ct); /// /// Полная очистка очереди обучения: DELETE FROM MlOutbox (ml_client.reset_model L122). Журнал CardMoves не трогается. diff --git a/src/core/Deal.Modules.Kanban/Application/ColumnRules/AmountParser.cs b/src/core/Deal.Modules.Kanban/Application/ColumnRules/AmountParser.cs index 84575bb..c27f8eb 100644 --- a/src/core/Deal.Modules.Kanban/Application/ColumnRules/AmountParser.cs +++ b/src/core/Deal.Modules.Kanban/Application/ColumnRules/AmountParser.cs @@ -68,7 +68,10 @@ public static class AmountParser var occupied = new List<(int Start, int End)>(); // Свободен ли диапазон (не пересекается с уже занятым конструкцией выше) — прототип _free L106–107. - static bool IsFree(List<(int Start, int End)> spans, int start, int end) + static bool IsFree( + List<(int Start, int End)> spans, + int start, + int end) { foreach ((int os, int oe) in spans) { @@ -81,7 +84,12 @@ public static class AmountParser return true; } - void Add(double? from, double? to, string? cur, int start, int end) + void Add( + double? from, + double? to, + string? cur, + int start, + int end) { if (cur is not null && (from is not null || to is not null) && IsFree(occupied, start, end)) { diff --git a/src/core/Deal.Modules.Kanban/Application/ColumnRules/BudgetInRange.cs b/src/core/Deal.Modules.Kanban/Application/ColumnRules/BudgetInRange.cs index e41e71f..26a8766 100644 --- a/src/core/Deal.Modules.Kanban/Application/ColumnRules/BudgetInRange.cs +++ b/src/core/Deal.Modules.Kanban/Application/ColumnRules/BudgetInRange.cs @@ -28,7 +28,10 @@ public static class BudgetInRange /// True — есть сумма, которая после конвертации (при необходимости) ≥ from и ≤ to; обе границы null → True; /// пустые amounts или непереводимая валюта → False. /// - public static bool IsInRange(IReadOnlyList amounts, BudgetRangeDto budget, IReadOnlyDictionary? rates) + public static bool IsInRange( + IReadOnlyList amounts, + BudgetRangeDto budget, + IReadOnlyDictionary? rates) { double? lo = budget.From; double? hi = budget.To; diff --git a/src/core/Deal.Modules.Kanban/Application/ColumnRules/ColumnMatcher.cs b/src/core/Deal.Modules.Kanban/Application/ColumnRules/ColumnMatcher.cs index f54bb76..09a3aec 100644 --- a/src/core/Deal.Modules.Kanban/Application/ColumnRules/ColumnMatcher.cs +++ b/src/core/Deal.Modules.Kanban/Application/ColumnRules/ColumnMatcher.cs @@ -24,7 +24,10 @@ public static class ColumnMatcher /// Курсы для конвертации бюджета (см. ); null — если бюджет в /// другой валюте, суммы не конвертируются и группа бюджета не совпадает. /// True — текст прошёл правила в режиме all/any. - public static bool MatchText(ContainerRulesDto? rules, string? text, IReadOnlyDictionary? rates = null) + public static bool MatchText( + ContainerRulesDto? rules, + string? text, + IReadOnlyDictionary? rates = null) { if (rules is null) { diff --git a/src/core/Deal.Modules.Kanban/Application/ColumnRules/ColumnRules.cs b/src/core/Deal.Modules.Kanban/Application/ColumnRules/ColumnRules.cs index f59d2df..ac3f20c 100644 --- a/src/core/Deal.Modules.Kanban/Application/ColumnRules/ColumnRules.cs +++ b/src/core/Deal.Modules.Kanban/Application/ColumnRules/ColumnRules.cs @@ -28,7 +28,10 @@ public static class ColumnRules /// False — сработало слово-исключение (veto) либо текст не прошёл активные правила; /// True — правил нет/неактивны или текст прошёл правила. /// - public static bool ContainerAccepts(ContainerRulesDto? rules, string? text, IReadOnlyDictionary? rates = null) + public static bool ContainerAccepts( + ContainerRulesDto? rules, + string? text, + IReadOnlyDictionary? rates = null) { if (ColumnExclusions.IsExcluded(rules, text)) { @@ -50,7 +53,10 @@ public static class ColumnRules /// Текст сообщения. /// Курсы для конвертации бюджета. /// True — текст прошёл правила в режиме all/any (исключения не учитываются). - public static bool Matches(ContainerRulesDto? rules, string? text, IReadOnlyDictionary? rates = null) + public static bool Matches( + ContainerRulesDto? rules, + string? text, + IReadOnlyDictionary? rates = null) { return ColumnMatcher.MatchText(rules, text, rates); } @@ -75,7 +81,10 @@ public static class ColumnRules /// Список совпавших критериев (MatchHitDto label/term/word); для пустых/null правил и колонок без /// активных правил — [] (Ruling 2). Исключения (veto) в список не входят (прототип hits L271–296). /// - public static IReadOnlyList ComputeHits(ContainerRulesDto? rules, string? text, IReadOnlyDictionary? rates = null) + public static IReadOnlyList ComputeHits( + ContainerRulesDto? rules, + string? text, + IReadOnlyDictionary? rates = null) { if (!ColumnMatcher.HasActiveRules(rules)) { diff --git a/src/core/Deal.Modules.Kanban/Application/ColumnRules/MatchHitBuilder.cs b/src/core/Deal.Modules.Kanban/Application/ColumnRules/MatchHitBuilder.cs index 6351e5d..89f5396 100644 --- a/src/core/Deal.Modules.Kanban/Application/ColumnRules/MatchHitBuilder.cs +++ b/src/core/Deal.Modules.Kanban/Application/ColumnRules/MatchHitBuilder.cs @@ -50,7 +50,10 @@ public static class MatchHitBuilder /// Курсы для конвертации бюджета (см. ). /// Список совпадений по группам; порядок — direction → keywords → stack → grade → levels → /// locations → types → budget → prices. - public static IReadOnlyList BuildHits(ContainerRulesDto? rules, string? text, IReadOnlyDictionary? rates = null) + public static IReadOnlyList BuildHits( + ContainerRulesDto? rules, + string? text, + IReadOnlyDictionary? rates = null) { var result = new List(); if (rules is null) @@ -144,7 +147,10 @@ public static class MatchHitBuilder // label: Метка группы. // lower: Текст в нижнем регистре. // Возвращает: Hits по каждому совпавшему терму (term сохраняет регистр пользователя). - private static IEnumerable MatchedTermHits(IReadOnlyList? rawTerms, string label, string lower) + private static IEnumerable MatchedTermHits( + IReadOnlyList? rawTerms, + string label, + string lower) { if (rawTerms is null) { diff --git a/src/core/Deal.Modules.Kanban/Application/ColumnRules/RulesDescriber.cs b/src/core/Deal.Modules.Kanban/Application/ColumnRules/RulesDescriber.cs index 82645a3..71b3135 100644 --- a/src/core/Deal.Modules.Kanban/Application/ColumnRules/RulesDescriber.cs +++ b/src/core/Deal.Modules.Kanban/Application/ColumnRules/RulesDescriber.cs @@ -99,7 +99,10 @@ public static class RulesDescriber // parts: Накопитель частей описания. // range: Диапазон группы (null — группа выключена). // title: Подпись группы («бюджет»/«цена»). - private static void AddRangePart(List parts, BudgetRangeDto? range, string title) + private static void AddRangePart( + List parts, + BudgetRangeDto? range, + string title) { if (range is null || (range.From is null && range.To is null)) { diff --git a/src/core/Deal.Modules.Kanban/Application/Services/BudgetNormalizer.cs b/src/core/Deal.Modules.Kanban/Application/Services/BudgetNormalizer.cs index 421978d..24c0c73 100644 --- a/src/core/Deal.Modules.Kanban/Application/Services/BudgetNormalizer.cs +++ b/src/core/Deal.Modules.Kanban/Application/Services/BudgetNormalizer.cs @@ -174,7 +174,11 @@ public static class BudgetNormalizer // toCurrency: Целевая валюта (код). // rates: Курсы к рублю либо null. // Возвращает: Сумма в целевой валюте или null. - private static double? Convert(double amount, string fromCurrency, string toCurrency, IReadOnlyDictionary? rates) + private static double? Convert( + double amount, + string fromCurrency, + string toCurrency, + IReadOnlyDictionary? rates) { return rates is null ? null : RatesService.ConvertAmount(amount, fromCurrency, toCurrency, rates); } diff --git a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Files.cs b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Files.cs index 3ac8e40..e4c5638 100644 --- a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Files.cs +++ b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Files.cs @@ -101,7 +101,10 @@ public sealed partial class CardsService /// Id записи файла (pf_...). /// Токен отмены. /// Метаданные записи файла либо null (карточка/запись не найдены). - public async Task GetFileEntryAsync(string cardId, string fileId, CancellationToken ct) + public async Task GetFileEntryAsync( + string cardId, + string fileId, + CancellationToken ct) { CardDto? card = await _store.GetCardAsync(cardId, ct); if (card is null) @@ -124,7 +127,10 @@ public sealed partial class CardsService /// Id удаляемой записи файла (pf_...). /// Токен отмены. /// Карточка после удаления (без записи) либо null — карточки нет (404-семантика). - public async Task RemoveFileAsync(string cardId, string fileId, CancellationToken ct) + public async Task RemoveFileAsync( + string cardId, + string fileId, + CancellationToken ct) { CardDto? card = await _store.GetCardAsync(cardId, ct); if (card is null) @@ -152,7 +158,10 @@ public sealed partial class CardsService // fileId: Id записи файла (pf_...; уникальный суффикс ключа). // name: Имя файла как прислано (в ключ идёт санитизированная часть). // Возвращает: Ключ объекта (opaque для хранилища). - private static string BuildObjectKey(string cardId, string fileId, string name) + private static string BuildObjectKey( + string cardId, + string fileId, + string name) { return $"{ObjectRootSegment}/{cardId}/{fileId}_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}_{SanitizeKeyName(name)}"; } diff --git a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Helpers.cs b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Helpers.cs index 0116089..a632b21 100644 --- a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Helpers.cs +++ b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Helpers.cs @@ -42,7 +42,12 @@ public sealed partial class CardsService // hits: matchHits для новой колонки (пересчитаны вызывающим, Ruling 2). // action: Действие журнала: move/trash. // ct: Токен отмены. - private async Task MoveToColumnAsync(CardDto card, string toCol, IReadOnlyList hits, string action, CancellationToken ct) + private async Task MoveToColumnAsync( + CardDto card, + string toCol, + IReadOnlyList hits, + string action, + CancellationToken ct) { await _store.UpdateColumnAsync(new CardColumnUpdateDto( CardId: card.Id, @@ -60,7 +65,12 @@ public sealed partial class CardsService // fromCol: Прежняя колонка (для comment — null). // toCol: Новая колонка (для comment — null). // ct: Токен отмены. - private async Task LogMoveAsync(string cardId, string action, string? fromCol, string? toCol, CancellationToken ct) + private async Task LogMoveAsync( + string cardId, + string action, + string? fromCol, + string? toCol, + CancellationToken ct) { await _store.AddMoveAsync(new CardMoveDto( PrefixId.New(KanbanIdPrefixes.CardMove), @@ -75,7 +85,10 @@ public sealed partial class CardsService // text: Текст карточки для правил (source_msg или title). // ct: Токен отмены. // Возвращает: Список совпавших критериев; доски нет/правил нет → пусто. - private async Task> ComputeHitsForBoardAsync(string boardId, string text, CancellationToken ct) + private async Task> ComputeHitsForBoardAsync( + string boardId, + string text, + CancellationToken ct) { ContainerDto? board = await _store.GetContainerAsync(boardId, ct); return board is null @@ -89,7 +102,10 @@ public sealed partial class CardsService // text: Текст карточки для правил. // ct: Токен отмены. // Возвращает: Совпавшие критерии (label/term[/word]); нет активных правил → пусто. - private async Task> ComputeHitsAsync(ContainerRulesDto? rules, string text, CancellationToken ct) + private async Task> ComputeHitsAsync( + ContainerRulesDto? rules, + string text, + CancellationToken ct) { // Кэш курсов из типизированного снимка настроек (C30): null → мок-курсы (дефолт RatesService). IReadOnlyDictionary rates = diff --git a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Operations.cs b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Operations.cs index bc0d547..1a9d366 100644 --- a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Operations.cs +++ b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Operations.cs @@ -66,7 +66,10 @@ public sealed partial class CardsService /// Цель: inbox либо id доски (b_...). /// Токен отмены. /// Результат: Error (400) | Card=null (карточки нет, 404) | Card — карточка после переноса. - public async Task MoveDashboardCardAsync(string cardId, string toCol, CancellationToken ct) + public async Task MoveDashboardCardAsync( + string cardId, + string toCol, + CancellationToken ct) { ContainerDto? board = null; if (toCol != CardIds.Inbox) @@ -138,7 +141,10 @@ public sealed partial class CardsService /// True — писать сигнал «спам» (действие пользователя); false — не писать. /// Токен отмены. /// Карточка после переноса (при no-op — как была) либо null — карточки нет (404). - public async Task TrashCardAsync(string cardId, bool teach, CancellationToken ct) + public async Task TrashCardAsync( + string cardId, + bool teach, + CancellationToken ct) { CardDto? card = await _store.GetCardAsync(cardId, ct); if (card is null) @@ -255,7 +261,10 @@ public sealed partial class CardsService /// Текст комментария (непустой после Trim). /// Токен отмены. /// Результат: Error (400) | Comments=null (404) | Comments — список после добавления. - public async Task AddCommentAsync(string cardId, string text, CancellationToken ct) + public async Task AddCommentAsync( + string cardId, + string text, + CancellationToken ct) { string trimmed = (text ?? string.Empty).Trim(); if (trimmed.Length == 0) @@ -284,7 +293,10 @@ public sealed partial class CardsService /// Id карточки либо null/пусто. /// Колонка либо null/пусто (используется, когда cardId не задан). /// Токен отмены. - public Task MarkSeenAsync(string? cardId, string? col, CancellationToken ct) + public Task MarkSeenAsync( + string? cardId, + string? col, + CancellationToken ct) { return _store.UpdateSeenAsync( string.IsNullOrEmpty(cardId) ? null : cardId, diff --git a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Reminders.cs b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Reminders.cs index e1c18e4..cab9a87 100644 --- a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Reminders.cs +++ b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Reminders.cs @@ -48,7 +48,10 @@ public sealed partial class CardsService /// Токен отмены. /// Результат: Error (400) | Card=null без Error (404) | /// Card — карточка с напоминанием. - public async Task SetReminderAsync(string cardId, long atMs, CancellationToken ct) + public async Task SetReminderAsync( + string cardId, + long atMs, + CancellationToken ct) { CardDto? card = await _store.GetCardAsync(cardId, ct); if (card is null) diff --git a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Selected.cs b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Selected.cs index 9cf43b6..21c90b3 100644 --- a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Selected.cs +++ b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.Selected.cs @@ -168,7 +168,10 @@ public sealed partial class CardsService /// Тело PATCH: ключ → JSON-значение (наличие ключа = поле меняется). /// Токен отмены. /// Обновлённая карточка или null — карточки нет (404). - public async Task PatchCardAsync(string cardId, IReadOnlyDictionary body, CancellationToken ct) + public async Task PatchCardAsync( + string cardId, + IReadOnlyDictionary body, + CancellationToken ct) { ArgumentNullException.ThrowIfNull(body); @@ -190,7 +193,11 @@ public sealed partial class CardsService /// URL ссылки (без схемы — добавится https://). /// Токен отмены. /// Результат: Error (400 «Пустая ссылка») | Card=null без Error (404) | Card — карточка со ссылкой. - public async Task AddLinkAsync(string cardId, string name, string url, CancellationToken ct) + public async Task AddLinkAsync( + string cardId, + string name, + string url, + CancellationToken ct) { CardDto? card = await _store.GetCardAsync(cardId, ct); if (card is null) @@ -236,7 +243,10 @@ public sealed partial class CardsService /// Id удаляемой ссылки (pl_...). /// Токен отмены. /// Результат: Card=null без Error (404) | Card — карточка без ссылки. - public async Task RemoveLinkAsync(string cardId, string linkId, CancellationToken ct) + public async Task RemoveLinkAsync( + string cardId, + string linkId, + CancellationToken ct) { if (!await _store.RemoveLinkAsync(cardId, linkId, ct)) { @@ -262,7 +272,10 @@ public sealed partial class CardsService /// Токен отмены. /// Результат: Error «Неизвестная стадия» (400) | Card=null без Error (карточки нет, 404) | /// Card — карточка после переноса. - public async Task MoveStageCardAsync(string cardId, string containerId, CancellationToken ct) + public async Task MoveStageCardAsync( + string cardId, + string containerId, + CancellationToken ct) { if (!CardsDefaultContainers.Contains(containerId)) { diff --git a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.cs b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.cs index da7d047..cf96e17 100644 --- a/src/core/Deal.Modules.Kanban/Application/Services/CardsService.cs +++ b/src/core/Deal.Modules.Kanban/Application/Services/CardsService.cs @@ -52,7 +52,11 @@ public sealed partial class CardsService /// KV-хранилище настроек тенанта (курсы, напоминания). /// Клиент ML: PushAsync — обучающий сигнал действия, StatusAsync — счётчики counts. /// Файловое хранилище вложений карточки (объекты файлов). - public CardsService(ICardStore store, ISettingsStore settings, IMlClient mlClient, IFileStorage storage) + public CardsService( + ICardStore store, + ISettingsStore settings, + IMlClient mlClient, + IFileStorage storage) { _store = store; _settings = settings; diff --git a/src/core/Deal.Modules.Kanban/Application/Services/ContainersService.cs b/src/core/Deal.Modules.Kanban/Application/Services/ContainersService.cs index 836c5a0..e633f2d 100644 --- a/src/core/Deal.Modules.Kanban/Application/Services/ContainersService.cs +++ b/src/core/Deal.Modules.Kanban/Application/Services/ContainersService.cs @@ -122,7 +122,10 @@ public sealed class ContainersService(ICardStore store, ISettingsStore settings) /// Изменения; null-поле означает «не менять». /// Токен отмены. /// Контейнер после патча; null — контейнера нет (404 «Контейнер не найден»). - public async Task PatchAsync(string containerId, ContainerPatchDto patch, CancellationToken ct) + public async Task PatchAsync( + string containerId, + ContainerPatchDto patch, + CancellationToken ct) { ContainerDto? current = await store.GetContainerAsync(containerId, ct); if (current is null) @@ -171,7 +174,10 @@ public sealed class ContainersService(ICardStore store, ISettingsStore settings) /// Пространство переставляемых контейнеров. /// Id контейнеров в новом порядке. /// Токен отмены. - public Task ReorderAsync(string space, IReadOnlyList containerIds, CancellationToken ct) + public Task ReorderAsync( + string space, + IReadOnlyList containerIds, + CancellationToken ct) { return store.ReorderContainersAsync(space, containerIds, ct); } @@ -195,7 +201,10 @@ public sealed class ContainersService(ICardStore store, ISettingsStore settings) /// Изменяемые поля состояния. /// Токен отмены. /// Состояние колонки после merge. - public async Task PatchColStateAsync(string colId, ColumnStateDto patch, CancellationToken ct) + public async Task PatchColStateAsync( + string colId, + ColumnStateDto patch, + CancellationToken ct) { Dictionary state = await ReadColStateAsync(ct); state.TryGetValue(colId, out ColumnStateDto? current); diff --git a/src/core/Deal.Modules.Kanban/Application/Services/SuggestHeuristics.cs b/src/core/Deal.Modules.Kanban/Application/Services/SuggestHeuristics.cs index 70ce832..80b45c1 100644 --- a/src/core/Deal.Modules.Kanban/Application/Services/SuggestHeuristics.cs +++ b/src/core/Deal.Modules.Kanban/Application/Services/SuggestHeuristics.cs @@ -121,9 +121,7 @@ public static class SuggestHeuristics /// Карточки «Неразобранного» с непустым source_msg (ICardStore.ListInboxWithSourceAsync). /// Имена существующих (suggested=false) досок — похожие темы не предлагаются. /// Планы колонок (≤4, каждая ≥2 карточки); пусто — мало карточек/нечего сгруппировать. - public static IReadOnlyList PlanColumns( - IReadOnlyList inbox, - IReadOnlyList existingBoardNames) + public static IReadOnlyList PlanColumns(IReadOnlyList inbox, IReadOnlyList existingBoardNames) { // Окно анализа: свежие карточки с текстом, как выборка SQL L96–99 (сортировка — страховка // детерминизма: адаптер уже отдаёт received_at DESC, но вход не должен влиять на выход). diff --git a/src/core/Deal.Modules.Pipeline/Application/Abstractions/IPipelineStore.cs b/src/core/Deal.Modules.Pipeline/Application/Abstractions/IPipelineStore.cs index 9af99a6..6abb0be 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Abstractions/IPipelineStore.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Abstractions/IPipelineStore.cs @@ -30,7 +30,10 @@ public interface IPipelineStore /// Id исходного сообщения; null — проверка не выполняется (вернёт false). /// Токен отмены. /// true — строка с DialogId+MsgId уже в очереди (Telethon-дубль не пишем). - public Task ExistsDuplicateAsync(string dialogId, long? msgId, CancellationToken ct); + public Task ExistsDuplicateAsync( + string dialogId, + long? msgId, + CancellationToken ct); /// /// Добавляет строку очереди (вставка без гвардов — сервис приёма уже проверил дубль и обрезал текст). @@ -46,7 +49,10 @@ public interface IPipelineStore /// Максимум строк (лимиты сервиса: 12/4 у воркера, ≤500 у списка). /// Токен отмены. /// Строки очереди (включая внутренний для воркера). - public Task> ListAsync(string? status, int limit, CancellationToken ct); + public Task> ListAsync( + string? status, + int limit, + CancellationToken ct); /// /// Сколько строк очереди со статусом (для счётчиков queue_counts: new/ai=filtered, L207–215). @@ -62,7 +68,10 @@ public interface IPipelineStore /// Id строки (p_...). /// Новый статус new|filtered. /// Токен отмены. - public Task SetStatusAsync(string id, string status, CancellationToken ct); + public Task SetStatusAsync( + string id, + string status, + CancellationToken ct); /// /// Удаляет строку очереди безвозвратно (отсев на любом этапе; сброс dedup-claim — DeleteClaimAsync). @@ -89,7 +98,10 @@ public interface IPipelineStore /// Размер страницы (≤500; валидирует сервис). /// Токен отмены. /// Записи страницы (полные DTO с подписями этапа/источника). - public Task> ListPageAsync(int offset, int limit, CancellationToken ct); + public Task> ListPageAsync( + int offset, + int limit, + CancellationToken ct); /// /// Кандидаты поиска по отсеву ПОЛНЫМИ СТРОКАМИ: FTS-совпадения (SearchTsv @@ plainto_tsquery, @@ -102,7 +114,11 @@ public interface IPipelineStore /// Лимит LIKE-дополнения. /// Токен отмены. /// Упорядоченный список полных записей-кандидатов (FTS-ранжированные первыми, затем LIKE-дополнение). - public Task> SearchAsync(string q, int limitFts, int limitLike, CancellationToken ct); + public Task> SearchAsync( + string q, + int limitFts, + int limitLike, + CancellationToken ct); /// /// Всего записей отсева (rejected_count; счётчик вкладки «Обработка»). @@ -148,7 +164,11 @@ public interface IPipelineStore /// Причина возврата (уже обрезана сервисом до 500). /// Момент возврата (UTC). /// Токен отмены. - public Task MarkReturnedAsync(string id, string reason, DateTimeOffset returnedAt, CancellationToken ct); + public Task MarkReturnedAsync( + string id, + string reason, + DateTimeOffset returnedAt, + CancellationToken ct); // ── Дедуп (DedupEntries) ────────────────────────────────────────────── @@ -185,7 +205,10 @@ public interface IPipelineStore /// SHA1-hex нормализованного текста. /// Id созданной карточки (c_...). /// Токен отмены. - public Task LinkAsync(string hash, string cardId, CancellationToken ct); + public Task LinkAsync( + string hash, + string cardId, + CancellationToken ct); /// /// Чистит «мягкие» ссылки карточки при её жёстком удалении (Ruling 3: DELETE DedupEntries diff --git a/src/core/Deal.Modules.Pipeline/Application/Parse/LocalFieldsParser.cs b/src/core/Deal.Modules.Pipeline/Application/Parse/LocalFieldsParser.cs index c9a2656..9f1947d 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Parse/LocalFieldsParser.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Parse/LocalFieldsParser.cs @@ -97,7 +97,10 @@ public sealed class LocalFieldsParser(ISettingsStore store) /// Маркеры найма (как сохранены или дефолты; нормализуются внутри). /// Термины грейдов (как сохранены или дефолты; нормализуются внутри). /// Локальные поля: заголовок, суть, стек/грейд/бюджет/контакты, признак найма (known=false, board=null). - public static LocalParsedFields Parse(string? text, IReadOnlyCollection? hireMarkers, IReadOnlyCollection? levelTerms) + public static LocalParsedFields Parse( + string? text, + IReadOnlyCollection? hireMarkers, + IReadOnlyCollection? levelTerms) { var hire = NormalizeMarkers(hireMarkers); var levels = NormalizeMarkers(levelTerms); diff --git a/src/core/Deal.Modules.Pipeline/Application/Parse/MessageTextCleaner.cs b/src/core/Deal.Modules.Pipeline/Application/Parse/MessageTextCleaner.cs index 055374c..a55c9c4 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Parse/MessageTextCleaner.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Parse/MessageTextCleaner.cs @@ -226,7 +226,10 @@ public static class MessageTextCleaner // index: Позиция символа. // length: Длина последовательности в UTF-16 единицах (1 или 2). // Возвращает: Кодовая точка или EmptyCodePoint для непарного суррогата. - private static int DecodeCodePoint(string value, int index, out int length) + private static int DecodeCodePoint( + string value, + int index, + out int length) { char current = value[index]; if (char.IsHighSurrogate(current) && index + 1 < value.Length && char.IsLowSurrogate(value[index + 1])) diff --git a/src/core/Deal.Modules.Pipeline/Application/Parse/SummaryComposer.cs b/src/core/Deal.Modules.Pipeline/Application/Parse/SummaryComposer.cs index ba3b105..34d8df6 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Parse/SummaryComposer.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Parse/SummaryComposer.cs @@ -146,7 +146,10 @@ public static class SummaryComposer /// Очищенные непустые строки текста (первая — заголовок). /// Сколько первых строк пропустить (заголовок); по умолчанию 1. /// Суть одним абзацем (≤600 символов). - public static string LocalSummary(string? body, IReadOnlyList lines, int skipFirst = DefaultSkipFirst) + public static string LocalSummary( + string? body, + IReadOnlyList lines, + int skipFirst = DefaultSkipFirst) { var parts = new List(MaxSummaryParts); foreach (string line in lines.Skip(skipFirst)) diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/AiClassifyContextBuilder.cs b/src/core/Deal.Modules.Pipeline/Application/Services/AiClassifyContextBuilder.cs index 0be7972..76dc7af 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/AiClassifyContextBuilder.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/AiClassifyContextBuilder.cs @@ -186,7 +186,10 @@ public sealed class AiClassifyContextBuilder(ISettingsStore settings, ICardStore // defaultValue: Дефолт из SettingsDefaults (когда переопределения нет). // settingsSnapshot: Типизированный снимок настроек тенанта (промпты/«Сфера и ключи»). // Возвращает: Текст промпта с подставленными {domain}/{keywords}. - private static string FillPrompt(string key, string defaultValue, TenantSettingsSnapshot settingsSnapshot) + private static string FillPrompt( + string key, + string defaultValue, + TenantSettingsSnapshot settingsSnapshot) { string prompt = settingsSnapshot.GetString(key, defaultValue); string domain = settingsSnapshot.GetString( diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/CardComposer.cs b/src/core/Deal.Modules.Pipeline/Application/Services/CardComposer.cs index f8faade..7b87084 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/CardComposer.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/CardComposer.cs @@ -175,7 +175,10 @@ public sealed class CardComposer(ICardStore kanjStore, ISettingsStore settings) // text: Текст исходного сообщения (первый источник fallback). // summary: Блок «О заявке» (второй источник fallback — сумма часто уходит в «Условия»). // Возвращает: Нормализованный бюджет либо null — суммы с валютой нет ни в разборе, ни в тексте. - private static CardBudgetDto? ComposeBudget(AiBudgetDto? parsedBudget, string? text, string summary) + private static CardBudgetDto? ComposeBudget( + AiBudgetDto? parsedBudget, + string? text, + string summary) { if (parsedBudget is not null) { diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/CardReclassifier.cs b/src/core/Deal.Modules.Pipeline/Application/Services/CardReclassifier.cs index f2521ba..eba79e9 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/CardReclassifier.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/CardReclassifier.cs @@ -145,7 +145,10 @@ public sealed class CardReclassifier( // card: Карточка. // pass: Накопители прохода (настройки/счётчики/признак ИИ). // ct: Токен отмены. - private async Task ReclassifyOneAsync(CardDto card, Pass pass, CancellationToken ct) + private async Task ReclassifyOneAsync( + CardDto card, + Pass pass, + CancellationToken ct) { string text = card.SourceMsg; if (string.IsNullOrWhiteSpace(text)) @@ -228,7 +231,11 @@ public sealed class CardReclassifier( // text: Исходный текст (обучающий пример). // pass: Накопители прохода. // ct: Токен отмены. - private async Task TrashAsync(CardDto card, string text, Pass pass, CancellationToken ct) + private async Task TrashAsync( + CardDto card, + string text, + Pass pass, + CancellationToken ct) { // teach=false: журнал action=trash пишется, но сигнал «спам» кладём явно ниже — с весом ИИ (0.4). await cardsService.TrashCardAsync(card.Id, teach: false, ct); @@ -272,7 +279,10 @@ public sealed class CardReclassifier( // to: Верхняя граница. // cur: Валюта (пусто — нет). // Возвращает: Бюджет карточки либо null. - private static CardBudgetDto? ToBudget(double? from, double? to, string cur) => + private static CardBudgetDto? ToBudget( + double? from, + double? to, + string cur) => cur.Length == 0 ? null : new CardBudgetDto(from, to, cur); // Контакт карточки: новый из разбора, иначе — валидный старый (python L327–331). @@ -337,7 +347,11 @@ public sealed class CardReclassifier( // started: Проход выполнен. // reason: Причина (если проход не выполнен) либо null. // Возвращает: Итог с полями wire-контракта. - private static ReclassifyResultDto Build(Pass pass, int attempted, bool started, string? reason) => + private static ReclassifyResultDto Build( + Pass pass, + int attempted, + bool started, + string? reason) => new( Started: started, Busy: false, diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/MlReviewService.cs b/src/core/Deal.Modules.Pipeline/Application/Services/MlReviewService.cs index fc39efd..f3c4fc0 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/MlReviewService.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/MlReviewService.cs @@ -115,7 +115,10 @@ public sealed class MlReviewService( /// Сколько последних сообщений вернуть (кламп 1.., дефолт вызывающего). /// Токен отмены. /// Кандидаты (свежие первыми): текст, текущий вердикт и мнение ML по каждому. - public async Task> CandidatesAsync(string? dialogId, int limit, CancellationToken ct) + public async Task> CandidatesAsync( + string? dialogId, + int limit, + CancellationToken ct) { int take = Math.Clamp(limit, MinCandidates, MaxCandidates); string dialog = (dialogId ?? string.Empty).Trim(); @@ -178,7 +181,11 @@ public sealed class MlReviewService( /// Действие: skip | spam | board:<id>. /// Токен отмены. /// Результат решения; null — исходное сообщение не найдено (404-семантика эндпоинта). - public async Task ApplyAsync(string dialogId, long msgId, string? action, CancellationToken ct) + public async Task ApplyAsync( + string dialogId, + long msgId, + string? action, + CancellationToken ct) { string normalized = (action ?? string.Empty).Trim(); string dialog = (dialogId ?? string.Empty).Trim(); @@ -216,7 +223,12 @@ public sealed class MlReviewService( // text: Текст сообщения. // ct: Токен отмены. // Возвращает: Результат решения. - private async Task ApplySpamAsync(string dialog, long msgId, CardDto? card, string text, CancellationToken ct) + private async Task ApplySpamAsync( + string dialog, + long msgId, + CardDto? card, + string text, + CancellationToken ct) { if (card is not null) { @@ -301,7 +313,11 @@ public sealed class MlReviewService( // card: Карточка сообщения (уже прочитана вызывающим). // ct: Токен отмены. // Возвращает: Непустой текст либо null, если сообщения нет ни в одном источнике. - private async Task FindTextAsync(string dialog, long msgId, CardDto? card, CancellationToken ct) + private async Task FindTextAsync( + string dialog, + long msgId, + CardDto? card, + CancellationToken ct) { if (card is not null && !string.IsNullOrWhiteSpace(card.SourceMsg)) { @@ -331,7 +347,10 @@ public sealed class MlReviewService( // msgId: Id сообщения. // ct: Токен отмены. // Возвращает: Строка очереди либо null — сообщение уже обработано/не в очереди. - private async Task FindQueuedAsync(string dialog, long msgId, CancellationToken ct) + private async Task FindQueuedAsync( + string dialog, + long msgId, + CancellationToken ct) { IReadOnlyList queue = await pipelineStore.ListAsync(status: null, MaxScan, ct); foreach (QueueItemDto row in queue) diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineProcessingService.cs b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineProcessingService.cs index 558b9c1..f6adb37 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineProcessingService.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineProcessingService.cs @@ -177,7 +177,11 @@ public sealed class PipelineProcessingService( /// Размер страницы (clamp 1..500). /// Токен отмены. /// Страница {items, total, offset, limit}. - public async Task ListRejectedAsync(string q, int offset, int limit, CancellationToken ct) + public async Task ListRejectedAsync( + string q, + int offset, + int limit, + CancellationToken ct) { int clampedOffset = Math.Max(0, offset); int clampedLimit = Math.Clamp(limit, 1, MaxPageSize); @@ -215,7 +219,10 @@ public sealed class PipelineProcessingService( /// Причина возврата (trim, ≤500; пишется на запись для аудита). /// Токен отмены. /// null — записи нет (404); иначе результат: Error (400) либо {id, returned:true, returnedAt}. - public async Task ReturnAsync(string rejectedId, string reason, CancellationToken ct) + public async Task ReturnAsync( + string rejectedId, + string reason, + CancellationToken ct) { RejectedItemDto? row = await store.GetAsync(rejectedId, ct); if (row is null) diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Checks.cs b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Checks.cs index fe217a7..4c928dd 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Checks.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Checks.cs @@ -66,7 +66,11 @@ public sealed partial class PipelineWorkerService // run: Снимок настроек воркера (флаги budgetRequiredHire/Order). // ct: Токен отмены. // Возвращает: True — фильтр требует сумму, а её нет (сообщение пропускаем). - private Task SkipNoBudgetAsync(AiParsedCardDto parsed, string text, WorkerRunSettings run, CancellationToken ct) + private Task SkipNoBudgetAsync( + AiParsedCardDto parsed, + string text, + WorkerRunSettings run, + CancellationToken ct) { if (!run.BudgetRequiredHire && !run.BudgetRequiredOrder) { diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Decisions.cs b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Decisions.cs index 84f50c7..0a20228 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Decisions.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Decisions.cs @@ -51,7 +51,10 @@ public sealed partial class PipelineWorkerService // key: Ключ счётчика (mlDecisions|aiDecisions). // delta: Приращение (>0 — только такие и пишем). // ct: Токен отмены. - private async Task IncrementCounterAsync(string key, int delta, CancellationToken ct) + private async Task IncrementCounterAsync( + string key, + int delta, + CancellationToken ct) { SettingValue? row = await _settings.GetAsync(key, ct); int current = 0; diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Learning.cs b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Learning.cs index f839fd4..28561c7 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Learning.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Learning.cs @@ -64,7 +64,11 @@ public sealed partial class PipelineWorkerService // parsed: Разбор, на котором собрана карточка (тип/спам из классификатора). // text: Текст сообщения (обучающий пример — как source_msg карточки). // ct: Токен отмены. - private Task LearnFromAiCardAsync(CardDto card, AiParsedCardDto parsed, string text, CancellationToken ct) + private Task LearnFromAiCardAsync( + CardDto card, + AiParsedCardDto parsed, + string text, + CancellationToken ct) { // Логика сигналов вынесена в общий AiCardLearning (её же использует ручная переклассификация). return AiCardLearning.PushSignalsAsync(_kanjStore, _mlClient, card.Col, parsed, text, MlLearningLabels.AiPushWeight, ct); diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Pump.cs b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Pump.cs index 9c42fcf..95e8099 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Pump.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Pump.cs @@ -60,7 +60,10 @@ public sealed partial class PipelineWorkerService // state: Накопители результата pump (счётчики/карточки). // run: Снимок настроек воркера на проход (Ruling 8). // ct: Токен отмены. - private async Task PumpNewPassAsync(PumpState state, WorkerRunSettings run, CancellationToken ct) + private async Task PumpNewPassAsync( + PumpState state, + WorkerRunSettings run, + CancellationToken ct) { IReadOnlyList rows = await _store.ListAsync(PipelineQueueStatuses.New, NewBatchLimit, ct); foreach (QueueItemDto row in rows) @@ -227,7 +230,10 @@ public sealed partial class PipelineWorkerService // state: Накопители результата pump (счётчики/карточки). // run: Снимок настроек воркера на проход (Ruling 8). // ct: Токен отмены. - private async Task PumpFilteredPassAsync(PumpState state, WorkerRunSettings run, CancellationToken ct) + private async Task PumpFilteredPassAsync( + PumpState state, + WorkerRunSettings run, + CancellationToken ct) { IReadOnlyList rows = await _store.ListAsync(PipelineQueueStatuses.Filtered, FilteredBatchLimit, ct); foreach (QueueItemDto row in rows) diff --git a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Rejections.cs b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Rejections.cs index b7af9f6..abab740 100644 --- a/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Rejections.cs +++ b/src/core/Deal.Modules.Pipeline/Application/Services/PipelineWorkerService.Rejections.cs @@ -35,7 +35,13 @@ public sealed partial class PipelineWorkerService // reason: Причина (текст 1:1 с прототипом; режется сервисом до 500). // kw: Сработавшая фраза/маркер правила (пусто — не правило). // ct: Токен отмены. - private Task RejectRowAsync(QueueItemDto row, string source, string stage, string reason, string kw, CancellationToken ct) + private Task RejectRowAsync( + QueueItemDto row, + string source, + string stage, + string reason, + string kw, + CancellationToken ct) { return _processing.RejectAsync(new RejectRecord { @@ -58,7 +64,11 @@ public sealed partial class PipelineWorkerService // digest: Хэш текста (для снятия незанятого dedup-claim). // withDedup: Снять claim дедупа (true — сообщение можно обработать заново после отсева). // ct: Токен отмены. - private async Task DropRowAsync(QueueItemDto row, string digest, bool withDedup, CancellationToken ct) + private async Task DropRowAsync( + QueueItemDto row, + string digest, + bool withDedup, + CancellationToken ct) { if (withDedup) { @@ -72,7 +82,10 @@ public sealed partial class PipelineWorkerService // row: Строка очереди со старым msg_at. // run: Снимок настроек воркера (срок до автоархива уже прочитан). // ct: Токен отмены. - private async Task DropStaleAsync(QueueItemDto row, WorkerRunSettings run, CancellationToken ct) + private async Task DropStaleAsync( + QueueItemDto row, + WorkerRunSettings run, + CancellationToken ct) { await RejectRowAsync(row, SourceStale, StageStale, string.Format(StaleReasonFormat, run.ArchiveAfterDays), string.Empty, ct); await DropRowAsync(row, DedupHasher.Hash(row.Text), withDedup: true, ct); diff --git a/src/core/Deal.Modules.Settings/Application/Abstractions/IGlobalSettingsStore.cs b/src/core/Deal.Modules.Settings/Application/Abstractions/IGlobalSettingsStore.cs index d3a016b..b97ef17 100644 --- a/src/core/Deal.Modules.Settings/Application/Abstractions/IGlobalSettingsStore.cs +++ b/src/core/Deal.Modules.Settings/Application/Abstractions/IGlobalSettingsStore.cs @@ -29,5 +29,8 @@ public interface IGlobalSettingsStore /// Ключ глобальной настройки. /// Значение, сериализованное в JSON. /// Токен отмены. - public Task SetAsync(string key, string valueJson, CancellationToken ct); + public Task SetAsync( + string key, + string valueJson, + CancellationToken ct); } diff --git a/src/core/Deal.Modules.Settings/Application/Abstractions/ISettingsStore.cs b/src/core/Deal.Modules.Settings/Application/Abstractions/ISettingsStore.cs index 783b49f..b056315 100644 --- a/src/core/Deal.Modules.Settings/Application/Abstractions/ISettingsStore.cs +++ b/src/core/Deal.Modules.Settings/Application/Abstractions/ISettingsStore.cs @@ -38,7 +38,10 @@ public interface ISettingsStore /// Ключ настройки. /// Значение, сериализованное в JSON. /// Токен отмены. - public Task SetAsync(string key, string valueJson, CancellationToken ct); + public Task SetAsync( + string key, + string valueJson, + CancellationToken ct); /// /// Удаляет значение по ключу (нет строки — no-op). diff --git a/src/core/Deal.Modules.Settings/Application/Models/IncomingRules.cs b/src/core/Deal.Modules.Settings/Application/Models/IncomingRules.cs index bcbf22e..0d889bc 100644 --- a/src/core/Deal.Modules.Settings/Application/Models/IncomingRules.cs +++ b/src/core/Deal.Modules.Settings/Application/Models/IncomingRules.cs @@ -216,7 +216,10 @@ public sealed class IncomingRules(ISettingsStore store) // reason: Причина для UI. // Kw: Фраза/маркер, по которому текст отсечён (для мониторинга); иначе "" (как python). // Возвращает: Результат этапа 1: pass=false. - private static IncomingRulesResult Fail(string kind, string reason, string Kw) + private static IncomingRulesResult Fail( + string kind, + string reason, + string Kw) { return new IncomingRulesResult(Pass: false, Reason: reason, Stage, kind, Kw); } diff --git a/src/core/Deal.Modules.Settings/Application/Services/PromptFiller.cs b/src/core/Deal.Modules.Settings/Application/Services/PromptFiller.cs index 667af18..31abb38 100644 --- a/src/core/Deal.Modules.Settings/Application/Services/PromptFiller.cs +++ b/src/core/Deal.Modules.Settings/Application/Services/PromptFiller.cs @@ -38,7 +38,10 @@ public static class PromptFiller /// Описание сферы; пустое или из пробелов → . /// Слова-маркеры заявок; пробельные элементы отбрасываются, склейка через «, », срез — первые . /// Текст с подставленными значениями. - public static string Fill(string? prompt, string? domain, IReadOnlyCollection? keywords) + public static string Fill( + string? prompt, + string? domain, + IReadOnlyCollection? keywords) { if (string.IsNullOrEmpty(prompt)) { diff --git a/src/core/Deal.Modules.Settings/Application/Services/SettingsService.PatchMyPrompts.cs b/src/core/Deal.Modules.Settings/Application/Services/SettingsService.PatchMyPrompts.cs index a386f7b..0e076cc 100644 --- a/src/core/Deal.Modules.Settings/Application/Services/SettingsService.PatchMyPrompts.cs +++ b/src/core/Deal.Modules.Settings/Application/Services/SettingsService.PatchMyPrompts.cs @@ -51,7 +51,10 @@ public sealed partial class SettingsService // field: Имя поля (name/prompt/description). // maxLength: Максимальная длина после trim. // Возвращает: Значение поля (может быть пустым). - private static string ReadTrimmedField(JsonElement item, string field, int maxLength) + private static string ReadTrimmedField( + JsonElement item, + string field, + int maxLength) { if (!TryReadFieldText(item, field, out string text)) { diff --git a/src/core/Deal.Modules.Settings/Application/Services/SettingsService.PatchScalarKeys.cs b/src/core/Deal.Modules.Settings/Application/Services/SettingsService.PatchScalarKeys.cs index 6d65a8a..07b5ad4 100644 --- a/src/core/Deal.Modules.Settings/Application/Services/SettingsService.PatchScalarKeys.cs +++ b/src/core/Deal.Modules.Settings/Application/Services/SettingsService.PatchScalarKeys.cs @@ -13,9 +13,7 @@ public sealed partial class SettingsService // body: Тело PATCH (не мутируется). // overrides: Сохранённые переопределения (для «другого конца» интервала). // Возвращает: Ключ → уже склампированное значение; ключи в результате пропускаются общим циклом. - private static Dictionary PrepareDelayClamps( - Dictionary body, - Dictionary overrides) + private static Dictionary PrepareDelayClamps(Dictionary body, Dictionary overrides) { var result = new Dictionary(StringComparer.Ordinal); (int min, int max) = IntClampRanges[SettingsKeys.DiscJoinDelayMin]; @@ -82,7 +80,10 @@ public sealed partial class SettingsService // key: Имя ключа. // value: JSON-значение из тела PATCH. // writes: Накопитель записей (key → valueJson). - private static void ApplyIntKey(string key, JsonElement value, Dictionary writes) + private static void ApplyIntKey( + string key, + JsonElement value, + Dictionary writes) { if (!TryReadInt(value, out int number)) { @@ -101,7 +102,10 @@ public sealed partial class SettingsService // key: Имя ключа. // value: JSON-значение из тела PATCH. // writes: Накопитель записей (key → valueJson). - private static void ApplyStringKey(string key, JsonElement value, Dictionary writes) + private static void ApplyStringKey( + string key, + JsonElement value, + Dictionary writes) { if (!TryReadText(value, out string? text) || text is null) { diff --git a/src/core/Deal.Modules.Settings/Application/Services/SettingsService.ReadMerge.cs b/src/core/Deal.Modules.Settings/Application/Services/SettingsService.ReadMerge.cs index f9e6a85..8be54cf 100644 --- a/src/core/Deal.Modules.Settings/Application/Services/SettingsService.ReadMerge.cs +++ b/src/core/Deal.Modules.Settings/Application/Services/SettingsService.ReadMerge.cs @@ -119,7 +119,10 @@ public sealed partial class SettingsService // name: Имя поля. // text: Значение поля. // Возвращает: True — поле присутствует и скалярно. - private static bool TryReadFieldText(JsonElement obj, string name, out string text) + private static bool TryReadFieldText( + JsonElement obj, + string name, + out string text) { if (obj.TryGetProperty(name, out JsonElement element) && TryReadText(element, out string? value) && value is not null) { @@ -136,10 +139,16 @@ public sealed partial class SettingsService // min: Нижняя граница. // max: Верхняя граница. // Возвращает: Значение в диапазоне [min..max]. - private static int Clamp(int value, int min, int max) => Math.Max(min, Math.Min(max, value)); + private static int Clamp( + int value, + int min, + int max) => Math.Max(min, Math.Min(max, value)); // Объединяет дефолт и переопределение целочисленного ключа. - private static int MergeInt(Dictionary overrides, string key, int defaultValue) + private static int MergeInt( + Dictionary overrides, + string key, + int defaultValue) { return overrides.TryGetValue(key, out JsonElement element) && TryReadInt(element, out int value) ? value @@ -147,7 +156,10 @@ public sealed partial class SettingsService } // Объединяет дефолт и переопределение булева ключа. - private static bool MergeBool(Dictionary overrides, string key, bool defaultValue) + private static bool MergeBool( + Dictionary overrides, + string key, + bool defaultValue) { return overrides.TryGetValue(key, out JsonElement element) && TryReadBool(element, out bool value) ? value @@ -155,7 +167,10 @@ public sealed partial class SettingsService } // Объединяет дефолт и переопределение строкового ключа. - private static string MergeString(Dictionary overrides, string key, string defaultValue) + private static string MergeString( + Dictionary overrides, + string key, + string defaultValue) { return overrides.TryGetValue(key, out JsonElement element) && TryReadText(element, out string? value) @@ -208,9 +223,7 @@ public sealed partial class SettingsService // defaults: Дефолтные конфиги на всех провайдеров каталога. // overrides: Сохранённые переопределения. // Возвращает: Эффективные конфиги: провайдеры дефолтов + перекрытия из хранилища (неизвестные пропускаются). - private static IReadOnlyDictionary MergeAiConfigs( - IReadOnlyDictionary defaults, - Dictionary overrides) + private static IReadOnlyDictionary MergeAiConfigs(IReadOnlyDictionary defaults, Dictionary overrides) { var merged = defaults.ToDictionary(pair => pair.Key, pair => pair.Value, StringComparer.Ordinal); diff --git a/src/core/Deal.Modules.Telegram/Application/DialogsService.cs b/src/core/Deal.Modules.Telegram/Application/DialogsService.cs index 3a89816..1d412da 100644 --- a/src/core/Deal.Modules.Telegram/Application/DialogsService.cs +++ b/src/core/Deal.Modules.Telegram/Application/DialogsService.cs @@ -82,7 +82,10 @@ public sealed class DialogsService( /// True — мониторить, false — выключить. /// Токен отмены. /// Результат: зеркальное enabled + признак «нужен первый фоновый Backfill». - public async Task SetMonitorAsync(string dialogId, bool enabled, CancellationToken ct) + public async Task SetMonitorAsync( + string dialogId, + bool enabled, + CancellationToken ct) { bool? backfilled = await store.GetBackfilledAsync(dialogId, ct).ConfigureAwait(false); if (backfilled is null) @@ -141,7 +144,12 @@ public sealed class DialogsService( /// Токен отмены. /// Завершается после записи каталога и команды зеркалу. public async Task AddDiscoveredMonitoredAsync( - string dialogId, string name, string handle, string kind, string hue, CancellationToken ct) + string dialogId, + string name, + string handle, + string kind, + string hue, + CancellationToken ct) { await store.UpsertDiscoveredMonitoredAsync( dialogId, @@ -215,7 +223,10 @@ public sealed class DialogsService( /// True — перечитать, даже если диалог уже разобран (кнопка «Перечитать»). /// Токен отмены. /// Сколько сообщений отправлено в ядро потоком PushMessage (0 — выхода нет/сообщений нет). - public async Task BackfillOneAsync(string dialogId, bool force, CancellationToken ct) + public async Task BackfillOneAsync( + string dialogId, + bool force, + CancellationToken ct) { bool? backfilled = await store.GetBackfilledAsync(dialogId, ct).ConfigureAwait(false); if (backfilled is null || (!force && backfilled.Value)) @@ -243,7 +254,10 @@ public sealed class DialogsService( /// Сколько последних сообщений (1..50; api-map /dialogs/preview limit 1..50). /// Токен отмены. /// Сообщения от новых к старым в форме §4.11 ({id, text, time, lead}). - public async Task> PreviewAsync(string dialogId, int limit, CancellationToken ct) + public async Task> PreviewAsync( + string dialogId, + int limit, + CancellationToken ct) { IReadOnlyList fresh; try @@ -293,7 +307,11 @@ public sealed class DialogsService( /// Токен отмены. /// Завершается после обновления каталога/TgMessages (дубли превью — no-op). public async Task SavePreviewAsync( - string dialogId, long? msgId, string text, DateTimeOffset? msgAt, CancellationToken ct) + string dialogId, + long? msgId, + string text, + DateTimeOffset? msgAt, + CancellationToken ct) { string trimmed = text.Trim(); if (trimmed.Length == 0) diff --git a/src/core/Deal.Modules.Telegram/Application/ITelegramStore.cs b/src/core/Deal.Modules.Telegram/Application/ITelegramStore.cs index 141da0c..e57da5b 100644 --- a/src/core/Deal.Modules.Telegram/Application/ITelegramStore.cs +++ b/src/core/Deal.Modules.Telegram/Application/ITelegramStore.cs @@ -28,7 +28,9 @@ public interface ITelegramStore /// Токен отмены. /// Число применённых записей каталога (= entries.Count; 0 — пустой каталог). public Task SyncFromTelegramAsync( - IReadOnlyCollection entries, bool autoMonitorNew, CancellationToken ct); + IReadOnlyCollection entries, + bool autoMonitorNew, + CancellationToken ct); /// /// Список диалогов каталога для GET /api/tg/dialogs (list_dialogs L521–534, ORDER BY monitor DESC, name). @@ -51,7 +53,10 @@ public interface ITelegramStore /// True — мониторить, false — выключить. /// Токен отмены. /// Завершается после обновления строки (нет строки — no-op). - public Task SetMonitorAsync(string dialogId, bool enabled, CancellationToken ct); + public Task SetMonitorAsync( + string dialogId, + bool enabled, + CancellationToken ct); /// /// Включает/выключает мониторинг всех диалогов каталога (set_monitor_all L554–563). @@ -98,7 +103,12 @@ public interface ITelegramStore /// Токен отмены. /// Завершается после записи. public Task UpsertDiscoveredMonitoredAsync( - string dialogId, string name, string handle, string kind, string hue, CancellationToken ct); + string dialogId, + string name, + string handle, + string kind, + string hue, + CancellationToken ct); /// /// Пишет строку превью сообщения в TgMessages (INSERT OR IGNORE, python L602–605). @@ -112,7 +122,11 @@ public interface ITelegramStore /// Токен отмены. /// Завершается после вставки (дубль — no-op). public Task SavePreviewAsync( - string messageId, string dialogId, string text, DateTimeOffset msgAt, CancellationToken ct); + string messageId, + string dialogId, + string text, + DateTimeOffset msgAt, + CancellationToken ct); /// /// Обновляет «последнее сообщение» диалога в каталоге (_on_message L270–273). @@ -123,7 +137,11 @@ public interface ITelegramStore /// Момент приёма сообщения (UTC). /// Токен отмены. /// Завершается после обновления строки (нет строки — no-op). - public Task TouchDialogLastAsync(string dialogId, string text, DateTimeOffset at, CancellationToken ct); + public Task TouchDialogLastAsync( + string dialogId, + string text, + DateTimeOffset at, + CancellationToken ct); /// /// Фолбэк превью из БД (dialog_messages L614–619): SELECT * FROM messages WHERE dialog_id ORDER BY msg_at DESC LIMIT. @@ -132,5 +150,8 @@ public interface ITelegramStore /// Сколько последних сообщений (≤50). /// Токен отмены. /// Сообщения TgMessages диалога от новых к старым (lead — по LeadId). - public Task> ListMessagesAsync(string dialogId, int limit, CancellationToken ct); + public Task> ListMessagesAsync( + string dialogId, + int limit, + CancellationToken ct); } diff --git a/src/core/Deal.Modules.Tenants/Application/Abstractions/IAuthStore.cs b/src/core/Deal.Modules.Tenants/Application/Abstractions/IAuthStore.cs index 146dccb..4547844 100644 --- a/src/core/Deal.Modules.Tenants/Application/Abstractions/IAuthStore.cs +++ b/src/core/Deal.Modules.Tenants/Application/Abstractions/IAuthStore.cs @@ -77,7 +77,10 @@ public interface IAuthStore /// Идентификатор пользователя. /// Новая encoded-строка хэша. /// Токен отмены. - public Task UpdatePasswordHashAsync(Guid userId, string passwordHash, CancellationToken ct); + public Task UpdatePasswordHashAsync( + Guid userId, + string passwordHash, + CancellationToken ct); /// /// Удаляет все сессии со сроком жизни не позднее текущего момента. diff --git a/src/core/Deal.Modules.Tenants/Application/Abstractions/IInviteStore.cs b/src/core/Deal.Modules.Tenants/Application/Abstractions/IInviteStore.cs index 19fc595..50543d1 100644 --- a/src/core/Deal.Modules.Tenants/Application/Abstractions/IInviteStore.cs +++ b/src/core/Deal.Modules.Tenants/Application/Abstractions/IInviteStore.cs @@ -45,7 +45,11 @@ public interface IInviteStore /// Момент активации при статусе activated; иначе null. /// Токен отмены. /// true, если приглашение с таким кодом найдено и статус изменён; false — кода нет. - public Task UpdateStatusAsync(string code, string status, DateTimeOffset? activatedAt, CancellationToken ct); + public Task UpdateStatusAsync( + string code, + string status, + DateTimeOffset? activatedAt, + CancellationToken ct); /// /// Атомарно активирует приглашение (CAS, Task 6): переход pending → activated выполняется только если @@ -57,7 +61,10 @@ public interface IInviteStore /// Момент активации (UTC) для колонки ActivatedAt. /// Токен отмены. /// true, если строка была в статусе pending и переведена в activated; false — кода нет или статус уже иной. - public Task TryActivateAsync(string code, DateTimeOffset activatedAt, CancellationToken ct); + public Task TryActivateAsync( + string code, + DateTimeOffset activatedAt, + CancellationToken ct); /// /// Активное (pending) приглашение на email — антидубль создания (частичная уникальность, Ruling 2). diff --git a/src/core/Deal.Modules.Tenants/Application/Abstractions/IRateLimitCounterStore.cs b/src/core/Deal.Modules.Tenants/Application/Abstractions/IRateLimitCounterStore.cs index d768d4c..b091cb6 100644 --- a/src/core/Deal.Modules.Tenants/Application/Abstractions/IRateLimitCounterStore.cs +++ b/src/core/Deal.Modules.Tenants/Application/Abstractions/IRateLimitCounterStore.cs @@ -30,7 +30,11 @@ public interface IRateLimitCounterStore /// Токен отмены. /// Значение счётчика в текущем окне после операции. public Task IncrementAsync( - string key, DateTimeOffset windowStart, DateTimeOffset windowEnd, int amount, CancellationToken ct); + string key, + DateTimeOffset windowStart, + DateTimeOffset windowEnd, + int amount, + CancellationToken ct); /// /// Читает счётчик текущего окна (0 — строки нет или окно сменилось). @@ -39,7 +43,10 @@ public interface IRateLimitCounterStore /// Начало текущего фиксированного окна (UTC). /// Токен отмены. /// Значение счётчика в окне (0, если окно иное/строки нет). - public Task GetCountAsync(string key, DateTimeOffset windowStart, CancellationToken ct); + public Task GetCountAsync( + string key, + DateTimeOffset windowStart, + CancellationToken ct); /// /// Удаляет счётчик ключа (успешный вход сбрасывает попытки, Ruling 5). diff --git a/src/core/Deal.Modules.Tenants/Application/Abstractions/ITenantLimitStore.cs b/src/core/Deal.Modules.Tenants/Application/Abstractions/ITenantLimitStore.cs index a93016c..380caac 100644 --- a/src/core/Deal.Modules.Tenants/Application/Abstractions/ITenantLimitStore.cs +++ b/src/core/Deal.Modules.Tenants/Application/Abstractions/ITenantLimitStore.cs @@ -31,7 +31,10 @@ public interface ITenantLimitStore /// Токен отмены. /// Дефолт-параметры создаваемой строки; null — дефолт адаптера (конфигурация/env). /// Строка лимита (созданная или существующая). - public Task GetOrCreateAsync(Guid tenantId, CancellationToken ct, TokenLimitDefaults? defaults = null); + public Task GetOrCreateAsync( + Guid tenantId, + CancellationToken ct, + TokenLimitDefaults? defaults = null); /// /// Текущее состояние бюджета тенанта (ленивый reset периода + статус тенанта и признак Allowed @@ -50,7 +53,10 @@ public interface ITenantLimitStore /// Списываемые токены (usage.Total ответа ai-service; ≤0 — no-op). /// Токен отмены. /// Состояние бюджета после списания (источник для гейта/сводки usage). - public Task AddUsageAsync(Guid tenantId, long tokens, CancellationToken ct); + public Task AddUsageAsync( + Guid tenantId, + long tokens, + CancellationToken ct); /// /// Меняет бюджет/период тенанта (операторский PATCH, Task 10) и сбрасывает флаги Warned80/ @@ -61,7 +67,11 @@ public interface ITenantLimitStore /// Новый тип периода (). /// Токен отмены. /// Состояние бюджета после изменения. - public Task UpdateBudgetAsync(Guid tenantId, long budgetTokens, string period, CancellationToken ct); + public Task UpdateBudgetAsync( + Guid tenantId, + long budgetTokens, + string period, + CancellationToken ct); /// /// Атомарно выставляет флаг Warned80, если порог 80% достигнут и флаг ещё не стоял (Task 9: diff --git a/src/core/Deal.Modules.Tenants/Application/Abstractions/ITenantRepository.cs b/src/core/Deal.Modules.Tenants/Application/Abstractions/ITenantRepository.cs index 2c0b2da..8882ec4 100644 --- a/src/core/Deal.Modules.Tenants/Application/Abstractions/ITenantRepository.cs +++ b/src/core/Deal.Modules.Tenants/Application/Abstractions/ITenantRepository.cs @@ -39,5 +39,8 @@ public interface ITenantRepository /// Новый статус — константа TenantStatuses. /// Токен отмены. /// true, если тенант существовал и статус обновлён; false — записи нет (404 на HTTP-слое). - public Task UpdateStatusAsync(Guid id, string status, CancellationToken ct); + public Task UpdateStatusAsync( + Guid id, + string status, + CancellationToken ct); } diff --git a/src/core/Deal.Modules.Tenants/Application/Services/AnalyticsService.cs b/src/core/Deal.Modules.Tenants/Application/Services/AnalyticsService.cs index 97e8e66..37c4891 100644 --- a/src/core/Deal.Modules.Tenants/Application/Services/AnalyticsService.cs +++ b/src/core/Deal.Modules.Tenants/Application/Services/AnalyticsService.cs @@ -35,7 +35,10 @@ public sealed class AnalyticsService( /// Конец периода (включительно; null — без границы). /// Токен отмены. /// Сводка аналитики. - public async Task OverviewAsync(DateTimeOffset? from, DateTimeOffset? to, CancellationToken ct) + public async Task OverviewAsync( + DateTimeOffset? from, + DateTimeOffset? to, + CancellationToken ct) { IReadOnlyList allTenants = await tenants.ListAsync(ct); int active = allTenants.Count(tenant => tenant.Status == TenantStatuses.Active); @@ -82,7 +85,11 @@ public sealed class AnalyticsService( /// Токен отмены. /// Строки агрегатов и итог. public async Task TokensAsync( - string groupBy, Guid? tenantId, DateTimeOffset? from, DateTimeOffset? to, CancellationToken ct) + string groupBy, + Guid? tenantId, + DateTimeOffset? from, + DateTimeOffset? to, + CancellationToken ct) { IReadOnlyList items = await tokenUsage.AggregateAsync( new TokenUsageEventQueryDto(tenantId, null, null, null, from, to, groupBy), ct); @@ -146,7 +153,10 @@ public sealed class AnalyticsService( // ct: Токен отмены. // Возвращает: Сумма по типам событий. private async Task CountEventTypesAsync( - DateTimeOffset? from, DateTimeOffset? to, string[] eventTypes, CancellationToken ct) + DateTimeOffset? from, + DateTimeOffset? to, + string[] eventTypes, + CancellationToken ct) { int total = 0; foreach (string eventType in eventTypes) diff --git a/src/core/Deal.Modules.Tenants/Application/Services/AuthService.cs b/src/core/Deal.Modules.Tenants/Application/Services/AuthService.cs index 0c4c283..29e0aca 100644 --- a/src/core/Deal.Modules.Tenants/Application/Services/AuthService.cs +++ b/src/core/Deal.Modules.Tenants/Application/Services/AuthService.cs @@ -54,7 +54,10 @@ public sealed class AuthService( /// заблокированный вход приостановленного тенанта (, /// UserId/TenantId заполнены) от «неверные учётные данные» (Error null, UserId/TenantId пусты). /// - public async Task LoginAsync(string login, string password, CancellationToken ct) + public async Task LoginAsync( + string login, + string password, + CancellationToken ct) { var normalizedLogin = NormalizeLogin(login); if (string.IsNullOrEmpty(normalizedLogin) || string.IsNullOrEmpty(password)) @@ -293,7 +296,11 @@ public sealed class AuthService( // login: Логин пользователя. // tenantId: Тенант пользователя. // Возвращает: ImpersonationResultDto с Ok=true. - private ImpersonationResultDto Success(string rawToken, Guid userId, string login, Guid tenantId) => + private ImpersonationResultDto Success( + string rawToken, + Guid userId, + string login, + Guid tenantId) => new( Ok: true, Error: null, diff --git a/src/core/Deal.Modules.Tenants/Application/Services/InvitesService.cs b/src/core/Deal.Modules.Tenants/Application/Services/InvitesService.cs index 285ab46..379f94a 100644 --- a/src/core/Deal.Modules.Tenants/Application/Services/InvitesService.cs +++ b/src/core/Deal.Modules.Tenants/Application/Services/InvitesService.cs @@ -44,7 +44,10 @@ public sealed partial class InvitesService(IInviteStore inviteStore) /// Токен отмены. /// Ok=true + созданное приглашение, либо код ошибки (текст — HTTP-слой). public async Task CreateInviteAsync( - Guid operatorId, string? email, Guid? tenantId, CancellationToken ct) + Guid operatorId, + string? email, + Guid? tenantId, + CancellationToken ct) { string normalizedEmail = NormalizeEmail(email); if (!IsValidEmail(normalizedEmail)) diff --git a/src/core/Deal.Modules.Tenants/Application/Services/OperatorAuthService.cs b/src/core/Deal.Modules.Tenants/Application/Services/OperatorAuthService.cs index 1189c50..24200ae 100644 --- a/src/core/Deal.Modules.Tenants/Application/Services/OperatorAuthService.cs +++ b/src/core/Deal.Modules.Tenants/Application/Services/OperatorAuthService.cs @@ -32,7 +32,10 @@ public sealed class OperatorAuthService(IOperatorAuthStore operatorAuthStore, IP /// Пароль в открытом виде. /// Токен отмены. /// При успехе — Login и Token; иначе оба null (текст «Неверный логин или пароль оператора» фиксирует endpoint). - public async Task LoginAsync(string login, string password, CancellationToken ct) + public async Task LoginAsync( + string login, + string password, + CancellationToken ct) { var normalizedLogin = NormalizeLogin(login); if (string.IsNullOrEmpty(normalizedLogin) || string.IsNullOrEmpty(password)) diff --git a/src/core/Deal.Modules.Tenants/Application/Services/SuspiciousActivityService.cs b/src/core/Deal.Modules.Tenants/Application/Services/SuspiciousActivityService.cs index 7e8516f..e6c7907 100644 --- a/src/core/Deal.Modules.Tenants/Application/Services/SuspiciousActivityService.cs +++ b/src/core/Deal.Modules.Tenants/Application/Services/SuspiciousActivityService.cs @@ -122,7 +122,10 @@ public sealed class SuspiciousActivityService /// Конец окна (включительно); null — «сейчас». /// Токен отмены. /// Сводка: окно, число разобранных записей, признак усечения и список находок. - public async Task AnalyzeAsync(DateTimeOffset? from, DateTimeOffset? to, CancellationToken ct) + public async Task AnalyzeAsync( + DateTimeOffset? from, + DateTimeOffset? to, + CancellationToken ct) { DateTimeOffset windowTo = to ?? _clock(); DateTimeOffset windowFrom = from ?? windowTo.AddHours(-DefaultWindowHours); diff --git a/src/core/Deal.Modules.Tenants/Application/Services/TenantAdminService.cs b/src/core/Deal.Modules.Tenants/Application/Services/TenantAdminService.cs index 8594989..1e947dc 100644 --- a/src/core/Deal.Modules.Tenants/Application/Services/TenantAdminService.cs +++ b/src/core/Deal.Modules.Tenants/Application/Services/TenantAdminService.cs @@ -41,7 +41,10 @@ public sealed class TenantAdminService( /// Ok=true — тенант создан (Tenant); при email дополнительно OwnerUserId/OwnerLogin/InitialPassword. /// Ok=false — код ошибки (имя пусто, email невалиден/уже зарегистрирован); текст — HTTP-слой. /// - public async Task CreateAsync(string? name, string? email, CancellationToken ct) + public async Task CreateAsync( + string? name, + string? email, + CancellationToken ct) { string tenantName = name?.Trim() ?? string.Empty; if (string.IsNullOrEmpty(tenantName)) @@ -142,7 +145,10 @@ public sealed class TenantAdminService( /// Новый статус — константа TenantStatuses. /// Токен отмены. /// Результат: тенант не найден (404) или применение с признаком реального изменения (Changed). - public async Task ChangeStatusAsync(Guid id, string status, CancellationToken ct) + public async Task ChangeStatusAsync( + Guid id, + string status, + CancellationToken ct) { var tenant = await tenantRepository.FindByIdAsync(id, ct); if (tenant is null) diff --git a/src/core/Deal.Modules.Tenants/Application/Services/TenantService.cs b/src/core/Deal.Modules.Tenants/Application/Services/TenantService.cs index 145ac7d..92c3de9 100644 --- a/src/core/Deal.Modules.Tenants/Application/Services/TenantService.cs +++ b/src/core/Deal.Modules.Tenants/Application/Services/TenantService.cs @@ -28,7 +28,10 @@ public sealed class TenantService(ITenantRepository tenantRepository, ITenantPro /// Идентификатор тенанта (определяет имя схемы). /// Токен отмены. /// Идентификатор созданного тенанта (он же имя схемы tenant_<id>). - public async Task CreateTenantAsync(string name, Guid id, CancellationToken ct) + public async Task CreateTenantAsync( + string name, + Guid id, + CancellationToken ct) { await tenantRepository.CreateAsync( new TenantRecordDto( diff --git a/src/core/Deal.Modules.Tenants/Application/Services/TokenBudgetService.cs b/src/core/Deal.Modules.Tenants/Application/Services/TokenBudgetService.cs index 0c960dc..713fcf2 100644 --- a/src/core/Deal.Modules.Tenants/Application/Services/TokenBudgetService.cs +++ b/src/core/Deal.Modules.Tenants/Application/Services/TokenBudgetService.cs @@ -25,7 +25,10 @@ public sealed class TokenBudgetService /// Тип периода (; любое иное значение трактуется как месяц). /// Текущий момент (UTC). /// True — пора обнулить счётчик и сдвинуть PeriodStart на now. - public bool IsPeriodExpired(DateTimeOffset periodStart, string period, DateTimeOffset now) + public bool IsPeriodExpired( + DateTimeOffset periodStart, + string period, + DateTimeOffset now) { DateTimeOffset periodEnd = period == TenantLimitPeriods.Day ? periodStart.AddDays(1) diff --git a/src/core/tests/Deal.Tests.Unit/AdminTickOrchestratorTests.cs b/src/core/tests/Deal.Tests.Unit/AdminTickOrchestratorTests.cs index cd86fa8..6109054 100644 --- a/src/core/tests/Deal.Tests.Unit/AdminTickOrchestratorTests.cs +++ b/src/core/tests/Deal.Tests.Unit/AdminTickOrchestratorTests.cs @@ -275,7 +275,11 @@ public sealed class AdminTickOrchestratorTests private static long NowMs() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); // Карточка hold с напоминанием (как строка Cards после маппинга адаптера). - private static CardDto HoldCard(string id, string title, long reminderAtMs, string stage = "hold") + private static CardDto HoldCard( + string id, + string title, + long reminderAtMs, + string stage = "hold") { return new CardDto { @@ -292,7 +296,10 @@ public sealed class AdminTickOrchestratorTests private sealed class ThrowingQueueReadPipelineStore : FakePipelineStore { /// - public override Task> ListAsync(string? status, int limit, CancellationToken ct) + public override Task> ListAsync( + string? status, + int limit, + CancellationToken ct) { throw new InvalidOperationException("Тестовый сбой чтения очереди (ListAsync)."); } diff --git a/src/core/tests/Deal.Tests.Unit/AiClassifyContextBuilderTests.cs b/src/core/tests/Deal.Tests.Unit/AiClassifyContextBuilderTests.cs index d03fc3c..64c5bce 100644 --- a/src/core/tests/Deal.Tests.Unit/AiClassifyContextBuilderTests.cs +++ b/src/core/tests/Deal.Tests.Unit/AiClassifyContextBuilderTests.cs @@ -182,7 +182,11 @@ public sealed class AiClassifyContextBuilderTests // cardId: Id карточки (l_…). // board: Колонка-назначение (доска). // text: Исходный текст карточки (source_msg). - private static void SeedCardWithMove(FakeKanjStore kanj, string cardId, string board, string text) + private static void SeedCardWithMove( + FakeKanjStore kanj, + string cardId, + string board, + string text) { kanj.SeedCard(new CardDto { Id = cardId, Col = board, SourceMsg = text }); kanj.AddMoveAsync(new CardMoveDto("lm_" + cardId, cardId, "move", "inbox", board), CancellationToken.None).GetAwaiter().GetResult(); diff --git a/src/core/tests/Deal.Tests.Unit/AuditLogStoreTests.cs b/src/core/tests/Deal.Tests.Unit/AuditLogStoreTests.cs index 8f3972d..930dba0 100644 --- a/src/core/tests/Deal.Tests.Unit/AuditLogStoreTests.cs +++ b/src/core/tests/Deal.Tests.Unit/AuditLogStoreTests.cs @@ -142,7 +142,11 @@ public sealed class AuditLogStoreTests } // Запись аудита с деталями по умолчанию. - private static AuditRecordDto Record(string eventType, string actorType, Guid? tenantId, DateTimeOffset at) => + private static AuditRecordDto Record( + string eventType, + string actorType, + Guid? tenantId, + DateTimeOffset at) => new(eventType, actorType, ActorId: null, tenantId, Ip: "127.0.0.1", DetailJson: null, at); private static AuditQueryDto EmptyFilter(int limit) => diff --git a/src/core/tests/Deal.Tests.Unit/AuthStoreTests.cs b/src/core/tests/Deal.Tests.Unit/AuthStoreTests.cs index 903c96d..491ea1f 100644 --- a/src/core/tests/Deal.Tests.Unit/AuthStoreTests.cs +++ b/src/core/tests/Deal.Tests.Unit/AuthStoreTests.cs @@ -69,7 +69,10 @@ public sealed class AuthStoreTests } // Создаёт пользователя тенанта (хэш пароля — заглушка, для чтения не нужен). - private static async Task CreateUserAsync(AuthStore store, Guid tenantId, string login) + private static async Task CreateUserAsync( + AuthStore store, + Guid tenantId, + string login) { await store.CreateUserAsync( new StoredUserDto(Guid.NewGuid(), login, tenantId, "active", "fake-argon2-encoded-hash"), diff --git a/src/core/tests/Deal.Tests.Unit/CardReclassifierTests.cs b/src/core/tests/Deal.Tests.Unit/CardReclassifierTests.cs index d26a00b..04b8bf4 100644 --- a/src/core/tests/Deal.Tests.Unit/CardReclassifierTests.cs +++ b/src/core/tests/Deal.Tests.Unit/CardReclassifierTests.cs @@ -65,7 +65,11 @@ public sealed class CardReclassifierTests // contact: Старый контакт карточки. // receivedAtMs: Время получения (для порядка батча). // Возвращает: Карточка как если бы была сохранена в БД. - private static CardDto Card(string id, string sourceMsg, string contact = "", long receivedAtMs = 1_700_000_000_000) => new() + private static CardDto Card( + string id, + string sourceMsg, + string contact = "", + long receivedAtMs = 1_700_000_000_000) => new() { Id = id, Col = "inbox", @@ -80,7 +84,11 @@ public sealed class CardReclassifierTests // isSpam: Вердикт «не заявка». // board: Назначенная колонка. // Возвращает: Разбор классификатора. - private static AiParsedCardDto Parsed(string title, bool isVacancy = false, bool isSpam = false, string? board = null) => new( + private static AiParsedCardDto Parsed( + string title, + bool isVacancy = false, + bool isSpam = false, + string? board = null) => new( Title: title, Company: null, Format: null, diff --git a/src/core/tests/Deal.Tests.Unit/CardsServiceRemindersTests.cs b/src/core/tests/Deal.Tests.Unit/CardsServiceRemindersTests.cs index 98fab26..b82bbf1 100644 --- a/src/core/tests/Deal.Tests.Unit/CardsServiceRemindersTests.cs +++ b/src/core/tests/Deal.Tests.Unit/CardsServiceRemindersTests.cs @@ -222,7 +222,11 @@ public sealed class CardsServiceRemindersTests private static long NowMs() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); // Карточка с полями по умолчанию (planned, CreatedAtMs=1, UpdatedAtMs=1). - private static CardDto Card(string id, string stage = "planned", string title = "", long updatedAtMs = 1) + private static CardDto Card( + string id, + string stage = "planned", + string title = "", + long updatedAtMs = 1) { return new CardDto { diff --git a/src/core/tests/Deal.Tests.Unit/CardsServiceTests.cs b/src/core/tests/Deal.Tests.Unit/CardsServiceTests.cs index 1cde99a..e12d2aa 100644 --- a/src/core/tests/Deal.Tests.Unit/CardsServiceTests.cs +++ b/src/core/tests/Deal.Tests.Unit/CardsServiceTests.cs @@ -797,7 +797,10 @@ public sealed class CardsServiceTests } // Ответ IMlClient.StatusAsync с заданными счётчиками (learning/ml/ai) для counts. - private static MlStatusResponseDto MlStatus(int learning, int ml, int ai) + private static MlStatusResponseDto MlStatus( + int learning, + int ml, + int ai) { var service = new MlServiceStatusDto( Ready: false, diff --git a/src/core/tests/Deal.Tests.Unit/ConversionRecomputerTests.cs b/src/core/tests/Deal.Tests.Unit/ConversionRecomputerTests.cs index e0de743..b6539bd 100644 --- a/src/core/tests/Deal.Tests.Unit/ConversionRecomputerTests.cs +++ b/src/core/tests/Deal.Tests.Unit/ConversionRecomputerTests.cs @@ -235,7 +235,12 @@ public sealed class ConversionRecomputerTests // from: Нижняя граница бюджета (null — «до X»). // to: Верхняя граница (null — «от X»/одна сумма). // cur: Валюта бюджета. - private static CardDto Card(string id, string col, double? from, double? to, string cur) => new() + private static CardDto Card( + string id, + string col, + double? from, + double? to, + string cur) => new() { Id = id, Col = col, @@ -262,7 +267,12 @@ public sealed class ConversionRecomputerTests // convFrom: Ожидаемая нижняя граница. // convTo: Ожидаемая верхняя граница. // convCur: Ожидаемая валюта конверсии. - private static void AssertConverted(FakeKanjStore store, string cardId, double convFrom, double convTo, string convCur) + private static void AssertConverted( + FakeKanjStore store, + string cardId, + double convFrom, + double convTo, + string convCur) { CardBudgetDto converted = ConvertedOf(store, cardId)!; Assert.NotNull(converted); diff --git a/src/core/tests/Deal.Tests.Unit/DataRetentionSchedulerTests.cs b/src/core/tests/Deal.Tests.Unit/DataRetentionSchedulerTests.cs index 429b5b2..9d14574 100644 --- a/src/core/tests/Deal.Tests.Unit/DataRetentionSchedulerTests.cs +++ b/src/core/tests/Deal.Tests.Unit/DataRetentionSchedulerTests.cs @@ -95,7 +95,9 @@ public sealed class DataRetentionSchedulerTests // counters: Фейк-хранилище счётчиков (окна посеяны сценарием). // Возвращает: Провайдер с сервисами цикла. private static ServiceProvider BuildProvider( - FakeAuditLogStore audit, FakeTenantLimitStore limits, FakeRateLimitCounterStore counters) + FakeAuditLogStore audit, + FakeTenantLimitStore limits, + FakeRateLimitCounterStore counters) { var services = new ServiceCollection(); services.AddScoped(_ => audit); diff --git a/src/core/tests/Deal.Tests.Unit/DialogsServiceTests.cs b/src/core/tests/Deal.Tests.Unit/DialogsServiceTests.cs index 1a07925..c88508e 100644 --- a/src/core/tests/Deal.Tests.Unit/DialogsServiceTests.cs +++ b/src/core/tests/Deal.Tests.Unit/DialogsServiceTests.cs @@ -522,12 +522,20 @@ public sealed class DialogsServiceTests // Строка каталога минимально (id/имя/kind + флаги/хвост каталога). private static FakeTelegramDialogRow Dialog( - string id, string name, string kind, bool Monitor, bool Backfilled, - string LastText = "", DateTimeOffset? LastAt = null) + string id, + string name, + string kind, + bool Monitor, + bool Backfilled, + string LastText = "", + DateTimeOffset? LastAt = null) => new(id, name, Handle: string.Empty, kind, Hue: "#666", Monitor, LastText, LastAt, Backfilled, UpdatedAt: DateTimeOffset.UtcNow); // Запись каталога (id/имя/kind; handle/hue по умолчанию). - private static TelegramDialogEntryDto Entry(string id, string name, string kind) + private static TelegramDialogEntryDto Entry( + string id, + string name, + string kind) => new(id, name, Handle: string.Empty, kind, Hue: "#666"); // Каталог из записей. diff --git a/src/core/tests/Deal.Tests.Unit/DiscoveryEvaluatorTests.cs b/src/core/tests/Deal.Tests.Unit/DiscoveryEvaluatorTests.cs index 2c27f37..ad04990 100644 --- a/src/core/tests/Deal.Tests.Unit/DiscoveryEvaluatorTests.cs +++ b/src/core/tests/Deal.Tests.Unit/DiscoveryEvaluatorTests.cs @@ -173,12 +173,14 @@ public sealed class DiscoveryEvaluatorTests } // Сообщение выборки (текст; для форума — с темой). - private static TelegramEvalMessageDto Msg(long id, string text, long? topic) + private static TelegramEvalMessageDto Msg( + long id, + string text, + long? topic) => new(id, text, DateMs: 1_700_000_000_000 + id, topic, TopicTitle: null); // Создаёт оценщик на фейках: флаги веток выставляются сценарием. - private static (DiscoveryEvaluator Evaluator, FakeSettingsStore Settings, FakeMlClient Ml, FakeAiTools Ai) Create( - bool aiEnabled, bool mlEnabled) + private static (DiscoveryEvaluator Evaluator, FakeSettingsStore Settings, FakeMlClient Ml, FakeAiTools Ai) Create(bool aiEnabled, bool mlEnabled) { var settings = new FakeSettingsStore(); settings.Preload(SettingsKeys.AiEnabled, aiEnabled ? "true" : "false"); diff --git a/src/core/tests/Deal.Tests.Unit/DiscoveryWorkerSchedulerTests.cs b/src/core/tests/Deal.Tests.Unit/DiscoveryWorkerSchedulerTests.cs index 8213b75..053e0c6 100644 --- a/src/core/tests/Deal.Tests.Unit/DiscoveryWorkerSchedulerTests.cs +++ b/src/core/tests/Deal.Tests.Unit/DiscoveryWorkerSchedulerTests.cs @@ -154,7 +154,12 @@ public sealed class DiscoveryWorkerSchedulerTests new(id, Name: "tenant", Status: "active", CreatedAt: DateTimeOffset.UtcNow); // Задача как строка DiscTasks (адаптер-форма). - private static DiscoveryTaskDto Task(string id, string[]? keywords = null, bool searchDone = true, int planJoins = 5, int joined = 0) + private static DiscoveryTaskDto Task( + string id, + string[]? keywords = null, + bool searchDone = true, + int planJoins = 5, + int joined = 0) { return new DiscoveryTaskDto( id, @@ -179,7 +184,11 @@ public sealed class DiscoveryWorkerSchedulerTests } // Запись результата глобального поиска (proto DialogEntry). - private static TelegramDialogEntryDto Entry(string id, string name, string handle, string kind) + private static TelegramDialogEntryDto Entry( + string id, + string name, + string handle, + string kind) => new(id, name, handle, kind, "#666"); // Логгер-коллектор: копит сообщения планировщика (диагностика в тестах цикла). @@ -198,7 +207,12 @@ public sealed class DiscoveryWorkerSchedulerTests public bool IsEnabled(LogLevel logLevel) => true; /// - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + public void Log( + LogLevel logLevel, + EventId eventId, + TState state, + Exception? exception, + Func formatter) { Messages.Add(formatter(state, exception)); } diff --git a/src/core/tests/Deal.Tests.Unit/DiscoveryWorkerServiceTests.cs b/src/core/tests/Deal.Tests.Unit/DiscoveryWorkerServiceTests.cs index cd1f241..48ffad5 100644 --- a/src/core/tests/Deal.Tests.Unit/DiscoveryWorkerServiceTests.cs +++ b/src/core/tests/Deal.Tests.Unit/DiscoveryWorkerServiceTests.cs @@ -519,15 +519,28 @@ public sealed class DiscoveryWorkerServiceTests } // Запись результата глобального поиска (proto DialogEntry). - private static TelegramDialogEntryDto Entry(string id, string name, string handle, string kind) + private static TelegramDialogEntryDto Entry( + string id, + string name, + string handle, + string kind) => new(id, name, handle, kind, "#666"); // Инфо источника (proto ChannelInfo). - private static TelegramChannelInfoDto Info(string id, string name, string username, string kind, int? participants, bool isForum = false) + private static TelegramChannelInfoDto Info( + string id, + string name, + string username, + string kind, + int? participants, + bool isForum = false) => new(id, name, username, kind, "#666", participants, isForum); // Сообщение форума с темой (proto EvalMessage). - private static TelegramEvalMessageDto MsgWithTopic(long id, string text, long? topic) + private static TelegramEvalMessageDto MsgWithTopic( + long id, + string text, + long? topic) => new(id, text, 1_700_000_000_000 + id, topic, TopicTitle: null); // Выборка сообщений (ok=true), тексты в порядке входных аргументов. diff --git a/src/core/tests/Deal.Tests.Unit/FakeAiTools.cs b/src/core/tests/Deal.Tests.Unit/FakeAiTools.cs index b385693..737b4a1 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeAiTools.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeAiTools.cs @@ -30,7 +30,10 @@ public sealed class FakeAiTools : IAiTools /// public Task EvaluateFitAsync( - string text, string description, IReadOnlyCollection keywords, CancellationToken ct) + string text, + string description, + IReadOnlyCollection keywords, + CancellationToken ct) { EvaluateFitCalls++; if (Error is not null) diff --git a/src/core/tests/Deal.Tests.Unit/FakeAuthStore.cs b/src/core/tests/Deal.Tests.Unit/FakeAuthStore.cs index a70d6d2..b822edc 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeAuthStore.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeAuthStore.cs @@ -105,7 +105,10 @@ public sealed class FakeAuthStore : IAuthStore } /// - public Task UpdatePasswordHashAsync(Guid userId, string passwordHash, CancellationToken ct) + public Task UpdatePasswordHashAsync( + Guid userId, + string passwordHash, + CancellationToken ct) { var index = _users.FindIndex(u => u.Id == userId); if (index >= 0) diff --git a/src/core/tests/Deal.Tests.Unit/FakeDiscoveryGateway.cs b/src/core/tests/Deal.Tests.Unit/FakeDiscoveryGateway.cs index 29cabe3..921c87e 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeDiscoveryGateway.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeDiscoveryGateway.cs @@ -80,11 +80,18 @@ public sealed class FakeDiscoveryGateway : ITelegramGateway => Task.FromResult(new TelegramAccountStatusDto("ready", Connected: true, Listener: true, "@test", Error: null, QrUrl: null)); /// - public Task StartPhoneAsync(string phone, int apiId, string apiHash, CancellationToken ct) + public Task StartPhoneAsync( + string phone, + int apiId, + string apiHash, + CancellationToken ct) => throw new NotSupportedException(); /// - public Task StartQrAsync(int apiId, string apiHash, CancellationToken ct) + public Task StartQrAsync( + int apiId, + string apiHash, + CancellationToken ct) => throw new NotSupportedException(); /// @@ -101,7 +108,10 @@ public sealed class FakeDiscoveryGateway : ITelegramGateway => throw new NotSupportedException(); /// - public Task SetMonitorAsync(string dialogId, bool enabled, CancellationToken ct) + public Task SetMonitorAsync( + string dialogId, + bool enabled, + CancellationToken ct) { SetMonitorCalls.Add((dialogId, enabled)); return Task.CompletedTask; @@ -111,18 +121,27 @@ public sealed class FakeDiscoveryGateway : ITelegramGateway public Task SetMonitorAllAsync(bool enabled, CancellationToken ct) => throw new NotSupportedException(); /// - public Task BackfillAsync(string dialogId, bool force, CancellationToken ct) + public Task BackfillAsync( + string dialogId, + bool force, + CancellationToken ct) { BackfillCalls.Add((dialogId, force)); return Task.FromResult(BackfillResult); } /// - public Task> ReadRecentAsync(string dialogId, int limit, CancellationToken ct) + public Task> ReadRecentAsync( + string dialogId, + int limit, + CancellationToken ct) => throw new NotSupportedException(); /// - public Task> SearchAsync(string query, int limit, CancellationToken ct) + public Task> SearchAsync( + string query, + int limit, + CancellationToken ct) { SearchedKeywords.Add(query); if (SearchError is not null) @@ -144,7 +163,10 @@ public sealed class FakeDiscoveryGateway : ITelegramGateway } /// - public Task ReadForEvalAsync(string dialogId, int limit, CancellationToken ct) + public Task ReadForEvalAsync( + string dialogId, + int limit, + CancellationToken ct) { ReadRequests.Add((dialogId, limit)); return Task.FromResult( diff --git a/src/core/tests/Deal.Tests.Unit/FakeDiscoveryStore.cs b/src/core/tests/Deal.Tests.Unit/FakeDiscoveryStore.cs index 2f87d76..7600ea4 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeDiscoveryStore.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeDiscoveryStore.cs @@ -90,7 +90,10 @@ public sealed class FakeDiscoveryStore : IDiscoveryStore /// Событие (join_auto/…). /// Момент события (UTC). /// Id задачи (по умолчанию — произвольный). - public void SeedLog(string @event, DateTimeOffset createdAt, string taskId = "dt_x") + public void SeedLog( + string @event, + DateTimeOffset createdAt, + string taskId = "dt_x") { _log.Add(new DiscoveryLogDto("dl_x", taskId, @event, string.Empty, createdAt.ToUnixTimeMilliseconds())); } @@ -137,7 +140,10 @@ public sealed class FakeDiscoveryStore : IDiscoveryStore } /// - public Task PatchTaskAsync(string taskId, DiscoveryTaskPatch patch, CancellationToken ct) + public Task PatchTaskAsync( + string taskId, + DiscoveryTaskPatch patch, + CancellationToken ct) { int index = _tasks.FindIndex(task => task.Id == taskId); if (index < 0) @@ -211,7 +217,10 @@ public sealed class FakeDiscoveryStore : IDiscoveryStore } /// - public Task SetTaskRunningAsync(string taskId, bool resetProgress, CancellationToken ct) + public Task SetTaskRunningAsync( + string taskId, + bool resetProgress, + CancellationToken ct) { int index = _tasks.FindIndex(task => task.Id == taskId); if (index < 0) @@ -272,7 +281,11 @@ public sealed class FakeDiscoveryStore : IDiscoveryStore } /// - public Task BumpTaskCounterAsync(string taskId, DiscoveryCounterField field, int n, CancellationToken ct) + public Task BumpTaskCounterAsync( + string taskId, + DiscoveryCounterField field, + int n, + CancellationToken ct) { int index = _tasks.FindIndex(task => task.Id == taskId); if (index < 0 || n <= 0) @@ -293,7 +306,11 @@ public sealed class FakeDiscoveryStore : IDiscoveryStore } /// - public Task AdvanceSearchAsync(string taskId, int nextIndex, bool searchDone, CancellationToken ct) + public Task AdvanceSearchAsync( + string taskId, + int nextIndex, + bool searchDone, + CancellationToken ct) { int index = _tasks.FindIndex(task => task.Id == taskId); if (index < 0) @@ -324,7 +341,10 @@ public sealed class FakeDiscoveryStore : IDiscoveryStore } /// - public Task> ListCandidatesAsync(string taskId, string? status, CancellationToken ct) + public Task> ListCandidatesAsync( + string taskId, + string? status, + CancellationToken ct) { IEnumerable query = _candidates.Where(candidate => candidate.TaskId == taskId); if (status is not null) @@ -387,7 +407,10 @@ public sealed class FakeDiscoveryStore : IDiscoveryStore } /// - public Task PatchCandidateAsync(string dialogId, DiscoveryCandidatePatch patch, CancellationToken ct) + public Task PatchCandidateAsync( + string dialogId, + DiscoveryCandidatePatch patch, + CancellationToken ct) { int index = _candidates.FindIndex(candidate => candidate.DialogId == dialogId); if (index < 0) @@ -451,7 +474,10 @@ public sealed class FakeDiscoveryStore : IDiscoveryStore } /// - public Task SetCandidateStatusAsync(string dialogId, string status, CancellationToken ct) + public Task SetCandidateStatusAsync( + string dialogId, + string status, + CancellationToken ct) { int index = _candidates.FindIndex(candidate => candidate.DialogId == dialogId); if (index < 0) @@ -468,7 +494,10 @@ public sealed class FakeDiscoveryStore : IDiscoveryStore } /// - public Task SetCandidateJoinedAsync(string dialogId, bool autoJoined, CancellationToken ct) + public Task SetCandidateJoinedAsync( + string dialogId, + bool autoJoined, + CancellationToken ct) { int index = _candidates.FindIndex(candidate => candidate.DialogId == dialogId); if (index < 0) @@ -521,7 +550,11 @@ public sealed class FakeDiscoveryStore : IDiscoveryStore } /// - public Task UpsertBlacklistAsync(string dialogId, string name, string reason, CancellationToken ct) + public Task UpsertBlacklistAsync( + string dialogId, + string name, + string reason, + CancellationToken ct) { DiscoveryBlacklistDto? existing = _blacklist.FirstOrDefault(entry => entry.DialogId == dialogId); if (existing is null) @@ -559,21 +592,32 @@ public sealed class FakeDiscoveryStore : IDiscoveryStore } /// - public Task AddLogAsync(string logId, string taskId, string logEvent, string text, CancellationToken ct) + public Task AddLogAsync( + string logId, + string taskId, + string logEvent, + string text, + CancellationToken ct) { _log.Add(new DiscoveryLogDto(logId, taskId, logEvent, text, _clock().ToUnixTimeMilliseconds())); return Task.CompletedTask; } /// - public Task CountLogEventAsync(string logEvent, DateTimeOffset sinceUtc, CancellationToken ct) + public Task CountLogEventAsync( + string logEvent, + DateTimeOffset sinceUtc, + CancellationToken ct) { long sinceMs = sinceUtc.ToUnixTimeMilliseconds(); return Task.FromResult(_log.Count(entry => entry.Event == logEvent && entry.CreatedAt >= sinceMs)); } /// - public Task> ListTaskLogAsync(string taskId, int limit, CancellationToken ct) + public Task> ListTaskLogAsync( + string taskId, + int limit, + CancellationToken ct) { IReadOnlyList entries = _log .Where(entry => entry.TaskId == taskId) diff --git a/src/core/tests/Deal.Tests.Unit/FakeFileStorage.cs b/src/core/tests/Deal.Tests.Unit/FakeFileStorage.cs index 86a3c58..749668f 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeFileStorage.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeFileStorage.cs @@ -43,7 +43,11 @@ public sealed class FakeFileStorage : IFileStorage } /// - public Task PutAsync(string objectKey, Stream content, string contentType, CancellationToken ct) + public Task PutAsync( + string objectKey, + Stream content, + string contentType, + CancellationToken ct) { ArgumentNullException.ThrowIfNull(content); diff --git a/src/core/tests/Deal.Tests.Unit/FakeGlobalSettingsStore.cs b/src/core/tests/Deal.Tests.Unit/FakeGlobalSettingsStore.cs index cba670d..f66c53e 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeGlobalSettingsStore.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeGlobalSettingsStore.cs @@ -49,7 +49,10 @@ public sealed class FakeGlobalSettingsStore : IGlobalSettingsStore } /// - public Task SetAsync(string key, string valueJson, CancellationToken ct) + public Task SetAsync( + string key, + string valueJson, + CancellationToken ct) { _rows[key] = new SettingValue(key, valueJson, DateTimeOffset.UtcNow); return Task.CompletedTask; diff --git a/src/core/tests/Deal.Tests.Unit/FakeInviteStore.cs b/src/core/tests/Deal.Tests.Unit/FakeInviteStore.cs index 5e5c012..04beb9c 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeInviteStore.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeInviteStore.cs @@ -54,7 +54,11 @@ public class FakeInviteStore : IInviteStore Task.FromResult>(_invites.OrderByDescending(i => i.CreatedAt).ToList()); /// - public Task UpdateStatusAsync(string code, string status, DateTimeOffset? activatedAt, CancellationToken ct) + public Task UpdateStatusAsync( + string code, + string status, + DateTimeOffset? activatedAt, + CancellationToken ct) { int index = _invites.FindIndex(i => i.Code == code); if (index < 0) @@ -68,7 +72,10 @@ public class FakeInviteStore : IInviteStore } /// - public virtual Task TryActivateAsync(string code, DateTimeOffset activatedAt, CancellationToken ct) + public virtual Task TryActivateAsync( + string code, + DateTimeOffset activatedAt, + CancellationToken ct) { // CAS (Task 6): переход pending → activated только если строка всё ещё pending; иначе (уже отозвана/ // активирована/истекла) — false без изменений, как условный UPDATE EF-адаптера. diff --git a/src/core/tests/Deal.Tests.Unit/FakeKanjStore.cs b/src/core/tests/Deal.Tests.Unit/FakeKanjStore.cs index dd5b9c7..0a0d17d 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeKanjStore.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeKanjStore.cs @@ -156,7 +156,10 @@ public class FakeKanjStore : ICardStore } /// - public Task ReorderContainersAsync(string space, IReadOnlyList containerIds, CancellationToken ct) + public Task ReorderContainersAsync( + string space, + IReadOnlyList containerIds, + CancellationToken ct) { for (int i = 0; i < containerIds.Count; i++) { @@ -185,7 +188,10 @@ public class FakeKanjStore : ICardStore } /// - public Task> SearchCardsAsync(string q, int limit, CancellationToken ct) + public Task> SearchCardsAsync( + string q, + int limit, + CancellationToken ct) { // 1:1 с KanbanStore.SearchCardsAsync для unit-сценариев: FTS-часть (tsvector/морфология) в фейке // недостижима — реализуется LIKE-дополнение (lower title/summary/contact/source_msg), порядок @@ -216,7 +222,10 @@ public class FakeKanjStore : ICardStore } /// - public Task GetCardBySourceAsync(string dialogId, long msgId, CancellationToken ct) + public Task GetCardBySourceAsync( + string dialogId, + long msgId, + CancellationToken ct) { // 1:1 с KanbanStore.GetCardBySourceAsync: свежайшая карточка по (dialogId, msgId); пустой dialogId — null. if (string.IsNullOrEmpty(dialogId)) @@ -338,7 +347,10 @@ public class FakeKanjStore : ICardStore } /// - public Task UpdateSeenAsync(string? cardId, string? col, CancellationToken ct) + public Task UpdateSeenAsync( + string? cardId, + string? col, + CancellationToken ct) { for (int i = 0; i < _cards.Count; i++) { @@ -397,7 +409,12 @@ public class FakeKanjStore : ICardStore } /// - public Task AddCommentAsync(string commentId, string cardId, string by, string text, CancellationToken ct) + public Task AddCommentAsync( + string commentId, + string cardId, + string by, + string text, + CancellationToken ct) { int index = _cards.FindIndex(card => card.Id == cardId); if (index < 0) @@ -471,7 +488,10 @@ public class FakeKanjStore : ICardStore } /// - public Task ArchiveAsync(IReadOnlyList cardIds, DateTimeOffset archivedAt, CancellationToken ct) + public Task ArchiveAsync( + IReadOnlyList cardIds, + DateTimeOffset archivedAt, + CancellationToken ct) { // 1:1 с KanbanStore.ArchiveAsync: один batch-перенос пачки в архив (col=archive, is_new=false, // archived_at=archivedAt, matchHits пусто; prev_col не трогается). Возврат — число архивированных. @@ -584,7 +604,12 @@ public class FakeKanjStore : ICardStore } /// - public Task UpdateConversionAsync(string cardId, double? convFrom, double? convTo, string convCur, CancellationToken ct) + public Task UpdateConversionAsync( + string cardId, + double? convFrom, + double? convTo, + string convCur, + CancellationToken ct) { // 1:1 с KanbanStore.UpdateConversionAsync: пишутся только conv-поля; нет карточки — no-op. // convCur пуст → конверсия снята (Converted = null, как маппинг адаптера ConvCur == ""). @@ -618,7 +643,10 @@ public class FakeKanjStore : ICardStore } /// - public Task PatchCardAsync(string cardId, CardPatch patch, CancellationToken ct) + public Task PatchCardAsync( + string cardId, + CardPatch patch, + CancellationToken ct) { int index = _cards.FindIndex(card => card.Id == cardId); if (index < 0) @@ -677,7 +705,10 @@ public class FakeKanjStore : ICardStore } /// - public Task AddLinkAsync(string cardId, CardLinkDto link, CancellationToken ct) + public Task AddLinkAsync( + string cardId, + CardLinkDto link, + CancellationToken ct) { int index = _cards.FindIndex(card => card.Id == cardId); if (index < 0) @@ -694,7 +725,10 @@ public class FakeKanjStore : ICardStore } /// - public Task RemoveLinkAsync(string cardId, string linkId, CancellationToken ct) + public Task RemoveLinkAsync( + string cardId, + string linkId, + CancellationToken ct) { int index = _cards.FindIndex(card => card.Id == cardId); if (index < 0) @@ -711,7 +745,10 @@ public class FakeKanjStore : ICardStore } /// - public Task AddFileAsync(string cardId, CardFileDto file, CancellationToken ct) + public Task AddFileAsync( + string cardId, + CardFileDto file, + CancellationToken ct) { int index = _cards.FindIndex(card => card.Id == cardId); if (index < 0) @@ -728,7 +765,10 @@ public class FakeKanjStore : ICardStore } /// - public Task RemoveFileAsync(string cardId, string fileId, CancellationToken ct) + public Task RemoveFileAsync( + string cardId, + string fileId, + CancellationToken ct) { int index = _cards.FindIndex(card => card.Id == cardId); if (index < 0) @@ -746,7 +786,11 @@ public class FakeKanjStore : ICardStore /// public Task MoveCardStageAsync( - string cardId, string containerId, CardHistoryDto historyEntry, long atMs, CancellationToken ct) + string cardId, + string containerId, + CardHistoryDto historyEntry, + long atMs, + CancellationToken ct) { int index = _cards.FindIndex(card => card.Id == cardId); if (index < 0) @@ -767,7 +811,10 @@ public class FakeKanjStore : ICardStore } /// - public Task SetReminderAsync(string cardId, long atMs, CancellationToken ct) + public Task SetReminderAsync( + string cardId, + long atMs, + CancellationToken ct) { int index = _cards.FindIndex(card => card.Id == cardId); if (index < 0) diff --git a/src/core/tests/Deal.Tests.Unit/FakeMlClient.cs b/src/core/tests/Deal.Tests.Unit/FakeMlClient.cs index 89177fb..10a3202 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeMlClient.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeMlClient.cs @@ -45,7 +45,11 @@ public sealed class FakeMlClient : IMlClient public Task StatusAsync(CancellationToken ct) => Task.FromResult(Status); /// - public Task PushAsync(string text, string label, double delta, CancellationToken ct) + public Task PushAsync( + string text, + string label, + double delta, + CancellationToken ct) { _pushed.Add((text, label, delta)); return Task.CompletedTask; diff --git a/src/core/tests/Deal.Tests.Unit/FakeMlLearningStore.cs b/src/core/tests/Deal.Tests.Unit/FakeMlLearningStore.cs index 5d89978..013522c 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeMlLearningStore.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeMlLearningStore.cs @@ -38,7 +38,12 @@ public sealed class FakeMlLearningStore : IMlLearningStore public Task CountOutboxAsync(CancellationToken ct) => Task.FromResult(_rows.Count); /// - public Task AddOutboxAsync(string id, string text, string label, double delta, CancellationToken ct) + public Task AddOutboxAsync( + string id, + string text, + string label, + double delta, + CancellationToken ct) { _rows.Add((id, text, label, delta)); return Task.CompletedTask; @@ -51,7 +56,11 @@ public sealed class FakeMlLearningStore : IMlLearningStore /// Текст обучающего примера. /// Метка обучения. /// Вес сигнала. - public void SeedOutbox(string id, string text, string label, double delta = 1.0) + public void SeedOutbox( + string id, + string text, + string label, + double delta = 1.0) { _rows.Add((id, text, label, delta)); } diff --git a/src/core/tests/Deal.Tests.Unit/FakePipelineStore.cs b/src/core/tests/Deal.Tests.Unit/FakePipelineStore.cs index eb9ee53..e565919 100644 --- a/src/core/tests/Deal.Tests.Unit/FakePipelineStore.cs +++ b/src/core/tests/Deal.Tests.Unit/FakePipelineStore.cs @@ -64,7 +64,10 @@ public class FakePipelineStore : IPipelineStore // ── Очередь (QueueItems) ─────────────────────────────────────────────── /// - public Task ExistsDuplicateAsync(string dialogId, long? msgId, CancellationToken ct) + public Task ExistsDuplicateAsync( + string dialogId, + long? msgId, + CancellationToken ct) { bool found = msgId is not null && _queue.Any(item => item.DialogId == dialogId && item.MsgId == msgId); @@ -79,7 +82,10 @@ public class FakePipelineStore : IPipelineStore } /// - public virtual Task> ListAsync(string? status, int limit, CancellationToken ct) + public virtual Task> ListAsync( + string? status, + int limit, + CancellationToken ct) { IReadOnlyList items = _queue .Where(item => status is null || item.Status == status) @@ -96,7 +102,10 @@ public class FakePipelineStore : IPipelineStore } /// - public Task SetStatusAsync(string id, string status, CancellationToken ct) + public Task SetStatusAsync( + string id, + string status, + CancellationToken ct) { int index = _queue.FindIndex(item => item.Id == id); if (index >= 0) @@ -152,7 +161,10 @@ public class FakePipelineStore : IPipelineStore } /// - public Task> ListPageAsync(int offset, int limit, CancellationToken ct) + public Task> ListPageAsync( + int offset, + int limit, + CancellationToken ct) { IReadOnlyList page = _rejected .OrderByDescending(item => item.RejectedAtMs) @@ -163,7 +175,11 @@ public class FakePipelineStore : IPipelineStore } /// - public Task> SearchAsync(string q, int limitFts, int limitLike, CancellationToken ct) + public Task> SearchAsync( + string q, + int limitFts, + int limitLike, + CancellationToken ct) { // FTS-кандидатов в unit нет (Ruling 6) — LIKE-половина адаптера (PipelineStore L166–180). Повторяем SQL // 1:1: lower(поле) LIKE %q%, где q уже нормализован сервисом (trim+lowercase): поле приводим к нижнему @@ -216,7 +232,11 @@ public class FakePipelineStore : IPipelineStore } /// - public Task MarkReturnedAsync(string id, string reason, DateTimeOffset returnedAt, CancellationToken ct) + public Task MarkReturnedAsync( + string id, + string reason, + DateTimeOffset returnedAt, + CancellationToken ct) { int index = _rejected.FindIndex(item => item.Id == id); if (index >= 0) @@ -269,7 +289,10 @@ public class FakePipelineStore : IPipelineStore } /// - public Task LinkAsync(string hash, string cardId, CancellationToken ct) + public Task LinkAsync( + string hash, + string cardId, + CancellationToken ct) { _dedup[hash] = cardId; return Task.CompletedTask; @@ -302,7 +325,10 @@ public class FakePipelineStore : IPipelineStore // id: Id записи (детерминированный либо r_+hex). // rejectedAtMs: Момент отсева (epoch-ms). // Возвращает: Полная запись отсева. - private static RejectedItemDto ToItem(RejectRecord record, string id, long rejectedAtMs) => new() + private static RejectedItemDto ToItem( + RejectRecord record, + string id, + long rejectedAtMs) => new() { Id = id, DialogId = record.DialogId, diff --git a/src/core/tests/Deal.Tests.Unit/FakeRateLimitCounterStore.cs b/src/core/tests/Deal.Tests.Unit/FakeRateLimitCounterStore.cs index 4b5eff8..7633af0 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeRateLimitCounterStore.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeRateLimitCounterStore.cs @@ -27,7 +27,11 @@ public sealed class FakeRateLimitCounterStore : IRateLimitCounterStore /// public Task IncrementAsync( - string key, DateTimeOffset windowStart, DateTimeOffset windowEnd, int amount, CancellationToken ct) + string key, + DateTimeOffset windowStart, + DateTimeOffset windowEnd, + int amount, + CancellationToken ct) { if (!_rows.TryGetValue(key, out Row? row) || row.WindowStart != windowStart) { @@ -44,7 +48,10 @@ public sealed class FakeRateLimitCounterStore : IRateLimitCounterStore } /// - public Task GetCountAsync(string key, DateTimeOffset windowStart, CancellationToken ct) + public Task GetCountAsync( + string key, + DateTimeOffset windowStart, + CancellationToken ct) => Task.FromResult(_rows.TryGetValue(key, out Row? row) && row.WindowStart == windowStart ? row.Count : 0); /// diff --git a/src/core/tests/Deal.Tests.Unit/FakeSettingsStore.cs b/src/core/tests/Deal.Tests.Unit/FakeSettingsStore.cs index 82b6079..a453b15 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeSettingsStore.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeSettingsStore.cs @@ -55,7 +55,10 @@ public sealed class FakeSettingsStore : ISettingsStore } /// - public Task SetAsync(string key, string valueJson, CancellationToken ct) + public Task SetAsync( + string key, + string valueJson, + CancellationToken ct) { _rows[key] = new SettingValue(key, valueJson, DateTimeOffset.UtcNow); return Task.CompletedTask; diff --git a/src/core/tests/Deal.Tests.Unit/FakeTelegramGateway.cs b/src/core/tests/Deal.Tests.Unit/FakeTelegramGateway.cs index 779c8f3..dcb60b4 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeTelegramGateway.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeTelegramGateway.cs @@ -81,11 +81,18 @@ public sealed class FakeTelegramGateway : ITelegramGateway } /// - public Task StartPhoneAsync(string phone, int apiId, string apiHash, CancellationToken ct) + public Task StartPhoneAsync( + string phone, + int apiId, + string apiHash, + CancellationToken ct) => Task.FromResult(new TelegramAuthResultDto("code", null)); /// - public Task StartQrAsync(int apiId, string apiHash, CancellationToken ct) + public Task StartQrAsync( + int apiId, + string apiHash, + CancellationToken ct) => Task.FromResult(new TelegramAuthResultDto("qr", "https://t.me/qr/test")); /// @@ -102,7 +109,10 @@ public sealed class FakeTelegramGateway : ITelegramGateway => Task.FromResult(Catalog); /// - public Task SetMonitorAsync(string dialogId, bool enabled, CancellationToken ct) + public Task SetMonitorAsync( + string dialogId, + bool enabled, + CancellationToken ct) { if (SetMonitorFailures.Contains(dialogId)) { @@ -121,7 +131,10 @@ public sealed class FakeTelegramGateway : ITelegramGateway } /// - public Task BackfillAsync(string dialogId, bool force, CancellationToken ct) + public Task BackfillAsync( + string dialogId, + bool force, + CancellationToken ct) { BackfillCalls.Add((dialogId, force)); if (BackfillFailures.Contains(dialogId)) @@ -133,11 +146,17 @@ public sealed class FakeTelegramGateway : ITelegramGateway } /// - public Task> ReadRecentAsync(string dialogId, int limit, CancellationToken ct) + public Task> ReadRecentAsync( + string dialogId, + int limit, + CancellationToken ct) => Task.FromResult>([]); /// - public Task> SearchAsync(string query, int limit, CancellationToken ct) + public Task> SearchAsync( + string query, + int limit, + CancellationToken ct) => Task.FromResult(Catalog); /// @@ -145,7 +164,10 @@ public sealed class FakeTelegramGateway : ITelegramGateway => Task.FromResult(new TelegramChannelInfoDto(dialogId, string.Empty, string.Empty, string.Empty, "#666", null, false)); /// - public Task ReadForEvalAsync(string dialogId, int limit, CancellationToken ct) + public Task ReadForEvalAsync( + string dialogId, + int limit, + CancellationToken ct) => Task.FromResult(new TelegramEvalReadDto(false, "no_history", [])); /// diff --git a/src/core/tests/Deal.Tests.Unit/FakeTelegramStore.cs b/src/core/tests/Deal.Tests.Unit/FakeTelegramStore.cs index e969ad1..cf3797b 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeTelegramStore.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeTelegramStore.cs @@ -49,7 +49,9 @@ public sealed class FakeTelegramStore : ITelegramStore /// public Task SyncFromTelegramAsync( - IReadOnlyCollection entries, bool autoMonitorNew, CancellationToken ct) + IReadOnlyCollection entries, + bool autoMonitorNew, + CancellationToken ct) { if (entries.Count == 0) { @@ -112,7 +114,10 @@ public sealed class FakeTelegramStore : ITelegramStore } /// - public Task SetMonitorAsync(string dialogId, bool enabled, CancellationToken ct) + public Task SetMonitorAsync( + string dialogId, + bool enabled, + CancellationToken ct) { if (_dialogs.TryGetValue(dialogId, out FakeTelegramDialogRow? existing)) { @@ -170,7 +175,12 @@ public sealed class FakeTelegramStore : ITelegramStore /// public Task UpsertDiscoveredMonitoredAsync( - string dialogId, string name, string handle, string kind, string hue, CancellationToken ct) + string dialogId, + string name, + string handle, + string kind, + string hue, + CancellationToken ct) { // 1:1 python add_dialog_monitored L850–873 (INSERT/ON CONFLICT): метаданные, monitor=TRUE, backfilled=FALSE. if (_dialogs.TryGetValue(dialogId, out FakeTelegramDialogRow? existing)) @@ -198,7 +208,11 @@ public sealed class FakeTelegramStore : ITelegramStore /// public Task SavePreviewAsync( - string messageId, string dialogId, string text, DateTimeOffset msgAt, CancellationToken ct) + string messageId, + string dialogId, + string text, + DateTimeOffset msgAt, + CancellationToken ct) { if (!_messages.ContainsKey(messageId)) { @@ -209,7 +223,11 @@ public sealed class FakeTelegramStore : ITelegramStore } /// - public Task TouchDialogLastAsync(string dialogId, string text, DateTimeOffset at, CancellationToken ct) + public Task TouchDialogLastAsync( + string dialogId, + string text, + DateTimeOffset at, + CancellationToken ct) { if (_dialogs.TryGetValue(dialogId, out FakeTelegramDialogRow? existing)) { @@ -220,7 +238,10 @@ public sealed class FakeTelegramStore : ITelegramStore } /// - public Task> ListMessagesAsync(string dialogId, int limit, CancellationToken ct) + public Task> ListMessagesAsync( + string dialogId, + int limit, + CancellationToken ct) { IReadOnlyList items = _messages.Values .Where(row => row.DialogId == dialogId) diff --git a/src/core/tests/Deal.Tests.Unit/FakeTenantLimitStore.cs b/src/core/tests/Deal.Tests.Unit/FakeTenantLimitStore.cs index a1902d2..d7af6ff 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeTenantLimitStore.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeTenantLimitStore.cs @@ -121,7 +121,10 @@ public sealed class FakeTenantLimitStore : ITenantLimitStore public bool Exists(Guid tenantId) => _rows.ContainsKey(tenantId); /// - public Task GetOrCreateAsync(Guid tenantId, CancellationToken ct, TokenLimitDefaults? defaults = null) + public Task GetOrCreateAsync( + Guid tenantId, + CancellationToken ct, + TokenLimitDefaults? defaults = null) { Row row = Ensure(tenantId, defaults ?? TokenBudgetDefaults.Default); return Task.FromResult(ToLimitDto(row, tenantId)); @@ -136,7 +139,10 @@ public sealed class FakeTenantLimitStore : ITenantLimitStore } /// - public Task AddUsageAsync(Guid tenantId, long tokens, CancellationToken ct) + public Task AddUsageAsync( + Guid tenantId, + long tokens, + CancellationToken ct) { Row row = Ensure(tenantId, TokenBudgetDefaults.Default); ResetIfPeriodExpired(row); @@ -151,7 +157,11 @@ public sealed class FakeTenantLimitStore : ITenantLimitStore } /// - public Task UpdateBudgetAsync(Guid tenantId, long budgetTokens, string period, CancellationToken ct) + public Task UpdateBudgetAsync( + Guid tenantId, + long budgetTokens, + string period, + CancellationToken ct) { Row row = Ensure(tenantId, TokenBudgetDefaults.Default); row.BudgetTokens = budgetTokens; diff --git a/src/core/tests/Deal.Tests.Unit/FakeTenantRegistry.cs b/src/core/tests/Deal.Tests.Unit/FakeTenantRegistry.cs index fdde73f..c94cc7f 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeTenantRegistry.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeTenantRegistry.cs @@ -41,6 +41,9 @@ public sealed class FakeTenantRegistry : ITenantRepository throw new NotSupportedException("CreateAsync не используется тестами ингресса"); /// - public Task UpdateStatusAsync(Guid id, string status, CancellationToken ct) => + public Task UpdateStatusAsync( + Guid id, + string status, + CancellationToken ct) => throw new NotSupportedException("UpdateStatusAsync не используется тестами ингресса"); } diff --git a/src/core/tests/Deal.Tests.Unit/FakeTenantRepository.cs b/src/core/tests/Deal.Tests.Unit/FakeTenantRepository.cs index 770c930..2d830d4 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeTenantRepository.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeTenantRepository.cs @@ -36,7 +36,10 @@ public sealed class FakeTenantRepository : ITenantRepository throw new NotSupportedException("FindByIdAsync не используется тестами реестра"); /// - public Task UpdateStatusAsync(Guid id, string status, CancellationToken ct) => + public Task UpdateStatusAsync( + Guid id, + string status, + CancellationToken ct) => throw new NotSupportedException("UpdateStatusAsync не используется тестами реестра"); /// diff --git a/src/core/tests/Deal.Tests.Unit/FakeTenantStore.cs b/src/core/tests/Deal.Tests.Unit/FakeTenantStore.cs index 538f455..98387ea 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeTenantStore.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeTenantStore.cs @@ -49,7 +49,10 @@ public sealed class FakeTenantStore : ITenantRepository Task.FromResult>(_tenants.ToList()); /// - public Task UpdateStatusAsync(Guid id, string status, CancellationToken ct) + public Task UpdateStatusAsync( + Guid id, + string status, + CancellationToken ct) { int index = _tenants.FindIndex(tenant => tenant.Id == id); if (index < 0) diff --git a/src/core/tests/Deal.Tests.Unit/FakeTokenUsageEventStore.cs b/src/core/tests/Deal.Tests.Unit/FakeTokenUsageEventStore.cs index 6ce2043..97ecc0c 100644 --- a/src/core/tests/Deal.Tests.Unit/FakeTokenUsageEventStore.cs +++ b/src/core/tests/Deal.Tests.Unit/FakeTokenUsageEventStore.cs @@ -59,8 +59,7 @@ public sealed class FakeTokenUsageEventStore : ITokenUsageEventStore .Select(group => ToAggregate(group.Key.ToString("yyyy-MM-dd"), group)) .ToList(); - private static IReadOnlyList GroupByString( - IEnumerable source, Func keySelector) => + private static IReadOnlyList GroupByString(IEnumerable source, Func keySelector) => source .GroupBy(keySelector) .Select(group => ToAggregate(group.Key, group)) diff --git a/src/core/tests/Deal.Tests.Unit/FileKindDetectorTests.cs b/src/core/tests/Deal.Tests.Unit/FileKindDetectorTests.cs index 68a38d7..b681b53 100644 --- a/src/core/tests/Deal.Tests.Unit/FileKindDetectorTests.cs +++ b/src/core/tests/Deal.Tests.Unit/FileKindDetectorTests.cs @@ -99,7 +99,11 @@ public sealed class FileKindDetectorTests AssertDetected("file.unknown", mime: "IMAGE/PNG", kind: "image", label: "Изображение"); } - private static void AssertDetected(string name, string? mime, string kind, string label) + private static void AssertDetected( + string name, + string? mime, + string kind, + string label) { CardFileKind result = FileKindDetector.Detect(name, mime); Assert.Equal(kind, result.Kind); diff --git a/src/core/tests/Deal.Tests.Unit/ForwardedHeadersHttpTests.cs b/src/core/tests/Deal.Tests.Unit/ForwardedHeadersHttpTests.cs index 0252905..468389a 100644 --- a/src/core/tests/Deal.Tests.Unit/ForwardedHeadersHttpTests.cs +++ b/src/core/tests/Deal.Tests.Unit/ForwardedHeadersHttpTests.cs @@ -192,7 +192,10 @@ public sealed class ForwardedHeadersHttpTests // baseAddress: Базовый адрес хоста. // forwarded: Шлются ли прокси-заголовки. // Возвращает: Тело ответа {ip, scheme}. - private static async Task ProbeAsync(HttpClient client, string baseAddress, bool forwarded) + private static async Task ProbeAsync( + HttpClient client, + string baseAddress, + bool forwarded) { using var request = new HttpRequestMessage(HttpMethod.Get, baseAddress + ClientProbePath); if (forwarded) diff --git a/src/core/tests/Deal.Tests.Unit/GrpcAiClassifierTests.cs b/src/core/tests/Deal.Tests.Unit/GrpcAiClassifierTests.cs index 845fc04..66a1cb8 100644 --- a/src/core/tests/Deal.Tests.Unit/GrpcAiClassifierTests.cs +++ b/src/core/tests/Deal.Tests.Unit/GrpcAiClassifierTests.cs @@ -305,7 +305,10 @@ public sealed class GrpcAiClassifierTests // limits: Фейк-хранилище лимитов (списание usage; при null — собственный экземпляр). // Возвращает: Экземпляр GrpcAiClassifier. private static GrpcAiClassifier CreateClassifier( - int port, FakeSettingsStore settings, FakeSecretCipher cipher, FakeKanjStore kanjStore, + int port, + FakeSettingsStore settings, + FakeSecretCipher cipher, + FakeKanjStore kanjStore, FakeTenantLimitStore? limits = null) { var tenantContext = new TenantContext(); @@ -337,7 +340,10 @@ public sealed class GrpcAiClassifierTests // completion: Ожидаемые токены ответа. // total: Ожидаемые суммарные токены. private static async Task AssertUsageAsync( - FakeSettingsStore settings, uint prompt, uint completion, uint total) + FakeSettingsStore settings, + uint prompt, + uint completion, + uint total) { string? stored = settings.GetStoredJson(SettingsKeys.AiTokenUsage); Assert.NotNull(stored); diff --git a/src/core/tests/Deal.Tests.Unit/GrpcAiToolsTests.cs b/src/core/tests/Deal.Tests.Unit/GrpcAiToolsTests.cs index 938be97..709d7e5 100644 --- a/src/core/tests/Deal.Tests.Unit/GrpcAiToolsTests.cs +++ b/src/core/tests/Deal.Tests.Unit/GrpcAiToolsTests.cs @@ -137,7 +137,10 @@ public sealed class GrpcAiToolsTests // limits: Фейк-хранилище лимитов (списание usage; при null — собственный экземпляр). // Возвращает: Экземпляр GrpcAiTools. private static GrpcAiTools CreateTools( - int port, FakeSettingsStore settings, FakeSecretCipher cipher, FakeTenantLimitStore? limits = null) + int port, + FakeSettingsStore settings, + FakeSecretCipher cipher, + FakeTenantLimitStore? limits = null) { var tenantContext = new TenantContext(); tenantContext.SetTenant(new TenantId(TenantIdValue)); diff --git a/src/core/tests/Deal.Tests.Unit/IntegrationsDiTests.cs b/src/core/tests/Deal.Tests.Unit/IntegrationsDiTests.cs index cbf828e..8e7896c 100644 --- a/src/core/tests/Deal.Tests.Unit/IntegrationsDiTests.cs +++ b/src/core/tests/Deal.Tests.Unit/IntegrationsDiTests.cs @@ -165,7 +165,9 @@ public sealed class IntegrationsDiTests // telegramOptions: Конфигурация секции Services:Telegram сценария. // Возвращает: Провайдер с зарегистрированными сервисами теста. private static ServiceProvider BuildProvider( - MlServiceOptions mlOptions, AiServiceOptions aiOptions, TelegramServiceOptions telegramOptions) + MlServiceOptions mlOptions, + AiServiceOptions aiOptions, + TelegramServiceOptions telegramOptions) { var services = new ServiceCollection(); var tenantContext = new TenantContext(); diff --git a/src/core/tests/Deal.Tests.Unit/JoinEndpointHttpTests.cs b/src/core/tests/Deal.Tests.Unit/JoinEndpointHttpTests.cs index 32f8c25..de92987 100644 --- a/src/core/tests/Deal.Tests.Unit/JoinEndpointHttpTests.cs +++ b/src/core/tests/Deal.Tests.Unit/JoinEndpointHttpTests.cs @@ -293,7 +293,11 @@ public sealed class JoinEndpointHttpTests } // Создаёт приглашение-строку для сидирования (дефолт — живой pending). - private static InviteDto NewInvite(string email, Guid? tenantId = null, string status = InviteStatuses.Pending, DateTimeOffset? expiresAt = null) => + private static InviteDto NewInvite( + string email, + Guid? tenantId = null, + string status = InviteStatuses.Pending, + DateTimeOffset? expiresAt = null) => new( Code, email, @@ -305,7 +309,10 @@ public sealed class JoinEndpointHttpTests DateTimeOffset.UtcNow); // Проверяет DetailJson записи аудита: поля email и codeHash (Security review: код — хэш). - private static void AssertDetailHasEmailAndCode(AuditRecordDto record, string email, string code) + private static void AssertDetailHasEmailAndCode( + AuditRecordDto record, + string email, + string code) { Assert.NotNull(record.DetailJson); using var document = JsonDocument.Parse(record.DetailJson!); @@ -323,7 +330,10 @@ public sealed class JoinEndpointHttpTests }; // POST JSON-тела и возврат ответа. - private static async Task PostJsonAsync(HttpClient client, string url, object body) + private static async Task PostJsonAsync( + HttpClient client, + string url, + object body) { string json = JsonSerializer.Serialize(body); using var content = new StringContent(json, Encoding.UTF8, "application/json"); diff --git a/src/core/tests/Deal.Tests.Unit/JoinFlowTests.cs b/src/core/tests/Deal.Tests.Unit/JoinFlowTests.cs index 048d24f..0877001 100644 --- a/src/core/tests/Deal.Tests.Unit/JoinFlowTests.cs +++ b/src/core/tests/Deal.Tests.Unit/JoinFlowTests.cs @@ -399,7 +399,10 @@ public sealed class JoinFlowTests // FakeInviteStore, имитирующий гонку: к моменту CAS-вызова конкурент уже перевёл строку в заданный статус. private sealed class RacingInviteStore(string statusBeforeActivate) : FakeInviteStore { - public override Task TryActivateAsync(string code, DateTimeOffset activatedAt, CancellationToken ct) + public override Task TryActivateAsync( + string code, + DateTimeOffset activatedAt, + CancellationToken ct) { // Конкурент (отзыв/другая активация) «успел» до нашего CAS — активация обязана не выполниться. _ = base.UpdateStatusAsync(code, statusBeforeActivate, null, ct); diff --git a/src/core/tests/Deal.Tests.Unit/LocalColumnSuggesterTests.cs b/src/core/tests/Deal.Tests.Unit/LocalColumnSuggesterTests.cs index 62e283f..20d5179 100644 --- a/src/core/tests/Deal.Tests.Unit/LocalColumnSuggesterTests.cs +++ b/src/core/tests/Deal.Tests.Unit/LocalColumnSuggesterTests.cs @@ -48,7 +48,11 @@ public sealed class LocalColumnSuggesterTests // text: Исходное сообщение. // store: Хранилище канбана. // receivedAtMs: Время получения, epoch-ms. - private static void SeedInbox(string id, string text, FakeKanjStore store, long receivedAtMs = 0) => + private static void SeedInbox( + string id, + string text, + FakeKanjStore store, + long receivedAtMs = 0) => store.SeedCard(new CardDto { Id = id, diff --git a/src/core/tests/Deal.Tests.Unit/LoginAttemptEndpointHttpTests.cs b/src/core/tests/Deal.Tests.Unit/LoginAttemptEndpointHttpTests.cs index bf4dccb..8791ce5 100644 --- a/src/core/tests/Deal.Tests.Unit/LoginAttemptEndpointHttpTests.cs +++ b/src/core/tests/Deal.Tests.Unit/LoginAttemptEndpointHttpTests.cs @@ -128,7 +128,10 @@ public sealed class LoginAttemptEndpointHttpTests }; // POST JSON-тела и возврат ответа. - private static async Task PostJsonAsync(HttpClient client, string url, object body) + private static async Task PostJsonAsync( + HttpClient client, + string url, + object body) { string json = JsonSerializer.Serialize(body); using var content = new StringContent(json, Encoding.UTF8, "application/json"); diff --git a/src/core/tests/Deal.Tests.Unit/MlOutboxFlushSchedulerTests.cs b/src/core/tests/Deal.Tests.Unit/MlOutboxFlushSchedulerTests.cs index a2b0335..e56d2bc 100644 --- a/src/core/tests/Deal.Tests.Unit/MlOutboxFlushSchedulerTests.cs +++ b/src/core/tests/Deal.Tests.Unit/MlOutboxFlushSchedulerTests.cs @@ -212,7 +212,10 @@ public sealed class MlOutboxFlushSchedulerTests // store: Хранилище-фейк. // count: Число строк. // prefix: Префикс id (различает тенантов теста). - private static void SeedRows(FakeMlLearningStore store, int count, string prefix) + private static void SeedRows( + FakeMlLearningStore store, + int count, + string prefix) { for (int i = 0; i < count; i++) { diff --git a/src/core/tests/Deal.Tests.Unit/MlReviewServiceTests.cs b/src/core/tests/Deal.Tests.Unit/MlReviewServiceTests.cs index 61c5efe..767c88d 100644 --- a/src/core/tests/Deal.Tests.Unit/MlReviewServiceTests.cs +++ b/src/core/tests/Deal.Tests.Unit/MlReviewServiceTests.cs @@ -29,7 +29,10 @@ public sealed class MlReviewServiceTests return new MlReviewService(pipeline, kanj, cards, processing, ml); } - private static QueueItemDto QueueRow(long msgId, string text, string dialog = Dialog) => new() + private static QueueItemDto QueueRow( + long msgId, + string text, + string dialog = Dialog) => new() { Id = $"p_{msgId}", DialogId = dialog, @@ -40,7 +43,10 @@ public sealed class MlReviewServiceTests MsgAtMs = 1_700_000_000_000 + msgId, }; - private static RejectedItemDto RejectedRow(long msgId, string text, string dialog = Dialog) => new() + private static RejectedItemDto RejectedRow( + long msgId, + string text, + string dialog = Dialog) => new() { Id = $"r_{msgId}", DialogId = dialog, @@ -52,7 +58,11 @@ public sealed class MlReviewServiceTests MsgAtMs = 1_700_000_000_000 + msgId, }; - private static CardDto Card(long msgId, string text, string col = "inbox", string dialog = Dialog) => new() + private static CardDto Card( + long msgId, + string text, + string col = "inbox", + string dialog = Dialog) => new() { Id = $"c_{msgId}", Col = col, diff --git a/src/core/tests/Deal.Tests.Unit/MtlsCertificatesTests.cs b/src/core/tests/Deal.Tests.Unit/MtlsCertificatesTests.cs index 32b9846..e769ad1 100644 --- a/src/core/tests/Deal.Tests.Unit/MtlsCertificatesTests.cs +++ b/src/core/tests/Deal.Tests.Unit/MtlsCertificatesTests.cs @@ -264,7 +264,10 @@ public sealed class MtlsCertificatesTests // issuer: Подписывающий CA (с ключом). // commonName: CN и DNS-имя SAN. // ekuOid: OID EKU (serverAuth/clientAuth). - private static byte[] CreateSignedLeafPfx(X509Certificate2 issuer, string commonName, string ekuOid) + private static byte[] CreateSignedLeafPfx( + X509Certificate2 issuer, + string commonName, + string ekuOid) { using RSA key = RSA.Create(2048); var request = new CertificateRequest( diff --git a/src/core/tests/Deal.Tests.Unit/OperatorAnalyticsEndpointsHttpTests.cs b/src/core/tests/Deal.Tests.Unit/OperatorAnalyticsEndpointsHttpTests.cs index 1057354..365c969 100644 --- a/src/core/tests/Deal.Tests.Unit/OperatorAnalyticsEndpointsHttpTests.cs +++ b/src/core/tests/Deal.Tests.Unit/OperatorAnalyticsEndpointsHttpTests.cs @@ -300,7 +300,12 @@ public sealed class OperatorAnalyticsEndpointsHttpTests CancellationToken.None); private static Task SeedTokensAsync( - FakeTokenUsageEventStore store, Guid tenantId, string provider, long prompt, long completion, long total) => + FakeTokenUsageEventStore store, + Guid tenantId, + string provider, + long prompt, + long completion, + long total) => store.AppendAsync( new TokenUsageEventDto( TenantId: tenantId, @@ -327,7 +332,10 @@ public sealed class OperatorAnalyticsEndpointsHttpTests Assert.Equal(HttpStatusCode.OK, login.StatusCode); } - private static async Task PostJsonAsync(HttpClient client, string url, object body) + private static async Task PostJsonAsync( + HttpClient client, + string url, + object body) { string json = JsonSerializer.Serialize(body); using var content = new StringContent(json, Encoding.UTF8, "application/json"); diff --git a/src/core/tests/Deal.Tests.Unit/OperatorAuditEndpointsHttpTests.cs b/src/core/tests/Deal.Tests.Unit/OperatorAuditEndpointsHttpTests.cs index 966fb4e..be907eb 100644 --- a/src/core/tests/Deal.Tests.Unit/OperatorAuditEndpointsHttpTests.cs +++ b/src/core/tests/Deal.Tests.Unit/OperatorAuditEndpointsHttpTests.cs @@ -329,7 +329,10 @@ public sealed class OperatorAuditEndpointsHttpTests } // POST JSON-тела и возврат ответа. - private static async Task PostJsonAsync(HttpClient client, string url, object body) + private static async Task PostJsonAsync( + HttpClient client, + string url, + object body) { string json = JsonSerializer.Serialize(body); using var content = new StringContent(json, Encoding.UTF8, "application/json"); diff --git a/src/core/tests/Deal.Tests.Unit/OperatorAuthEndpointsHttpTests.cs b/src/core/tests/Deal.Tests.Unit/OperatorAuthEndpointsHttpTests.cs index 3b5b24c..f00f5c1 100644 --- a/src/core/tests/Deal.Tests.Unit/OperatorAuthEndpointsHttpTests.cs +++ b/src/core/tests/Deal.Tests.Unit/OperatorAuthEndpointsHttpTests.cs @@ -234,7 +234,10 @@ public sealed class OperatorAuthEndpointsHttpTests }; // POST JSON-тела и возврат ответа. - private static async Task PostJsonAsync(HttpClient client, string url, object body) + private static async Task PostJsonAsync( + HttpClient client, + string url, + object body) { string json = JsonSerializer.Serialize(body); using var content = new StringContent(json, Encoding.UTF8, "application/json"); diff --git a/src/core/tests/Deal.Tests.Unit/OperatorBootstrapHostedServiceTests.cs b/src/core/tests/Deal.Tests.Unit/OperatorBootstrapHostedServiceTests.cs index 2652794..ccebd4f 100644 --- a/src/core/tests/Deal.Tests.Unit/OperatorBootstrapHostedServiceTests.cs +++ b/src/core/tests/Deal.Tests.Unit/OperatorBootstrapHostedServiceTests.cs @@ -194,7 +194,12 @@ public sealed class OperatorBootstrapHostedServiceTests public bool IsEnabled(LogLevel logLevel) => true; /// - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + public void Log( + LogLevel logLevel, + EventId eventId, + TState state, + Exception? exception, + Func formatter) { Messages.Add(formatter(state, exception)); } diff --git a/src/core/tests/Deal.Tests.Unit/OperatorHealthEndpointsHttpTests.cs b/src/core/tests/Deal.Tests.Unit/OperatorHealthEndpointsHttpTests.cs index 54a6d5e..19ecd2a 100644 --- a/src/core/tests/Deal.Tests.Unit/OperatorHealthEndpointsHttpTests.cs +++ b/src/core/tests/Deal.Tests.Unit/OperatorHealthEndpointsHttpTests.cs @@ -103,8 +103,7 @@ public sealed class OperatorHealthEndpointsHttpTests } // Прогоняет сценарий на хосте с активным оператором operator/operator (реестр/лимиты пустые). - private static Task RunAsync( - Func scenario) => + private static Task RunAsync(Func scenario) => OperatorAuthHttpHost.RunAsync(NewOperatorStore(), new FakeAuthStore(), scenario); // Фейк-хранилище оператора с активным оператором operator/operator. @@ -128,7 +127,10 @@ public sealed class OperatorHealthEndpointsHttpTests }; // POST JSON-тела и возврат ответа. - private static async Task PostJsonAsync(HttpClient client, string url, object body) + private static async Task PostJsonAsync( + HttpClient client, + string url, + object body) { string json = JsonSerializer.Serialize(body); using var content = new StringContent(json, Encoding.UTF8, "application/json"); diff --git a/src/core/tests/Deal.Tests.Unit/OperatorInvitesEndpointsHttpTests.cs b/src/core/tests/Deal.Tests.Unit/OperatorInvitesEndpointsHttpTests.cs index 2fa854e..b29f27e 100644 --- a/src/core/tests/Deal.Tests.Unit/OperatorInvitesEndpointsHttpTests.cs +++ b/src/core/tests/Deal.Tests.Unit/OperatorInvitesEndpointsHttpTests.cs @@ -250,7 +250,10 @@ public sealed class OperatorInvitesEndpointsHttpTests // ─── Хелперы ───────────────────────────────────────────────────────── // Проверяет DetailJson записи аудита: поля email и codeHash (Security review: код — хэш). - private static void AssertDetailHasEmailAndCode(AuditRecordDto record, string email, string code) + private static void AssertDetailHasEmailAndCode( + AuditRecordDto record, + string email, + string code) { Assert.NotNull(record.DetailJson); using var document = JsonDocument.Parse(record.DetailJson!); @@ -291,7 +294,10 @@ public sealed class OperatorInvitesEndpointsHttpTests }; // POST JSON-тела и возврат ответа. - private static async Task PostJsonAsync(HttpClient client, string url, object body) + private static async Task PostJsonAsync( + HttpClient client, + string url, + object body) { string json = JsonSerializer.Serialize(body); using var content = new StringContent(json, Encoding.UTF8, "application/json"); diff --git a/src/core/tests/Deal.Tests.Unit/OperatorLimitsEndpointsHelpersTests.cs b/src/core/tests/Deal.Tests.Unit/OperatorLimitsEndpointsHelpersTests.cs index 3326df7..be1c7c1 100644 --- a/src/core/tests/Deal.Tests.Unit/OperatorLimitsEndpointsHelpersTests.cs +++ b/src/core/tests/Deal.Tests.Unit/OperatorLimitsEndpointsHelpersTests.cs @@ -13,7 +13,10 @@ public sealed class OperatorLimitsEndpointsHelpersTests [InlineData(8_000_000, 10_000_000, 80)] [InlineData(10_000_000, 10_000_000, 100)] [InlineData(500_000_000_000_000_000, 1_000_000_000_000_000_000, 50)] - public void CalculatePercent_ReturnsFloorPercentInRange(long used, long budget, int expected) + public void CalculatePercent_ReturnsFloorPercentInRange( + long used, + long budget, + int expected) { Assert.Equal(expected, OperatorLimitsEndpoints.CalculatePercent(used, budget)); } diff --git a/src/core/tests/Deal.Tests.Unit/OperatorLimitsEndpointsHttpTests.cs b/src/core/tests/Deal.Tests.Unit/OperatorLimitsEndpointsHttpTests.cs index 9808988..0a277ba 100644 --- a/src/core/tests/Deal.Tests.Unit/OperatorLimitsEndpointsHttpTests.cs +++ b/src/core/tests/Deal.Tests.Unit/OperatorLimitsEndpointsHttpTests.cs @@ -295,9 +295,7 @@ public sealed class OperatorLimitsEndpointsHttpTests $"{baseAddress}/api/operator/tenants/{tenantId}/limit"; // Прогоняет сценарий на хосте с двумя активными тенантами и предзаполненными строками лимитов. - private static Task RunAsync( - Func scenario, - FakeAuditLogStore? auditStore = null) + private static Task RunAsync(Func scenario, FakeAuditLogStore? auditStore = null) { // Первый тенант: расход 80% месячного бюджета (8 000 000 / 10 000 000) + флаг Warned80; // второй: небольшой бюджет без расхода. Дата начала периода — текущая (reset не срабатывает). @@ -359,7 +357,10 @@ public sealed class OperatorLimitsEndpointsHttpTests }; // POST JSON-тела и возврат ответа. - private static async Task PostJsonAsync(HttpClient client, string url, object body) + private static async Task PostJsonAsync( + HttpClient client, + string url, + object body) { string json = JsonSerializer.Serialize(body); using var content = new StringContent(json, Encoding.UTF8, "application/json"); @@ -367,7 +368,10 @@ public sealed class OperatorLimitsEndpointsHttpTests } // PATCH JSON-тела и возврат ответа. - private static async Task PatchJsonAsync(HttpClient client, string url, object body) + private static async Task PatchJsonAsync( + HttpClient client, + string url, + object body) { string json = JsonSerializer.Serialize(body); using var content = new StringContent(json, Encoding.UTF8, "application/json"); diff --git a/src/core/tests/Deal.Tests.Unit/OperatorMaintenanceEndpointsHttpTests.cs b/src/core/tests/Deal.Tests.Unit/OperatorMaintenanceEndpointsHttpTests.cs index 549df91..6df90a2 100644 --- a/src/core/tests/Deal.Tests.Unit/OperatorMaintenanceEndpointsHttpTests.cs +++ b/src/core/tests/Deal.Tests.Unit/OperatorMaintenanceEndpointsHttpTests.cs @@ -80,9 +80,7 @@ public sealed class OperatorMaintenanceEndpointsHttpTests } // Прогоняет сценарий на хосте с активным оператором operator/operator и заданным реестром тенантов. - private static Task RunAsync( - FakeTenantStore tenantStore, - Func scenario) => + private static Task RunAsync(FakeTenantStore tenantStore, Func scenario) => OperatorAuthHttpHost.RunAsync(NewOperatorStore(), new FakeAuthStore(), scenario, tenantStore: tenantStore); // Фейк-хранилище оператора с активным оператором operator/operator. diff --git a/src/core/tests/Deal.Tests.Unit/OperatorSettingsEndpointsHttpTests.cs b/src/core/tests/Deal.Tests.Unit/OperatorSettingsEndpointsHttpTests.cs index bba2a9f..7ee8d92 100644 --- a/src/core/tests/Deal.Tests.Unit/OperatorSettingsEndpointsHttpTests.cs +++ b/src/core/tests/Deal.Tests.Unit/OperatorSettingsEndpointsHttpTests.cs @@ -268,9 +268,7 @@ public sealed class OperatorSettingsEndpointsHttpTests $"{baseAddress}/api/operator/settings/telegram-keys"; // Прогоняет сценарий на хосте с фейком глобального хранилища и аудита. - private static Task RunAsync( - Func scenario, - FakeAuditLogStore? auditStore = null) => + private static Task RunAsync(Func scenario, FakeAuditLogStore? auditStore = null) => OperatorAuthHttpHost.RunWithGlobalSettingsAsync( NewOperatorStore(), new FakeAuthStore(), @@ -306,7 +304,10 @@ public sealed class OperatorSettingsEndpointsHttpTests }; // POST JSON-тела и возврат ответа. - private static async Task PostJsonAsync(HttpClient client, string url, object body) + private static async Task PostJsonAsync( + HttpClient client, + string url, + object body) { string json = JsonSerializer.Serialize(body); using var content = new StringContent(json, Encoding.UTF8, "application/json"); @@ -314,7 +315,10 @@ public sealed class OperatorSettingsEndpointsHttpTests } // PUT JSON-тела и возврат ответа. - private static async Task PutJsonAsync(HttpClient client, string url, object body) + private static async Task PutJsonAsync( + HttpClient client, + string url, + object body) { string json = JsonSerializer.Serialize(body); using var content = new StringContent(json, Encoding.UTF8, "application/json"); diff --git a/src/core/tests/Deal.Tests.Unit/OperatorTenantsEndpointsHttpTests.cs b/src/core/tests/Deal.Tests.Unit/OperatorTenantsEndpointsHttpTests.cs index 2d0ae3c..43ccb22 100644 --- a/src/core/tests/Deal.Tests.Unit/OperatorTenantsEndpointsHttpTests.cs +++ b/src/core/tests/Deal.Tests.Unit/OperatorTenantsEndpointsHttpTests.cs @@ -513,7 +513,10 @@ public sealed class OperatorTenantsEndpointsHttpTests } // Проверяет запись tenant_status_changed с заданным статусом и актором-оператором. - private static void AssertStatusChangedAudit(FakeAuditLogStore auditStore, FakeOperatorAuthStore operatorStore, string status) + private static void AssertStatusChangedAudit( + FakeAuditLogStore auditStore, + FakeOperatorAuthStore operatorStore, + string status) { // В ленте может быть несколько tenant_status_changed этого тенанта (suspend → unsuspend) — ищем по статусу. AuditRecordDto record = auditStore.Records @@ -546,7 +549,10 @@ public sealed class OperatorTenantsEndpointsHttpTests // GET с tenant-кукой deal_session=rawToken (impersonation-токен как значение куки). private static async Task GetWithTenantCookieAsync( - HttpClient client, string baseAddress, string rawToken, string path) + HttpClient client, + string baseAddress, + string rawToken, + string path) { using var request = new HttpRequestMessage(HttpMethod.Get, $"{baseAddress}{path}"); request.Headers.TryAddWithoutValidation("Cookie", $"{TenantCookieName}={rawToken}"); @@ -555,7 +561,10 @@ public sealed class OperatorTenantsEndpointsHttpTests // POST без тела с tenant-кукой deal_session=rawToken. private static async Task PostWithTenantCookieAsync( - HttpClient client, string baseAddress, string rawToken, string path) + HttpClient client, + string baseAddress, + string rawToken, + string path) { using var request = new HttpRequestMessage(HttpMethod.Post, $"{baseAddress}{path}"); request.Headers.TryAddWithoutValidation("Cookie", $"{TenantCookieName}={rawToken}"); @@ -605,7 +614,10 @@ public sealed class OperatorTenantsEndpointsHttpTests }; // POST JSON-тела и возврат ответа. - private static async Task PostJsonAsync(HttpClient client, string url, object body) + private static async Task PostJsonAsync( + HttpClient client, + string url, + object body) { string json = JsonSerializer.Serialize(body); using var content = new StringContent(json, Encoding.UTF8, "application/json"); diff --git a/src/core/tests/Deal.Tests.Unit/OriginGuardHttpTests.cs b/src/core/tests/Deal.Tests.Unit/OriginGuardHttpTests.cs index b66e339..60f1b60 100644 --- a/src/core/tests/Deal.Tests.Unit/OriginGuardHttpTests.cs +++ b/src/core/tests/Deal.Tests.Unit/OriginGuardHttpTests.cs @@ -189,7 +189,11 @@ public sealed class OriginGuardHttpTests // origin: Значение заголовка Origin; null — заголовок не шлётся. // Возвращает: Ответ эндпоинта. private static async Task SendAsync( - HttpClient client, HttpMethod method, string baseAddress, string path, string? origin) + HttpClient client, + HttpMethod method, + string baseAddress, + string path, + string? origin) { using var request = new HttpRequestMessage(method, baseAddress + path); if (origin is not null) diff --git a/src/core/tests/Deal.Tests.Unit/PipelineCardWriterTests.cs b/src/core/tests/Deal.Tests.Unit/PipelineCardWriterTests.cs index 3fa8b6e..9b33a06 100644 --- a/src/core/tests/Deal.Tests.Unit/PipelineCardWriterTests.cs +++ b/src/core/tests/Deal.Tests.Unit/PipelineCardWriterTests.cs @@ -120,7 +120,10 @@ public sealed class PipelineCardWriterTests isVacancy, isVacancyKnown, IsSpam: false, board); // Строка очереди-источника (поля сообщения; статус/время постановки не важны). - private static QueueItemDto Message(string text, string dialogId = "d_1", long? msgId = 123) => new() + private static QueueItemDto Message( + string text, + string dialogId = "d_1", + long? msgId = 123) => new() { Id = "p_1", DialogId = dialogId, diff --git a/src/core/tests/Deal.Tests.Unit/PipelineProcessingServiceTests.cs b/src/core/tests/Deal.Tests.Unit/PipelineProcessingServiceTests.cs index 19404c3..8085cbb 100644 --- a/src/core/tests/Deal.Tests.Unit/PipelineProcessingServiceTests.cs +++ b/src/core/tests/Deal.Tests.Unit/PipelineProcessingServiceTests.cs @@ -396,7 +396,11 @@ public sealed class PipelineProcessingServiceTests } // Команда записи отсева (поля по умолчанию — length/stop, текст без ограничений). - private static RejectRecord Reject(string dialogId, long? msgId, string stage, string reason) => new() + private static RejectRecord Reject( + string dialogId, + long? msgId, + string stage, + string reason) => new() { DialogId = dialogId, MsgId = msgId, @@ -407,7 +411,10 @@ public sealed class PipelineProcessingServiceTests }; // Строка очереди для сидирования (id/статус/время постановки). - private static QueueItemDto QueueItem(string id, string status, long queuedAtMs) => new() + private static QueueItemDto QueueItem( + string id, + string status, + long queuedAtMs) => new() { Id = id, DialogId = "d_1", diff --git a/src/core/tests/Deal.Tests.Unit/PipelineWorkerGrpcAiTests.cs b/src/core/tests/Deal.Tests.Unit/PipelineWorkerGrpcAiTests.cs index 78f2fa1..4030d20 100644 --- a/src/core/tests/Deal.Tests.Unit/PipelineWorkerGrpcAiTests.cs +++ b/src/core/tests/Deal.Tests.Unit/PipelineWorkerGrpcAiTests.cs @@ -192,7 +192,10 @@ public sealed class PipelineWorkerGrpcAiTests // budgeted: True — классификатор обёрнут декоратором BudgetedAiClassifier // (гейт Task 9; fallback — реальный LocalAiClassifier). // Возвращает: Воркер и фейки для проверок. - private static Context CreateContext(int port, FakeTenantLimitStore? limits = null, bool budgeted = false) + private static Context CreateContext( + int port, + FakeTenantLimitStore? limits = null, + bool budgeted = false) { var settings = new FakeSettingsStore(); var pipelineStore = new FakePipelineStore(); diff --git a/src/core/tests/Deal.Tests.Unit/PipelineWorkerSchedulerTests.cs b/src/core/tests/Deal.Tests.Unit/PipelineWorkerSchedulerTests.cs index 6ce9990..c18596f 100644 --- a/src/core/tests/Deal.Tests.Unit/PipelineWorkerSchedulerTests.cs +++ b/src/core/tests/Deal.Tests.Unit/PipelineWorkerSchedulerTests.cs @@ -242,7 +242,10 @@ public sealed class PipelineWorkerSchedulerTests new(id, Name: "tenant", Status: "active", CreatedAt: DateTimeOffset.UtcNow); // Строка очереди (status new; сценарий перекрывает текст/время — сейчас, stale не сработает). - private static QueueItemDto QueueRow(string id, string text, string dialogId) => new() + private static QueueItemDto QueueRow( + string id, + string text, + string dialogId) => new() { Id = id, DialogId = dialogId, @@ -292,7 +295,10 @@ public sealed class PipelineWorkerSchedulerTests private sealed class ThrowingQueueReadPipelineStore : FakePipelineStore { /// - public override Task> ListAsync(string? status, int limit, CancellationToken ct) + public override Task> ListAsync( + string? status, + int limit, + CancellationToken ct) { throw new InvalidOperationException("Тестовый сбой чтения очереди (ListAsync)."); } @@ -313,7 +319,12 @@ public sealed class PipelineWorkerSchedulerTests public bool IsEnabled(LogLevel logLevel) => true; /// - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + public void Log( + LogLevel logLevel, + EventId eventId, + TState state, + Exception? exception, + Func formatter) { Messages.Add(exception is null ? formatter(state, exception) : formatter(state, exception) + " :: " + exception); } diff --git a/src/core/tests/Deal.Tests.Unit/PipelineWorkerServiceTests.cs b/src/core/tests/Deal.Tests.Unit/PipelineWorkerServiceTests.cs index 5235550..f2b6ee8 100644 --- a/src/core/tests/Deal.Tests.Unit/PipelineWorkerServiceTests.cs +++ b/src/core/tests/Deal.Tests.Unit/PipelineWorkerServiceTests.cs @@ -118,7 +118,11 @@ public sealed class PipelineWorkerServiceTests // msgAtMs: Время исходного сообщения (дефолт — сейчас: не устарело). // force: Флаг «возвращено из отсева». // Возвращает: Строка как если бы была сохранена в БД. - private static QueueItemDto QueueRow(string id, string text, long? msgAtMs = null, bool force = false) => new() + private static QueueItemDto QueueRow( + string id, + string text, + long? msgAtMs = null, + bool force = false) => new() { Id = id, DialogId = "d_1", diff --git a/src/core/tests/Deal.Tests.Unit/PromptDefaultsTests.cs b/src/core/tests/Deal.Tests.Unit/PromptDefaultsTests.cs index a6baa70..49056f9 100644 --- a/src/core/tests/Deal.Tests.Unit/PromptDefaultsTests.cs +++ b/src/core/tests/Deal.Tests.Unit/PromptDefaultsTests.cs @@ -157,7 +157,10 @@ public sealed class PromptDefaultsTests // ─── Помощники ─────────────────────────────────────────────────────────── - private static void AssertPromptsIdentical(string label, string expected, string actual) + private static void AssertPromptsIdentical( + string label, + string expected, + string actual) { Assert.True(!expected.EndsWith('\n'), label + ": в data.js текст не оканчивается переводом строки"); Assert.True(!actual.EndsWith('\n'), label + ": DefaultPrompts не должен оканчиваться переводом строки"); diff --git a/src/core/tests/Deal.Tests.Unit/RatesServiceTests.cs b/src/core/tests/Deal.Tests.Unit/RatesServiceTests.cs index 6333a1a..ef150da 100644 --- a/src/core/tests/Deal.Tests.Unit/RatesServiceTests.cs +++ b/src/core/tests/Deal.Tests.Unit/RatesServiceTests.cs @@ -433,7 +433,11 @@ public sealed class RatesServiceTests // source: Источник кэша (mock|cbr). // extraCode: Дополнительный код валюты (необязательно). // extraRate: Его курс. - private static string CacheJson(long nowMs, string source, string? extraCode = null, double extraRate = 0) + private static string CacheJson( + long nowMs, + string source, + string? extraCode = null, + double extraRate = 0) { var rates = new Dictionary { ["RUB"] = 1.0 }; if (extraCode is not null) diff --git a/src/core/tests/Deal.Tests.Unit/RecordingAiService.cs b/src/core/tests/Deal.Tests.Unit/RecordingAiService.cs index dfa8d16..2fcda82 100644 --- a/src/core/tests/Deal.Tests.Unit/RecordingAiService.cs +++ b/src/core/tests/Deal.Tests.Unit/RecordingAiService.cs @@ -136,8 +136,7 @@ public sealed class RecordingAiService : AiService.AiServiceBase } /// - public override Task GenerateKeywords( - GenerateKeywordsRequest request, ServerCallContext context) + public override Task GenerateKeywords(GenerateKeywordsRequest request, ServerCallContext context) { KeywordsCalls++; RecordMetadata(context); diff --git a/src/core/tests/Deal.Tests.Unit/ServiceHealthProbeTests.cs b/src/core/tests/Deal.Tests.Unit/ServiceHealthProbeTests.cs index ee413cf..41edc10 100644 --- a/src/core/tests/Deal.Tests.Unit/ServiceHealthProbeTests.cs +++ b/src/core/tests/Deal.Tests.Unit/ServiceHealthProbeTests.cs @@ -89,9 +89,7 @@ public sealed class ServiceHealthProbeTests // Поднимает in-process gRPC-health-сервер с заданной проверкой "ready" и гоняет сценарий. // check: Проверка "ready" (async: Healthy/Unhealthy; задержка — сценарий дедлайна). // scenario: Сценарий: получает порт хоста и пробу. - private static async Task RunWithHealthServerAsync( - Func> check, - Func scenario) + private static async Task RunWithHealthServerAsync(Func> check, Func scenario) { WebApplication? app = null; try diff --git a/src/core/tests/Deal.Tests.Unit/StorageTickSchedulerTests.cs b/src/core/tests/Deal.Tests.Unit/StorageTickSchedulerTests.cs index 5ab2f4f..0e1b434 100644 --- a/src/core/tests/Deal.Tests.Unit/StorageTickSchedulerTests.cs +++ b/src/core/tests/Deal.Tests.Unit/StorageTickSchedulerTests.cs @@ -410,7 +410,11 @@ public sealed class StorageTickSchedulerTests // reminderAtMs: Время напоминания, epoch-ms (прошлое — «выстрелит» на проходе). // stage: Контейнер карточки (по умолчанию hold — её напоминания проверяет тик, Ruling 3). // Возвращает: Карточка как DTO хранилища. - private static CardDto HoldCard(string id, string title, long reminderAtMs, string stage = "hold") + private static CardDto HoldCard( + string id, + string title, + long reminderAtMs, + string stage = "hold") { return new CardDto { @@ -434,7 +438,10 @@ public sealed class StorageTickSchedulerTests } // Карточка в колонке с моментом получения (epoch-ms). - private static CardDto Card(string id, string col, long receivedAtMs) => + private static CardDto Card( + string id, + string col, + long receivedAtMs) => new() { Id = id, Col = col, ReceivedAtMs = receivedAtMs }; // Минимальная запись отсева для сидирования (purge смотрит только RejectedAtMs — 3 суток). @@ -498,7 +505,10 @@ public sealed class StorageTickSchedulerTests throw new NotSupportedException(); /// - public Task UpdateStatusAsync(Guid id, string status, CancellationToken ct) => + public Task UpdateStatusAsync( + Guid id, + string status, + CancellationToken ct) => throw new NotSupportedException(); } @@ -514,7 +524,10 @@ public sealed class StorageTickSchedulerTests throw new NotSupportedException(); /// - public Task SetAsync(string key, string valueJson, CancellationToken ct) => + public Task SetAsync( + string key, + string valueJson, + CancellationToken ct) => throw new NotSupportedException(); /// diff --git a/src/core/tests/Deal.Tests.Unit/StorageTickServiceTests.cs b/src/core/tests/Deal.Tests.Unit/StorageTickServiceTests.cs index a17fe15..2b9290f 100644 --- a/src/core/tests/Deal.Tests.Unit/StorageTickServiceTests.cs +++ b/src/core/tests/Deal.Tests.Unit/StorageTickServiceTests.cs @@ -220,7 +220,10 @@ public sealed class StorageTickServiceTests private static ContainerDto Board(string id) => new() { Id = id, Name = id, Order = 0 }; // Карточка в колонке с моментом получения (epoch-ms). - private static CardDto Card(string id, string col, long receivedAtMs) => + private static CardDto Card( + string id, + string col, + long receivedAtMs) => new() { Id = id, Col = col, ReceivedAtMs = receivedAtMs }; // Epoch-ms момента «now − возраст» (ReceivedAt карточки-кандидата). diff --git a/src/core/tests/Deal.Tests.Unit/SuggestHeuristicsTests.cs b/src/core/tests/Deal.Tests.Unit/SuggestHeuristicsTests.cs index 4bf277a..69df26a 100644 --- a/src/core/tests/Deal.Tests.Unit/SuggestHeuristicsTests.cs +++ b/src/core/tests/Deal.Tests.Unit/SuggestHeuristicsTests.cs @@ -22,7 +22,10 @@ public sealed class SuggestHeuristicsTests // sourceMsg: Исходное сообщение (source_msg). // receivedAtMs: Время получения, epoch-ms. // Возвращает: Карточка для входа PlanColumns. - private static CardDto InboxCard(string id, string sourceMsg, long receivedAtMs = 0) => new() + private static CardDto InboxCard( + string id, + string sourceMsg, + long receivedAtMs = 0) => new() { Id = id, Col = KanbanColumns.Inbox, @@ -325,8 +328,7 @@ public sealed class SuggestHeuristicsTests // Детерминированный снимок планов для сравнения (Word/Name/Note + CardIds по порядку). // plans: Планы колонок. // Возвращает: Список кортежей-снимков (порядок значим). - private static List<(string Word, string Name, string Note, string Cards)> Snapshot( - IReadOnlyList plans) => + private static List<(string Word, string Name, string Note, string Cards)> Snapshot(IReadOnlyList plans) => plans .Select(plan => (plan.Word, plan.Name, plan.Note, string.Join(",", plan.CardIds))) .ToList(); diff --git a/src/core/tests/Deal.Tests.Unit/SuspiciousActivityServiceTests.cs b/src/core/tests/Deal.Tests.Unit/SuspiciousActivityServiceTests.cs index 45596ed..50c2f7c 100644 --- a/src/core/tests/Deal.Tests.Unit/SuspiciousActivityServiceTests.cs +++ b/src/core/tests/Deal.Tests.Unit/SuspiciousActivityServiceTests.cs @@ -157,7 +157,11 @@ public sealed class SuspiciousActivityServiceTests } // Пишет запись «неудачный вход» с заданным временем. - private static void SeedFailed(FakeAuditLogStore store, string ip, string login, int minutesAgo) + private static void SeedFailed( + FakeAuditLogStore store, + string ip, + string login, + int minutesAgo) { store.AppendAsync( new AuditRecordDto( diff --git a/src/core/tests/Deal.Tests.Unit/TelegramIngressServiceTests.cs b/src/core/tests/Deal.Tests.Unit/TelegramIngressServiceTests.cs index 747effa..ee6b68e 100644 --- a/src/core/tests/Deal.Tests.Unit/TelegramIngressServiceTests.cs +++ b/src/core/tests/Deal.Tests.Unit/TelegramIngressServiceTests.cs @@ -379,7 +379,11 @@ public sealed class TelegramIngressServiceTests // request: Запрос PushMessage. // tenantId: Id тенанта в metadata (null — без заголовка tenant-id). // tokenHeader: Значение metadata «service-token» (null — без заголовка). - private static Task PushAsync(GrpcChannel channel, PushMessageRequest request, Guid? tenantId, string? tokenHeader) + private static Task PushAsync( + GrpcChannel channel, + PushMessageRequest request, + Guid? tenantId, + string? tokenHeader) { var client = new IngressService.IngressServiceClient(channel); AsyncUnaryCall call = client.PushMessageAsync( @@ -392,7 +396,10 @@ public sealed class TelegramIngressServiceTests // channel: Канал к хосту. // request: Запрос SyncDialogs. // tenantId: Id тенанта в metadata (null — без заголовка tenant-id). - private static Task SyncAsync(GrpcChannel channel, SyncDialogsRequest request, Guid? tenantId) + private static Task SyncAsync( + GrpcChannel channel, + SyncDialogsRequest request, + Guid? tenantId) { var client = new IngressService.IngressServiceClient(channel); AsyncUnaryCall call = client.SyncDialogsAsync( @@ -476,7 +483,10 @@ public sealed class TelegramIngressServiceTests // settings: KV-хранилище сценария. // phase: Ожидаемая фаза. // connected: Ожидаемый флаг connected. - private static void AssertStatusStored(FakeSettingsStore settings, string phase, bool connected) + private static void AssertStatusStored( + FakeSettingsStore settings, + string phase, + bool connected) { string? json = settings.GetStoredJson(SettingsKeys.TgStatus); Assert.NotNull(json); diff --git a/src/core/tests/Deal.Tests.Unit/TenantLimitStoreTests.cs b/src/core/tests/Deal.Tests.Unit/TenantLimitStoreTests.cs index 5521f7b..f71bf8d 100644 --- a/src/core/tests/Deal.Tests.Unit/TenantLimitStoreTests.cs +++ b/src/core/tests/Deal.Tests.Unit/TenantLimitStoreTests.cs @@ -278,7 +278,10 @@ public sealed class TenantLimitStoreTests } // Заводит строку public.tenants (статус нужен BudgetStateDto). - private static void AddTenant(DealDbContext db, Guid tenantId, string status) + private static void AddTenant( + DealDbContext db, + Guid tenantId, + string status) { db.Tenants.Add(new TenantEntity { @@ -292,8 +295,14 @@ public sealed class TenantLimitStoreTests // Заводит строку tenant_limits с заданными полями (сценарий «уже есть расход/флаги»). private static void AddLimit( - DealDbContext db, Guid tenantId, long budgetTokens, string period, DateTimeOffset periodStart, - long usedTokens, bool warned80 = false, bool notifiedExhausted = false) + DealDbContext db, + Guid tenantId, + long budgetTokens, + string period, + DateTimeOffset periodStart, + long usedTokens, + bool warned80 = false, + bool notifiedExhausted = false) { db.TenantLimits.Add(new TenantLimitEntity { diff --git a/src/core/tests/Deal.Tests.Unit/TenantSchemaMigrationServiceTests.cs b/src/core/tests/Deal.Tests.Unit/TenantSchemaMigrationServiceTests.cs index 11e8829..9b2fb05 100644 --- a/src/core/tests/Deal.Tests.Unit/TenantSchemaMigrationServiceTests.cs +++ b/src/core/tests/Deal.Tests.Unit/TenantSchemaMigrationServiceTests.cs @@ -83,9 +83,7 @@ public sealed class TenantSchemaMigrationServiceTests // repository: Реестр тенантов. // provisioner: Провижинер схем. // Возвращает: Тестируемый сервис. - private static TenantSchemaMigrationService NewService( - ITenantRepository repository, - ITenantProvisioner provisioner) + private static TenantSchemaMigrationService NewService(ITenantRepository repository, ITenantProvisioner provisioner) => new(repository, provisioner, NullLogger.Instance); // Строка реестра тенантов для сценария. diff --git a/src/core/tests/Deal.Tests.Unit/TgStatusServiceTests.cs b/src/core/tests/Deal.Tests.Unit/TgStatusServiceTests.cs index 23dfef4..74c52b7 100644 --- a/src/core/tests/Deal.Tests.Unit/TgStatusServiceTests.cs +++ b/src/core/tests/Deal.Tests.Unit/TgStatusServiceTests.cs @@ -128,13 +128,20 @@ public sealed class TgStatusServiceTests // settings: Глобальное KV-хранилище сценария. // apiId: apiId приложения (5..9 цифр). // apiHash: Открытый api_hash (ядро шифрует при сохранении). - private static void PreloadKeys(FakeGlobalSettingsStore settings, string apiId, string apiHash) + private static void PreloadKeys( + FakeGlobalSettingsStore settings, + string apiId, + string apiHash) { string encrypted = new FakeSecretCipher().Encrypt(apiHash); settings.Preload(GlobalSettingsKeys.TelegramKeys, $"{{\"apiId\":\"{apiId}\",\"apiHash\":\"{encrypted}\"}}"); } // Строка каталога минимально (id/имя/kind + флаг мониторинга). - private static FakeTelegramDialogRow Dialog(string id, string name, string kind, bool Monitor) + private static FakeTelegramDialogRow Dialog( + string id, + string name, + string kind, + bool Monitor) => new(id, name, Handle: string.Empty, kind, Hue: "#666", Monitor, LastText: string.Empty, LastAt: null, Backfilled: false, UpdatedAt: DateTimeOffset.UtcNow); } diff --git a/src/grpc-hosting/Deal.Grpc.Hosting/MtlsCertificates.cs b/src/grpc-hosting/Deal.Grpc.Hosting/MtlsCertificates.cs index 3801f29..9664130 100644 --- a/src/grpc-hosting/Deal.Grpc.Hosting/MtlsCertificates.cs +++ b/src/grpc-hosting/Deal.Grpc.Hosting/MtlsCertificates.cs @@ -85,7 +85,10 @@ public sealed class MtlsCertificates /// Клиентский сертификат из рукопожатия (null — RequireCertificate не выполнен). /// Цепочка стандартной проверки (игнорируется — пересобирается на нашу CA). /// Ошибки стандартной проверки TLS. - public bool ValidateClientCertificate(X509Certificate2? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) + public bool ValidateClientCertificate( + X509Certificate2? certificate, + X509Chain? chain, + SslPolicyErrors sslPolicyErrors) { if (certificate is null) { @@ -129,7 +132,11 @@ public sealed class MtlsCertificates // certificate: Сертификат сервера из рукопожатия. // chain: Цепочка стандартной проверки (игнорируется — пересобирается на нашу CA). // sslPolicyErrors: Ошибки стандартной проверки TLS. - private bool ValidateServerCertificate(object? sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) + private bool ValidateServerCertificate( + object? sender, + X509Certificate? certificate, + X509Chain? chain, + SslPolicyErrors sslPolicyErrors) { if (certificate is null) { @@ -219,7 +226,10 @@ public sealed class MtlsCertificates // configuredPath: Путь из env. // envKey: Env-ключ пути (для сообщения об ошибке). // role: Роль сертификата (для сообщения об ошибке). - private static string RequireExistingFile(string configuredPath, string envKey, string role) + private static string RequireExistingFile( + string configuredPath, + string envKey, + string role) { if (string.IsNullOrWhiteSpace(configuredPath)) { diff --git a/src/grpc-hosting/Deal.Grpc.Hosting/RpcCallLoggingInterceptor.cs b/src/grpc-hosting/Deal.Grpc.Hosting/RpcCallLoggingInterceptor.cs index 4acf150..7ba641b 100644 --- a/src/grpc-hosting/Deal.Grpc.Hosting/RpcCallLoggingInterceptor.cs +++ b/src/grpc-hosting/Deal.Grpc.Hosting/RpcCallLoggingInterceptor.cs @@ -131,7 +131,10 @@ public sealed class RpcCallLoggingInterceptor : Interceptor // context: Контекст вызова (метод). // startedAt: Метка времени старта вызова (Stopwatch.GetTimestamp). // statusCode: Итоговый gRPC-статус; null — успех (OK). - private void LogCall(ServerCallContext context, long startedAt, StatusCode? statusCode) + private void LogCall( + ServerCallContext context, + long startedAt, + StatusCode? statusCode) { long elapsedMs = (long)Stopwatch.GetElapsedTime(startedAt).TotalMilliseconds; _logger.LogInformation( diff --git a/src/ml-service/Deal.Ml.Tests/LearningData.cs b/src/ml-service/Deal.Ml.Tests/LearningData.cs index 783c6a8..91a6c56 100644 --- a/src/ml-service/Deal.Ml.Tests/LearningData.cs +++ b/src/ml-service/Deal.Ml.Tests/LearningData.cs @@ -52,7 +52,10 @@ internal static class LearningData /// Метка класса. /// Текст примера. /// Вес сигнала. - public static LearnItem Signal(string label, string text, double delta) => new(text, label, delta); + public static LearnItem Signal( + string label, + string text, + double delta) => new(text, label, delta); /// /// Пример обучения с дельтой 1.0 (действие пользователя). @@ -88,7 +91,10 @@ internal static class LearningData // target: Список-приёмник. // item: Пример. // count: Число копий. - private static void AddRepeated(List target, LearnItem item, int count) + private static void AddRepeated( + List target, + LearnItem item, + int count) { for (int i = 0; i < count; i++) { diff --git a/src/ml-service/Deal.Ml.Tests/MlServiceHostTests.cs b/src/ml-service/Deal.Ml.Tests/MlServiceHostTests.cs index c0af623..af6bd09 100644 --- a/src/ml-service/Deal.Ml.Tests/MlServiceHostTests.cs +++ b/src/ml-service/Deal.Ml.Tests/MlServiceHostTests.cs @@ -146,7 +146,10 @@ public sealed class MlServiceHostTests // serviceToken: Ожидаемый токен хоста (env DEAL_SERVICE_TOKEN). // tokenHeader: Значение metadata «service-token» запроса либо null (нет заголовка). // expected: Ожидаемый StatusCode ответа. - private static async Task AssertDealRpcRejectedAsync(string? serviceToken, string? tokenHeader, StatusCode expected) + private static async Task AssertDealRpcRejectedAsync( + string? serviceToken, + string? tokenHeader, + StatusCode expected) { await RunHostScenarioAsync( serviceToken, @@ -157,7 +160,10 @@ public sealed class MlServiceHostTests // channel: Канал к хосту ml-service. // tokenHeader: Значение metadata «service-token» либо null (нет заголовка). // expected: Ожидаемый StatusCode. - private static async Task AssertRejectedAsync(GrpcChannel channel, string? tokenHeader, StatusCode expected) + private static async Task AssertRejectedAsync( + GrpcChannel channel, + string? tokenHeader, + StatusCode expected) { var client = new MlService.MlServiceClient(channel); Metadata metadata = new(); diff --git a/src/ml-service/Deal.Ml/MlServiceImpl.cs b/src/ml-service/Deal.Ml/MlServiceImpl.cs index b99ec5b..b1b10ae 100644 --- a/src/ml-service/Deal.Ml/MlServiceImpl.cs +++ b/src/ml-service/Deal.Ml/MlServiceImpl.cs @@ -169,7 +169,10 @@ public sealed class MlServiceImpl : MlService.MlServiceBase // value: Значение поля запроса (в proto строка не бывает null). // maxLength: Допустимый максимум символов. // detail: Текст отказа (detail RPC). - private static void EnsureLengthAtMost(string value, int maxLength, string detail) + private static void EnsureLengthAtMost( + string value, + int maxLength, + string detail) { if (value.Length > maxLength) { diff --git a/src/ml-service/Deal.Ml/Model/OnlineNaiveBayes.cs b/src/ml-service/Deal.Ml/Model/OnlineNaiveBayes.cs index 744f62d..b0b67a0 100644 --- a/src/ml-service/Deal.Ml/Model/OnlineNaiveBayes.cs +++ b/src/ml-service/Deal.Ml/Model/OnlineNaiveBayes.cs @@ -240,7 +240,10 @@ public static class OnlineNaiveBayes // state: Состояние модели. // label: Класс-победитель (не spam). // tokens: Термины текста (в порядке появления). - private static IReadOnlyList MatchTerms(ModelState state, string label, string[] tokens) + private static IReadOnlyList MatchTerms( + ModelState state, + string label, + string[] tokens) { Dictionary? weights = TryGetTerms(state, label); if (weights is null) diff --git a/src/ml-service/Deal.Ml/Model/TenantModel.cs b/src/ml-service/Deal.Ml/Model/TenantModel.cs index 41dfd47..fc8c189 100644 --- a/src/ml-service/Deal.Ml/Model/TenantModel.cs +++ b/src/ml-service/Deal.Ml/Model/TenantModel.cs @@ -133,7 +133,10 @@ public sealed class TenantModel : IDisposable // label: Метка действия пользователя. // text: Текст примера. // delta: Вес сигнала. - private EvalEntry? TrySelfEval(string label, string text, double delta) + private EvalEntry? TrySelfEval( + string label, + string text, + double delta) { if (delta != 1.0 || label.StartsWith(ModelConstants.TypeLabelPrefix, StringComparison.Ordinal) @@ -161,7 +164,10 @@ public sealed class TenantModel : IDisposable // label: Метка класса. // delta: Вес сигнала. // tokens: Термины текста (повторы — повторы инкрементов). - private void ApplyToMemory(string label, double delta, string[] tokens) + private void ApplyToMemory( + string label, + double delta, + string[] tokens) { double newCount = _state.Classes.GetValueOrDefault(label) + delta; if (newCount > 0) diff --git a/src/ml-service/Deal.Ml/Storage/MlDb.cs b/src/ml-service/Deal.Ml/Storage/MlDb.cs index d87e480..c50490e 100644 --- a/src/ml-service/Deal.Ml/Storage/MlDb.cs +++ b/src/ml-service/Deal.Ml/Storage/MlDb.cs @@ -146,9 +146,7 @@ public sealed class MlDb : IDisposable /// /// Применяемые примеры: label, delta и термины текста (в порядке появления). /// Новые строки самооценки (решения до применения батча). - public void ApplyLearnBatch( - IReadOnlyList<(string Label, double Delta, string[] Tokens)> items, - IReadOnlyList newEvalRows) + public void ApplyLearnBatch(IReadOnlyList<(string Label, double Delta, string[] Tokens)> items, IReadOnlyList newEvalRows) { if (items.Count == 0 && newEvalRows.Count == 0) { @@ -197,7 +195,11 @@ public sealed class MlDb : IDisposable // label: Метка класса. // delta: Вес сигнала (знак — учить/разучивать). // tokens: Термины текста (повторы слова — повторы строк, как python-executemany). - private void ApplyExample(SqliteTransaction transaction, string label, double delta, string[] tokens) + private void ApplyExample( + SqliteTransaction transaction, + string label, + double delta, + string[] tokens) { long nowMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); diff --git a/src/telegram-service/Deal.Telegram.Tests/BackfillServiceTests.cs b/src/telegram-service/Deal.Telegram.Tests/BackfillServiceTests.cs index e22256e..b94db0e 100644 --- a/src/telegram-service/Deal.Telegram.Tests/BackfillServiceTests.cs +++ b/src/telegram-service/Deal.Telegram.Tests/BackfillServiceTests.cs @@ -177,13 +177,19 @@ public sealed class BackfillServiceTests // dialogId: Id диалога. // id: Id сообщения. // text: Текст. - private static TelegramMessage Message(string dialogId, int id, string text) + private static TelegramMessage Message( + string dialogId, + int id, + string text) => new(dialogId, id, text, dateMs: 1_700_000_000_000 + id, ChannelName, ChannelHandle); // Служба backfill на пуле, фейк-канале в ядро и фейк-пейсере. // farm: Пул сессий. // ingress: Фейк ингресса. // pacer: Фейк-пейсер (без реальных задержек). - private static BackfillService CreateService(SessionFarm farm, FakeIngress ingress, IBackfillPacer pacer) + private static BackfillService CreateService( + SessionFarm farm, + FakeIngress ingress, + IBackfillPacer pacer) => new(farm, ingress, pacer, NullLogger.Instance); } diff --git a/src/telegram-service/Deal.Telegram.Tests/DialogHueTests.cs b/src/telegram-service/Deal.Telegram.Tests/DialogHueTests.cs index 176beb7..058ab24 100644 --- a/src/telegram-service/Deal.Telegram.Tests/DialogHueTests.cs +++ b/src/telegram-service/Deal.Telegram.Tests/DialogHueTests.cs @@ -18,7 +18,10 @@ public sealed class DialogHueTests [InlineData("-123456", "Уютный чат", "#34d399")] [InlineData("+79990001122", "Иван Петров", "#fb7185")] [InlineData("-100999", "", "#34d399")] - public void Compute_MatchesPythonPrototype(string dialogId, string name, string expectedHue) + public void Compute_MatchesPythonPrototype( + string dialogId, + string name, + string expectedHue) { Assert.Equal(expectedHue, DialogHue.Compute(dialogId, name)); } diff --git a/src/telegram-service/Deal.Telegram.Tests/DiscoveryOpsTests.cs b/src/telegram-service/Deal.Telegram.Tests/DiscoveryOpsTests.cs index 5726517..5e92989 100644 --- a/src/telegram-service/Deal.Telegram.Tests/DiscoveryOpsTests.cs +++ b/src/telegram-service/Deal.Telegram.Tests/DiscoveryOpsTests.cs @@ -280,7 +280,11 @@ public sealed class DiscoveryOpsTests // name: Имя. // username: Username. // kind: Тип канона. - private static TelegramDialog Entry(string id, string name, string username, string kind) + private static TelegramDialog Entry( + string id, + string name, + string username, + string kind) => new(id, name, username, kind, unreadCount: 0, topMessageId: 0); // Служба discovery на пуле с фейк-клиентом и фейк-пейсером. diff --git a/src/telegram-service/Deal.Telegram.Tests/DiscoveryRpcTests.cs b/src/telegram-service/Deal.Telegram.Tests/DiscoveryRpcTests.cs index a1acb37..a4bdd68 100644 --- a/src/telegram-service/Deal.Telegram.Tests/DiscoveryRpcTests.cs +++ b/src/telegram-service/Deal.Telegram.Tests/DiscoveryRpcTests.cs @@ -276,7 +276,10 @@ public sealed class DiscoveryRpcTests // client: Клиент TelegramService. // tenantId: Id тенанта сценария. // phone: Номер телефона. - private static async Task LoginAsync(TelegramService.TelegramServiceClient client, string tenantId = TenantId, string phone = Phone) + private static async Task LoginAsync( + TelegramService.TelegramServiceClient client, + string tenantId = TenantId, + string phone = Phone) { await client.StartPhoneAsync( new StartPhoneRequest { Phone = phone, ApiId = SessionHarness.ApiId, ApiHash = SessionHarness.ApiHash }, @@ -289,7 +292,10 @@ public sealed class DiscoveryRpcTests // client: Клиент TelegramService. // tenantId: Id тенанта. // phase: Ожидаемая фаза ("ready"). - private static async Task WaitForPhaseAsync(TelegramService.TelegramServiceClient client, string tenantId, string phase) + private static async Task WaitForPhaseAsync( + TelegramService.TelegramServiceClient client, + string tenantId, + string phase) { var deadline = DateTime.UtcNow.AddMilliseconds(PollTimeoutMilliseconds); while (DateTime.UtcNow < deadline) diff --git a/src/telegram-service/Deal.Telegram.Tests/FakeSessionClient.cs b/src/telegram-service/Deal.Telegram.Tests/FakeSessionClient.cs index 61a5806..282a373 100644 --- a/src/telegram-service/Deal.Telegram.Tests/FakeSessionClient.cs +++ b/src/telegram-service/Deal.Telegram.Tests/FakeSessionClient.cs @@ -22,7 +22,10 @@ internal sealed class FakeSessionClient : ISessionClient /// api_id приложения. /// api_hash приложения. /// Байты «сохранённой сессии» (для проверки передачи в фабрику). - public FakeSessionClient(int apiId, string apiHash, byte[]? storedSession) + public FakeSessionClient( + int apiId, + string apiHash, + byte[]? storedSession) { ApiId = apiId; ApiHash = apiHash; @@ -274,7 +277,10 @@ internal sealed class FakeSessionClient : ISessionClient } /// - public Task> GetMessagesAsync(string dialogId, int limit, CancellationToken cancellationToken) + public Task> GetMessagesAsync( + string dialogId, + int limit, + CancellationToken cancellationToken) { ReadDialogs.Add(dialogId); if (MessagesError is not null) @@ -353,7 +359,10 @@ internal sealed class FakeSessionClient : ISessionClient public Exception? LeaveError { get; set; } /// - public Task> SearchAsync(string query, int limit, CancellationToken cancellationToken) + public Task> SearchAsync( + string query, + int limit, + CancellationToken cancellationToken) { SearchQueries.Add((query, limit)); if (SearchError is not null) @@ -375,7 +384,10 @@ internal sealed class FakeSessionClient : ISessionClient } /// - public Task ReadForEvalAsync(string dialogId, int limit, CancellationToken cancellationToken) + public Task ReadForEvalAsync( + string dialogId, + int limit, + CancellationToken cancellationToken) { ReadRequests.Add(dialogId); return Task.FromResult( @@ -449,7 +461,10 @@ internal sealed class FakeClientFactory : ITelegramClientFactory public Action? OnClientCreated { get; set; } /// - public ISessionClient Create(int apiId, string apiHash, byte[]? storedSession) + public ISessionClient Create( + int apiId, + string apiHash, + byte[]? storedSession) { var client = new FakeSessionClient(apiId, apiHash, storedSession); OnClientCreated?.Invoke(client); diff --git a/src/telegram-service/Deal.Telegram.Tests/FarmHarness.cs b/src/telegram-service/Deal.Telegram.Tests/FarmHarness.cs index 57fd951..15c4598 100644 --- a/src/telegram-service/Deal.Telegram.Tests/FarmHarness.cs +++ b/src/telegram-service/Deal.Telegram.Tests/FarmHarness.cs @@ -11,7 +11,11 @@ internal sealed class FarmHarness : IAsyncDisposable { private const string Phone = "+79990001122"; - private FarmHarness(string directory, SessionStore store, FakeClientFactory factory, SessionFarm farm) + private FarmHarness( + string directory, + SessionStore store, + FakeClientFactory factory, + SessionFarm farm) { Directory = directory; Store = store; diff --git a/src/telegram-service/Deal.Telegram.Tests/RealtimeListenerTests.cs b/src/telegram-service/Deal.Telegram.Tests/RealtimeListenerTests.cs index f9ba5d3..9fe6915 100644 --- a/src/telegram-service/Deal.Telegram.Tests/RealtimeListenerTests.cs +++ b/src/telegram-service/Deal.Telegram.Tests/RealtimeListenerTests.cs @@ -133,6 +133,9 @@ public sealed class RealtimeListenerTests // session: Ready-сессия тенанта. // catalog: Зеркало мониторинга. // ingress: Фейк ингресса. - private static RealtimeListener CreateListener(TenantSession session, DialogCatalog catalog, FakeIngress ingress) + private static RealtimeListener CreateListener( + TenantSession session, + DialogCatalog catalog, + FakeIngress ingress) => new(session, catalog, ingress, NullLogger.Instance); } diff --git a/src/telegram-service/Deal.Telegram.Tests/RealtimeSweepTests.cs b/src/telegram-service/Deal.Telegram.Tests/RealtimeSweepTests.cs index dbbeaf9..25bc56f 100644 --- a/src/telegram-service/Deal.Telegram.Tests/RealtimeSweepTests.cs +++ b/src/telegram-service/Deal.Telegram.Tests/RealtimeSweepTests.cs @@ -143,6 +143,9 @@ public sealed class RealtimeSweepTests // farm: Пул сессий. // catalog: Зеркало каталога. // ingress: Фейк ингресса. - private static RealtimeSweep CreateSweep(SessionFarm farm, DialogCatalog catalog, FakeIngress ingress) + private static RealtimeSweep CreateSweep( + SessionFarm farm, + DialogCatalog catalog, + FakeIngress ingress) => new(farm, catalog, ingress, NullLogger.Instance); } diff --git a/src/telegram-service/Deal.Telegram.Tests/SessionHarness.cs b/src/telegram-service/Deal.Telegram.Tests/SessionHarness.cs index 8a8b68b..2730d75 100644 --- a/src/telegram-service/Deal.Telegram.Tests/SessionHarness.cs +++ b/src/telegram-service/Deal.Telegram.Tests/SessionHarness.cs @@ -18,7 +18,11 @@ internal sealed class SessionHarness : IAsyncDisposable /// public const string ApiHash = "0123456789abcdef0123456789abcdef"; - private SessionHarness(string directory, SessionStore store, FakeClientFactory factory, TenantSession session) + private SessionHarness( + string directory, + SessionStore store, + FakeClientFactory factory, + TenantSession session) { Directory = directory; Store = store; diff --git a/src/telegram-service/Deal.Telegram.Tests/TelegramServiceHostTests.cs b/src/telegram-service/Deal.Telegram.Tests/TelegramServiceHostTests.cs index 977190c..dc2ef7c 100644 --- a/src/telegram-service/Deal.Telegram.Tests/TelegramServiceHostTests.cs +++ b/src/telegram-service/Deal.Telegram.Tests/TelegramServiceHostTests.cs @@ -130,7 +130,10 @@ public sealed class TelegramServiceHostTests // serviceToken: Ожидаемый токен хоста (env DEAL_SERVICE_TOKEN). // tokenHeader: Значение metadata «service-token» запроса либо null (нет заголовка). // expected: Ожидаемый StatusCode ответа. - private static async Task AssertDealRpcRejectedAsync(string? serviceToken, string? tokenHeader, StatusCode expected) + private static async Task AssertDealRpcRejectedAsync( + string? serviceToken, + string? tokenHeader, + StatusCode expected) { await RunScenarioAsync(serviceToken, channel => AssertRejectedAsync(channel, tokenHeader, expected)); } @@ -139,7 +142,10 @@ public sealed class TelegramServiceHostTests // channel: Канал к хосту telegram-service. // tokenHeader: Значение metadata «service-token» либо null (нет заголовка). // expected: Ожидаемый StatusCode. - private static async Task AssertRejectedAsync(GrpcChannel channel, string? tokenHeader, StatusCode expected) + private static async Task AssertRejectedAsync( + GrpcChannel channel, + string? tokenHeader, + StatusCode expected) { var client = new TelegramService.TelegramServiceClient(channel); AsyncUnaryCall call = client.GetStatusAsync( diff --git a/src/telegram-service/Deal.Telegram.Tests/TenantSessionTests.cs b/src/telegram-service/Deal.Telegram.Tests/TenantSessionTests.cs index d8f20f8..b6a896d 100644 --- a/src/telegram-service/Deal.Telegram.Tests/TenantSessionTests.cs +++ b/src/telegram-service/Deal.Telegram.Tests/TenantSessionTests.cs @@ -474,7 +474,11 @@ public sealed class TenantSessionTests { private readonly FakeClientFactory _factory; - private SessionContext(string directory, SessionStore store, TenantSession session, FakeClientFactory factory) + private SessionContext( + string directory, + SessionStore store, + TenantSession session, + FakeClientFactory factory) { Directory = directory; Store = store; diff --git a/src/telegram-service/Deal.Telegram.Tests/TestDoubles.cs b/src/telegram-service/Deal.Telegram.Tests/TestDoubles.cs index 4a5dfeb..986f686 100644 --- a/src/telegram-service/Deal.Telegram.Tests/TestDoubles.cs +++ b/src/telegram-service/Deal.Telegram.Tests/TestDoubles.cs @@ -47,7 +47,10 @@ internal sealed class FakeIngress : ICoreIngressClient public string? LastPushDialogId => Pushes.Count == 0 ? null : Pushes[^1].Message.DialogId; /// - public Task PushMessageAsync(string tenantId, PushMessageRequest message, CancellationToken cancellationToken) + public Task PushMessageAsync( + string tenantId, + PushMessageRequest message, + CancellationToken cancellationToken) { if (PushError is not null) { @@ -59,7 +62,10 @@ internal sealed class FakeIngress : ICoreIngressClient } /// - public Task> SyncDialogsAsync(string tenantId, IReadOnlyList entries, CancellationToken cancellationToken) + public Task> SyncDialogsAsync( + string tenantId, + IReadOnlyList entries, + CancellationToken cancellationToken) { if (SyncError is not null) { @@ -81,7 +87,10 @@ internal sealed class RecordingPacer : IBackfillPacer public List<(double MinSeconds, double MaxSeconds)> Waits { get; } = []; /// - public Task WaitAsync(double minSeconds, double maxSeconds, CancellationToken cancellationToken) + public Task WaitAsync( + double minSeconds, + double maxSeconds, + CancellationToken cancellationToken) { Waits.Add((minSeconds, maxSeconds)); return Task.CompletedTask; diff --git a/src/telegram-service/Deal.Telegram.Tests/TlMessageMapperTests.cs b/src/telegram-service/Deal.Telegram.Tests/TlMessageMapperTests.cs index ecb98b4..0da9617 100644 --- a/src/telegram-service/Deal.Telegram.Tests/TlMessageMapperTests.cs +++ b/src/telegram-service/Deal.Telegram.Tests/TlMessageMapperTests.cs @@ -173,7 +173,10 @@ public sealed class TlMessageMapperTests // id: Id сообщения. // text: Текст. // flags: Флаги сообщения (по умолчанию входящее). - private static Message ChannelMessage(int id, string text, Message.Flags flags = 0) + private static Message ChannelMessage( + int id, + string text, + Message.Flags flags = 0) => new() { id = id, diff --git a/src/telegram-service/Deal.Telegram/Core/CoreIngressClient.cs b/src/telegram-service/Deal.Telegram/Core/CoreIngressClient.cs index 460d686..3f150fa 100644 --- a/src/telegram-service/Deal.Telegram/Core/CoreIngressClient.cs +++ b/src/telegram-service/Deal.Telegram/Core/CoreIngressClient.cs @@ -39,7 +39,10 @@ public sealed class CoreIngressClient : ICoreIngressClient /// Конфигурация (адрес из env, service-token). /// Логгер. /// Сертификаты mTLS (Ruling 6, Task 13): null — plaintext-канал (dev). - public CoreIngressClient(CoreIngressOptions options, ILogger logger, MtlsCertificates? mtlsCertificates = null) + public CoreIngressClient( + CoreIngressOptions options, + ILogger logger, + MtlsCertificates? mtlsCertificates = null) { _options = options; _logger = logger; @@ -47,7 +50,10 @@ public sealed class CoreIngressClient : ICoreIngressClient } /// - public async Task PushMessageAsync(string tenantId, PushMessageRequest message, CancellationToken cancellationToken) + public async Task PushMessageAsync( + string tenantId, + PushMessageRequest message, + CancellationToken cancellationToken) { IngressService.IngressServiceClient client = GetClient(); try @@ -61,7 +67,10 @@ public sealed class CoreIngressClient : ICoreIngressClient } /// - public async Task> SyncDialogsAsync(string tenantId, IReadOnlyList entries, CancellationToken cancellationToken) + public async Task> SyncDialogsAsync( + string tenantId, + IReadOnlyList entries, + CancellationToken cancellationToken) { IngressService.IngressServiceClient client = GetClient(); var request = new SyncDialogsRequest(); diff --git a/src/telegram-service/Deal.Telegram/Core/ICoreIngressClient.cs b/src/telegram-service/Deal.Telegram/Core/ICoreIngressClient.cs index ac894c0..66f76db 100644 --- a/src/telegram-service/Deal.Telegram/Core/ICoreIngressClient.cs +++ b/src/telegram-service/Deal.Telegram/Core/ICoreIngressClient.cs @@ -21,7 +21,10 @@ public interface ICoreIngressClient /// Сообщение в контракте PushMessageRequest. /// Отмена вызова. /// Ответ ядра (accepted/duplicate — дубль dialog+msgId в очереди не растёт). - public Task PushMessageAsync(string tenantId, PushMessageRequest message, CancellationToken cancellationToken); + public Task PushMessageAsync( + string tenantId, + PushMessageRequest message, + CancellationToken cancellationToken); /// /// Синхронизирует каталог с ядром (Ingress.SyncDialogs → SyncFromTelegram, Ruling 7). Ядро применяет @@ -32,5 +35,8 @@ public interface ICoreIngressClient /// Актуальный каталог диалогов (как refresh_dialogs). /// Отмена вызова. /// Список id диалогов с включённым мониторингом (по версии ядра). - public Task> SyncDialogsAsync(string tenantId, IReadOnlyList entries, CancellationToken cancellationToken); + public Task> SyncDialogsAsync( + string tenantId, + IReadOnlyList entries, + CancellationToken cancellationToken); } diff --git a/src/telegram-service/Deal.Telegram/Dialogs/BackfillService.cs b/src/telegram-service/Deal.Telegram/Dialogs/BackfillService.cs index 2a01a34..a33fc2f 100644 --- a/src/telegram-service/Deal.Telegram/Dialogs/BackfillService.cs +++ b/src/telegram-service/Deal.Telegram/Dialogs/BackfillService.cs @@ -89,7 +89,11 @@ public sealed class BackfillService /// True — «Перечитать» по кнопке (семантика флага — в ядре, см. класс). /// Отмена операции. /// Сколько сообщений отправлено в ядро (0 — диалог уже перечитывается/нет текстов). - public async Task ExecuteAsync(string tenantId, string dialogId, bool force, CancellationToken cancellationToken) + public async Task ExecuteAsync( + string tenantId, + string dialogId, + bool force, + CancellationToken cancellationToken) { var key = (tenantId, dialogId); if (!_running.TryAdd(key, 0)) @@ -168,7 +172,10 @@ public sealed class BackfillService // dialogId: Подписанный id диалога. // cancellationToken: Отмена операции. // Возвращает: Сколько сообщений отправлено в ядро. - private async Task BackfillDialogAsync(string tenantId, string dialogId, CancellationToken cancellationToken) + private async Task BackfillDialogAsync( + string tenantId, + string dialogId, + CancellationToken cancellationToken) { IReadOnlyList messages = await _sessionFarm .GetMessagesAsync(tenantId, dialogId, MessagesLimit, cancellationToken) diff --git a/src/telegram-service/Deal.Telegram/Dialogs/DialogCatalog.cs b/src/telegram-service/Deal.Telegram/Dialogs/DialogCatalog.cs index aa7759b..1b6ed35 100644 --- a/src/telegram-service/Deal.Telegram/Dialogs/DialogCatalog.cs +++ b/src/telegram-service/Deal.Telegram/Dialogs/DialogCatalog.cs @@ -43,7 +43,10 @@ public sealed class DialogCatalog /// Id тенанта. /// Id диалога. /// Мониторить (true) или выключить (false). - public void SetMonitored(string tenantId, string dialogId, bool enabled) + public void SetMonitored( + string tenantId, + string dialogId, + bool enabled) { ArgumentException.ThrowIfNullOrWhiteSpace(tenantId); ArgumentException.ThrowIfNullOrWhiteSpace(dialogId); diff --git a/src/telegram-service/Deal.Telegram/Dialogs/IBackfillPacer.cs b/src/telegram-service/Deal.Telegram/Dialogs/IBackfillPacer.cs index 43a0733..a79aaa3 100644 --- a/src/telegram-service/Deal.Telegram/Dialogs/IBackfillPacer.cs +++ b/src/telegram-service/Deal.Telegram/Dialogs/IBackfillPacer.cs @@ -17,5 +17,8 @@ public interface IBackfillPacer /// Верхняя граница паузы, секунды (≥ minSeconds). /// Отмена ожидания. /// Задача ожидания. - public Task WaitAsync(double minSeconds, double maxSeconds, CancellationToken cancellationToken); + public Task WaitAsync( + double minSeconds, + double maxSeconds, + CancellationToken cancellationToken); } diff --git a/src/telegram-service/Deal.Telegram/Dialogs/RandomBackfillPacer.cs b/src/telegram-service/Deal.Telegram/Dialogs/RandomBackfillPacer.cs index 4068166..5f23d87 100644 --- a/src/telegram-service/Deal.Telegram/Dialogs/RandomBackfillPacer.cs +++ b/src/telegram-service/Deal.Telegram/Dialogs/RandomBackfillPacer.cs @@ -7,7 +7,10 @@ namespace Deal.Telegram.Dialogs; public sealed class RandomBackfillPacer : IBackfillPacer { /// - public async Task WaitAsync(double minSeconds, double maxSeconds, CancellationToken cancellationToken) + public async Task WaitAsync( + double minSeconds, + double maxSeconds, + CancellationToken cancellationToken) { if (maxSeconds < 0 || minSeconds < 0 || minSeconds > maxSeconds) { diff --git a/src/telegram-service/Deal.Telegram/Dialogs/RealtimeSweep.cs b/src/telegram-service/Deal.Telegram/Dialogs/RealtimeSweep.cs index b9406fa..883a14a 100644 --- a/src/telegram-service/Deal.Telegram/Dialogs/RealtimeSweep.cs +++ b/src/telegram-service/Deal.Telegram/Dialogs/RealtimeSweep.cs @@ -47,7 +47,11 @@ public sealed class RealtimeSweep /// Зеркало каталога/мониторинга (актуализируется ответом SyncDialogs). /// Канал в ядро (PushMessage/SyncDialogs). /// Логгер. - public RealtimeSweep(SessionFarm sessionFarm, DialogCatalog catalog, ICoreIngressClient ingress, ILogger logger) + public RealtimeSweep( + SessionFarm sessionFarm, + DialogCatalog catalog, + ICoreIngressClient ingress, + ILogger logger) { _sessionFarm = sessionFarm; _catalog = catalog; @@ -136,7 +140,10 @@ public sealed class RealtimeSweep // tenantId: Id тенанта. // dialog: Диалог с unread_count > 0. // cancellationToken: Отмена операции. - private async Task SweepDialogAsync(string tenantId, TelegramDialog dialog, CancellationToken cancellationToken) + private async Task SweepDialogAsync( + string tenantId, + TelegramDialog dialog, + CancellationToken cancellationToken) { try { diff --git a/src/telegram-service/Deal.Telegram/Discovery/DiscoveryOps.cs b/src/telegram-service/Deal.Telegram/Discovery/DiscoveryOps.cs index 9b386ed..a482b71 100644 --- a/src/telegram-service/Deal.Telegram/Discovery/DiscoveryOps.cs +++ b/src/telegram-service/Deal.Telegram/Discovery/DiscoveryOps.cs @@ -48,7 +48,10 @@ public sealed class DiscoveryOps /// Пул сессий тенантов (операции только на сессии своего тенанта). /// Анти-бан-паузы (реальный — случайные 2–4 с, тесты — фейк). /// Логгер. - public DiscoveryOps(SessionFarm sessionFarm, IBackfillPacer pacer, ILogger logger) + public DiscoveryOps( + SessionFarm sessionFarm, + IBackfillPacer pacer, + ILogger logger) { _sessionFarm = sessionFarm; _pacer = pacer; @@ -65,7 +68,11 @@ public sealed class DiscoveryOps /// Верхняя граница результата (≤ 0 — прототип-дефолт 30). /// Отмена операции. /// Найденные источники (каналы/группы/личные) без дублей, не длиннее limit. - public async Task> SearchAsync(string tenantId, string query, int limit, CancellationToken cancellationToken) + public async Task> SearchAsync( + string tenantId, + string query, + int limit, + CancellationToken cancellationToken) { int effectiveLimit = limit > 0 ? limit : SearchDefaultLimit; IReadOnlyList found = await _sessionFarm @@ -84,7 +91,10 @@ public sealed class DiscoveryOps /// Подписанный id источника («-100…»/«-…»/«+…»). /// Отмена операции. /// Инфо об источнике (сбои определения не бросаются — см. ISessionClient.GetInfoAsync). - public Task GetInfoAsync(string tenantId, string dialogId, CancellationToken cancellationToken) + public Task GetInfoAsync( + string tenantId, + string dialogId, + CancellationToken cancellationToken) => _sessionFarm.GetInfoAsync(tenantId, dialogId, cancellationToken); /// @@ -95,7 +105,11 @@ public sealed class DiscoveryOps /// Размер выборки (≤ 0 — дефолт; форумы читаются по активным темам). /// Отмена операции. /// ok/сообщения либо ok=false/error="no_history" (не ошибка RPC). - public Task ReadForEvalAsync(string tenantId, string dialogId, int limit, CancellationToken cancellationToken) + public Task ReadForEvalAsync( + string tenantId, + string dialogId, + int limit, + CancellationToken cancellationToken) => _sessionFarm.ReadForEvalAsync(tenantId, dialogId, limit > 0 ? limit : ReadForEvalDefaultLimit, cancellationToken); /// @@ -106,7 +120,10 @@ public sealed class DiscoveryOps /// Id тенанта. /// Username источника («@»/пробелы нормализуются). /// Отмена операции. - public async Task JoinAsync(string tenantId, string username, CancellationToken cancellationToken) + public async Task JoinAsync( + string tenantId, + string username, + CancellationToken cancellationToken) { string normalized = NormalizeUsername(username); if (normalized.Length == 0) @@ -124,7 +141,10 @@ public sealed class DiscoveryOps /// Id тенанта. /// Подписанный id диалога. /// Отмена операции. - public Task LeaveAsync(string tenantId, string dialogId, CancellationToken cancellationToken) + public Task LeaveAsync( + string tenantId, + string dialogId, + CancellationToken cancellationToken) => _sessionFarm.LeaveAsync(tenantId, dialogId, cancellationToken); /// diff --git a/src/telegram-service/Deal.Telegram/Sessions/SessionException.cs b/src/telegram-service/Deal.Telegram/Sessions/SessionException.cs index 755abdf..71bfddf 100644 --- a/src/telegram-service/Deal.Telegram/Sessions/SessionException.cs +++ b/src/telegram-service/Deal.Telegram/Sessions/SessionException.cs @@ -18,7 +18,10 @@ public sealed class SessionException : Exception /// gRPC-статус ошибки (контракт telegram.proto, шапка файла). /// Текст причины — detail RPC (1:1 с текстами прототипа). /// Внутренняя причина (исключение Telegram/адаптера), если есть. - public SessionException(StatusCode code, string message, Exception? innerException = null) + public SessionException( + StatusCode code, + string message, + Exception? innerException = null) : base(message, innerException) { Code = code; diff --git a/src/telegram-service/Deal.Telegram/Sessions/SessionFarm.cs b/src/telegram-service/Deal.Telegram/Sessions/SessionFarm.cs index 5be5caa..c2586e3 100644 --- a/src/telegram-service/Deal.Telegram/Sessions/SessionFarm.cs +++ b/src/telegram-service/Deal.Telegram/Sessions/SessionFarm.cs @@ -56,7 +56,10 @@ public sealed class SessionFarm /// Id тенанта. /// Верхняя граница числа диалогов. /// Отмена операции. - public Task> ListDialogsAsync(string tenantId, int limit, CancellationToken cancellationToken) + public Task> ListDialogsAsync( + string tenantId, + int limit, + CancellationToken cancellationToken) => RequireSession(tenantId).ListDialogsAsync(limit, cancellationToken); /// @@ -66,7 +69,11 @@ public sealed class SessionFarm /// Подписанный id диалога. /// Сколько последних сообщений запросить. /// Отмена операции. - public Task> GetMessagesAsync(string tenantId, string dialogId, int limit, CancellationToken cancellationToken) + public Task> GetMessagesAsync( + string tenantId, + string dialogId, + int limit, + CancellationToken cancellationToken) => RequireSession(tenantId).GetMessagesAsync(dialogId, limit, cancellationToken); /// @@ -75,7 +82,10 @@ public sealed class SessionFarm /// Id тенанта. /// Подписанный id диалога. /// Отмена операции. - public Task MarkReadAsync(string tenantId, string dialogId, CancellationToken cancellationToken) + public Task MarkReadAsync( + string tenantId, + string dialogId, + CancellationToken cancellationToken) => RequireSession(tenantId).MarkReadAsync(dialogId, cancellationToken); /// @@ -85,7 +95,11 @@ public sealed class SessionFarm /// Поисковый запрос (ключ задачи discovery). /// Верхняя граница результата. /// Отмена операции. - public Task> SearchAsync(string tenantId, string query, int limit, CancellationToken cancellationToken) + public Task> SearchAsync( + string tenantId, + string query, + int limit, + CancellationToken cancellationToken) => RequireSession(tenantId).SearchAsync(query, limit, cancellationToken); /// @@ -94,7 +108,10 @@ public sealed class SessionFarm /// Id тенанта. /// Подписанный id источника. /// Отмена операции. - public Task GetInfoAsync(string tenantId, string dialogId, CancellationToken cancellationToken) + public Task GetInfoAsync( + string tenantId, + string dialogId, + CancellationToken cancellationToken) => RequireSession(tenantId).GetInfoAsync(dialogId, cancellationToken); /// @@ -104,7 +121,11 @@ public sealed class SessionFarm /// Подписанный id источника. /// Размер выборки. /// Отмена операции. - public Task ReadForEvalAsync(string tenantId, string dialogId, int limit, CancellationToken cancellationToken) + public Task ReadForEvalAsync( + string tenantId, + string dialogId, + int limit, + CancellationToken cancellationToken) => RequireSession(tenantId).ReadForEvalAsync(dialogId, limit, cancellationToken); /// @@ -113,7 +134,10 @@ public sealed class SessionFarm /// Id тенанта. /// Username (без «@»; нормализует DiscoveryOps). /// Отмена операции. - public Task JoinAsync(string tenantId, string username, CancellationToken cancellationToken) + public Task JoinAsync( + string tenantId, + string username, + CancellationToken cancellationToken) => RequireSession(tenantId).JoinAsync(username, cancellationToken); /// @@ -122,7 +146,10 @@ public sealed class SessionFarm /// Id тенанта. /// Подписанный id диалога. /// Отмена операции. - public Task LeaveAsync(string tenantId, string dialogId, CancellationToken cancellationToken) + public Task LeaveAsync( + string tenantId, + string dialogId, + CancellationToken cancellationToken) => RequireSession(tenantId).LeaveAsync(dialogId, cancellationToken); /// @@ -133,7 +160,12 @@ public sealed class SessionFarm /// api_hash приложения. /// Номер телефона. /// Отмена операции. - public Task StartPhoneAsync(string tenantId, int apiId, string apiHash, string phone, CancellationToken cancellationToken) + public Task StartPhoneAsync( + string tenantId, + int apiId, + string apiHash, + string phone, + CancellationToken cancellationToken) => GetOrCreate(tenantId).StartPhoneAsync(apiId, apiHash, phone, cancellationToken); /// @@ -143,7 +175,11 @@ public sealed class SessionFarm /// api_id приложения. /// api_hash приложения. /// Отмена операции. - public Task StartQrAsync(string tenantId, int apiId, string apiHash, CancellationToken cancellationToken) + public Task StartQrAsync( + string tenantId, + int apiId, + string apiHash, + CancellationToken cancellationToken) => GetOrCreate(tenantId).StartQrAsync(apiId, apiHash, cancellationToken); /// @@ -152,7 +188,10 @@ public sealed class SessionFarm /// Id тенанта. /// Код. /// Отмена операции. - public Task SendCodeAsync(string tenantId, string code, CancellationToken cancellationToken) + public Task SendCodeAsync( + string tenantId, + string code, + CancellationToken cancellationToken) => RequireSession(tenantId).SendCodeAsync(code, cancellationToken); /// @@ -161,7 +200,10 @@ public sealed class SessionFarm /// Id тенанта. /// Пароль. /// Отмена операции. - public Task SendPasswordAsync(string tenantId, string password, CancellationToken cancellationToken) + public Task SendPasswordAsync( + string tenantId, + string password, + CancellationToken cancellationToken) => RequireSession(tenantId).SendPasswordAsync(password, cancellationToken); /// diff --git a/src/telegram-service/Deal.Telegram/Sessions/SessionStore.cs b/src/telegram-service/Deal.Telegram/Sessions/SessionStore.cs index 336c310..e0375c7 100644 --- a/src/telegram-service/Deal.Telegram/Sessions/SessionStore.cs +++ b/src/telegram-service/Deal.Telegram/Sessions/SessionStore.cs @@ -32,7 +32,10 @@ public sealed class SessionStore /// Опции хранения сессий (каталог, ключ шифрования). /// AES-GCM-обёртка файлов сессий. /// Логгер. - public SessionStore(TgOptions options, SessionFileCipher cipher, ILogger logger) + public SessionStore( + TgOptions options, + SessionFileCipher cipher, + ILogger logger) { _sessionsDirectory = options.SessionsDirectory; _cipher = cipher; @@ -101,7 +104,10 @@ public sealed class SessionStore /// Id тенанта. /// Открытое содержимое сессии для шифрования at-rest. /// Отмена операции. - public async Task SaveAsync(string tenantId, StoredSession session, CancellationToken cancellationToken = default) + public async Task SaveAsync( + string tenantId, + StoredSession session, + CancellationToken cancellationToken = default) { string filePath = PathFor(tenantId); byte[] plainBytes = JsonSerializer.SerializeToUtf8Bytes(session); diff --git a/src/telegram-service/Deal.Telegram/Sessions/TenantSession.cs b/src/telegram-service/Deal.Telegram/Sessions/TenantSession.cs index 602704e..5fc5337 100644 --- a/src/telegram-service/Deal.Telegram/Sessions/TenantSession.cs +++ b/src/telegram-service/Deal.Telegram/Sessions/TenantSession.cs @@ -90,7 +90,11 @@ public sealed class TenantSession : IAsyncDisposable /// Отмена операции. /// Снимок состояния после операции (фаза "code"). /// Нет ключей (INVALID_ARGUMENT) / ошибки Telegram. - public async Task StartPhoneAsync(int apiId, string apiHash, string phone, CancellationToken cancellationToken) + public async Task StartPhoneAsync( + int apiId, + string apiHash, + string phone, + CancellationToken cancellationToken) { await _gate.WaitAsync(cancellationToken).ConfigureAwait(false); try @@ -148,7 +152,10 @@ public sealed class TenantSession : IAsyncDisposable /// Отмена операции. /// Снимок состояния: Qr с qrUrl или Ready (авторизация уже была). /// Нет ключей (INVALID_ARGUMENT) / ошибки Telegram до первого URL. - public async Task StartQrAsync(int apiId, string apiHash, CancellationToken cancellationToken) + public async Task StartQrAsync( + int apiId, + string apiHash, + CancellationToken cancellationToken) { await _gate.WaitAsync(cancellationToken).ConfigureAwait(false); try @@ -410,7 +417,10 @@ public sealed class TenantSession : IAsyncDisposable /// Сколько последних сообщений запросить. /// Отмена операции. /// Сообщения диалога (от новых к старым, непустые тексты). - public async Task> GetMessagesAsync(string dialogId, int limit, CancellationToken cancellationToken) + public async Task> GetMessagesAsync( + string dialogId, + int limit, + CancellationToken cancellationToken) { await _gate.WaitAsync(cancellationToken).ConfigureAwait(false); try @@ -452,7 +462,10 @@ public sealed class TenantSession : IAsyncDisposable /// Верхняя граница результата. /// Отмена операции. /// Найденные источники (нейтральный вид; личные чаты отсеивает ядро, Ruling 10). - public async Task> SearchAsync(string query, int limit, CancellationToken cancellationToken) + public async Task> SearchAsync( + string query, + int limit, + CancellationToken cancellationToken) { await _gate.WaitAsync(cancellationToken).ConfigureAwait(false); try @@ -493,7 +506,10 @@ public sealed class TenantSession : IAsyncDisposable /// Размер выборки (limit ≤ 0 — пусто без сети). /// Отмена операции. /// Результат чтения (ok/messages либо no_history). - public async Task ReadForEvalAsync(string dialogId, int limit, CancellationToken cancellationToken) + public async Task ReadForEvalAsync( + string dialogId, + int limit, + CancellationToken cancellationToken) { await _gate.WaitAsync(cancellationToken).ConfigureAwait(false); try @@ -812,7 +828,10 @@ public sealed class TenantSession : IAsyncDisposable // apiId: api_id приложения. // apiHash: api_hash приложения. // cancellationToken: Отмена операции. - private async Task EnsureClientAsync(int apiId, string apiHash, CancellationToken cancellationToken) + private async Task EnsureClientAsync( + int apiId, + string apiHash, + CancellationToken cancellationToken) { if (_client is not null && _clientApiId == apiId && _clientApiHash == apiHash) { @@ -988,7 +1007,10 @@ public sealed class TenantSession : IAsyncDisposable // firstUrlTcs: TCS первого URL. // cancellationToken: Отмена операции. // Возвращает: Первый URL либо null (задача завершилась без URL). - private static async Task WaitForFirstQrUrlAsync(Task qrTask, TaskCompletionSource firstUrlTcs, CancellationToken cancellationToken) + private static async Task WaitForFirstQrUrlAsync( + Task qrTask, + TaskCompletionSource firstUrlTcs, + CancellationToken cancellationToken) { Task urlTask = firstUrlTcs.Task; Task completed = await Task.WhenAny(urlTask, qrTask).WaitAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/telegram-service/Deal.Telegram/Telegram/ClientFactory.cs b/src/telegram-service/Deal.Telegram/Telegram/ClientFactory.cs index b21f916..dcf278c 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/ClientFactory.cs +++ b/src/telegram-service/Deal.Telegram/Telegram/ClientFactory.cs @@ -12,6 +12,9 @@ namespace Deal.Telegram.Telegram; public sealed class ClientFactory : ITelegramClientFactory { /// - public ISessionClient Create(int apiId, string apiHash, byte[]? storedSession) + public ISessionClient Create( + int apiId, + string apiHash, + byte[]? storedSession) => new WTelegramSessionClient(apiId, apiHash, storedSession); } diff --git a/src/telegram-service/Deal.Telegram/Telegram/DiscoveryMessage.cs b/src/telegram-service/Deal.Telegram/Telegram/DiscoveryMessage.cs index 12928a5..848a413 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/DiscoveryMessage.cs +++ b/src/telegram-service/Deal.Telegram/Telegram/DiscoveryMessage.cs @@ -18,7 +18,12 @@ public sealed record DiscoveryMessage /// Время сообщения, epoch-ms. /// Id темы форума (для обычных источников пуст). /// Название темы форума (для обычных источников пусто). - public DiscoveryMessage(long id, string text, long dateMs, long? topicId, string? topicTitle) + public DiscoveryMessage( + long id, + string text, + long dateMs, + long? topicId, + string? topicTitle) { Id = id; Text = text; diff --git a/src/telegram-service/Deal.Telegram/Telegram/DiscoveryReadResult.cs b/src/telegram-service/Deal.Telegram/Telegram/DiscoveryReadResult.cs index f29891e..4f024be 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/DiscoveryReadResult.cs +++ b/src/telegram-service/Deal.Telegram/Telegram/DiscoveryReadResult.cs @@ -25,7 +25,10 @@ public sealed record DiscoveryReadResult /// True — выборка получена; false — история недоступна. /// Код причины при ok=false ("no_history"); иначе null. /// Сообщения выборки (форумы — по темам, topic_id/topic_title заполнены). - public DiscoveryReadResult(bool ok, string? error, IReadOnlyList messages) + public DiscoveryReadResult( + bool ok, + string? error, + IReadOnlyList messages) { Ok = ok; Error = error; diff --git a/src/telegram-service/Deal.Telegram/Telegram/ISessionClient.cs b/src/telegram-service/Deal.Telegram/Telegram/ISessionClient.cs index d4f8b68..f7be887 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/ISessionClient.cs +++ b/src/telegram-service/Deal.Telegram/Telegram/ISessionClient.cs @@ -114,7 +114,10 @@ public interface ISessionClient : IAsyncDisposable /// Сколько последних сообщений запросить. /// Отмена операции. /// Сообщения диалога (с канальными полями для PushMessage). - public Task> GetMessagesAsync(string dialogId, int limit, CancellationToken cancellationToken); + public Task> GetMessagesAsync( + string dialogId, + int limit, + CancellationToken cancellationToken); /// /// Помечает весь диалог прочитанным (send_read_acknowledge прототипа L277/L383/L454/L609). @@ -145,7 +148,10 @@ public interface ISessionClient : IAsyncDisposable /// Верхняя граница результата (прототип: default 30). /// Отмена операции. /// Найденные источники в нейтральном виде (каналы/группы/личные). - public Task> SearchAsync(string query, int limit, CancellationToken cancellationToken); + public Task> SearchAsync( + string query, + int limit, + CancellationToken cancellationToken); /// /// Инфо об источнике для оценки кандидата (discovery_info L666–716): имя/username/kind + участники @@ -169,7 +175,10 @@ public interface ISessionClient : IAsyncDisposable /// Размер выборки (прототип: limit сообщений/тем; limit ≤ 0 — пусто без сети). /// Отмена операции. /// Результат чтения выборки (ok + сообщения либо no_history). - public Task ReadForEvalAsync(string dialogId, int limit, CancellationToken cancellationToken); + public Task ReadForEvalAsync( + string dialogId, + int limit, + CancellationToken cancellationToken); /// /// Вступить в канал/группу по username (discovery_join L818–839; ручной join вне квот — паузу перед diff --git a/src/telegram-service/Deal.Telegram/Telegram/ITelegramClientFactory.cs b/src/telegram-service/Deal.Telegram/Telegram/ITelegramClientFactory.cs index f4db009..68d9d37 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/ITelegramClientFactory.cs +++ b/src/telegram-service/Deal.Telegram/Telegram/ITelegramClientFactory.cs @@ -19,5 +19,8 @@ public interface ITelegramClientFactory /// либо null — новая сессия (нет файла или ключи приложения сменились). /// /// Клиент, готовый к ConnectAsync/логину. - public ISessionClient Create(int apiId, string apiHash, byte[]? storedSession); + public ISessionClient Create( + int apiId, + string apiHash, + byte[]? storedSession); } diff --git a/src/telegram-service/Deal.Telegram/Telegram/TelegramDialog.cs b/src/telegram-service/Deal.Telegram/Telegram/TelegramDialog.cs index 292e087..410bcb5 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/TelegramDialog.cs +++ b/src/telegram-service/Deal.Telegram/Telegram/TelegramDialog.cs @@ -20,7 +20,13 @@ public sealed record TelegramDialog /// Тип канона: channel|group|forum|chat (шапка telegram.proto). /// Число непрочитанных сообщений (для realtime_sweep). /// Id самого свежего сообщения диалога (для read-ack). - public TelegramDialog(string id, string name, string username, string kind, int unreadCount, int topMessageId) + public TelegramDialog( + string id, + string name, + string username, + string kind, + int unreadCount, + int topMessageId) { Id = id; Name = name; diff --git a/src/telegram-service/Deal.Telegram/Telegram/TelegramMessage.cs b/src/telegram-service/Deal.Telegram/Telegram/TelegramMessage.cs index e839b62..2c56da3 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/TelegramMessage.cs +++ b/src/telegram-service/Deal.Telegram/Telegram/TelegramMessage.cs @@ -19,7 +19,13 @@ public sealed record TelegramMessage /// Время сообщения, epoch-ms. /// Имя диалога (title/first_name) для PushMessage.channel_name. /// Username диалога для PushMessage.channel_handle. - public TelegramMessage(string dialogId, int id, string text, long dateMs, string dialogName, string dialogHandle) + public TelegramMessage( + string dialogId, + int id, + string text, + long dateMs, + string dialogName, + string dialogHandle) { DialogId = dialogId; Id = id; diff --git a/src/telegram-service/Deal.Telegram/Telegram/TelegramSourceInfo.cs b/src/telegram-service/Deal.Telegram/Telegram/TelegramSourceInfo.cs index 39c0741..9d8df17 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/TelegramSourceInfo.cs +++ b/src/telegram-service/Deal.Telegram/Telegram/TelegramSourceInfo.cs @@ -19,7 +19,13 @@ public sealed record TelegramSourceInfo /// Тип канона контракта: channel|group|forum|chat (пусто — не определён). /// Число участников (full_chat); null — определить не удалось. /// True — мегагруппа с темами (форум; core трактует kind как forum). - public TelegramSourceInfo(string id, string name, string username, string kind, int? participants, bool isForum) + public TelegramSourceInfo( + string id, + string name, + string username, + string kind, + int? participants, + bool isForum) { Id = id; Name = name; diff --git a/src/telegram-service/Deal.Telegram/Telegram/TlMessageMapper.cs b/src/telegram-service/Deal.Telegram/Telegram/TlMessageMapper.cs index f163943..d03543d 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/TlMessageMapper.cs +++ b/src/telegram-service/Deal.Telegram/Telegram/TlMessageMapper.cs @@ -32,7 +32,10 @@ public static class TlMessageMapper /// Диалог из ответа getDialogs. /// Сущности чатов контейнера (по raw id). /// Сущности пользователей контейнера (по raw id). - public static TelegramDialog? ToDialog(DialogBase dialog, IReadOnlyDictionary chats, IReadOnlyDictionary users) + public static TelegramDialog? ToDialog( + DialogBase dialog, + IReadOnlyDictionary chats, + IReadOnlyDictionary users) { if (dialog.Peer is null) { @@ -53,7 +56,10 @@ public static class TlMessageMapper /// Сообщение (MessageBase). /// Сущности чатов контейнера. /// Сущности пользователей контейнера. - public static TelegramMessage? ToMessage(MessageBase message, IReadOnlyDictionary chats, IReadOnlyDictionary users) + public static TelegramMessage? ToMessage( + MessageBase message, + IReadOnlyDictionary chats, + IReadOnlyDictionary users) { if (message is not Message textMessage || string.IsNullOrWhiteSpace(textMessage.message) @@ -103,7 +109,11 @@ public static class TlMessageMapper /// Id темы форума (для обычных источников null). /// Название темы форума (для обычных источников null). /// Текущее время (фолбэк даты сообщения без времени, как python `now`). - public static DiscoveryMessage? ToEvalMessage(MessageBase message, long? topicId, string? topicTitle, DateTimeOffset now) + public static DiscoveryMessage? ToEvalMessage( + MessageBase message, + long? topicId, + string? topicTitle, + DateTimeOffset now) { if (message is not Message textMessage || string.IsNullOrWhiteSpace(textMessage.message)) @@ -136,7 +146,10 @@ public static class TlMessageMapper // signedId: Подписанный id диалога (фолбэк имени). // chat: Сущность чата/канала (или null). // user: Сущность пользователя (или null). - private static (string Name, string Handle, string Kind) DescribePeer(string signedId, ChatBase? chat, UserBase? user) + private static (string Name, string Handle, string Kind) DescribePeer( + string signedId, + ChatBase? chat, + UserBase? user) { if (chat is not null) { diff --git a/src/telegram-service/Deal.Telegram/Telegram/WTelegramSessionClient.cs b/src/telegram-service/Deal.Telegram/Telegram/WTelegramSessionClient.cs index ae25b93..d6facba 100644 --- a/src/telegram-service/Deal.Telegram/Telegram/WTelegramSessionClient.cs +++ b/src/telegram-service/Deal.Telegram/Telegram/WTelegramSessionClient.cs @@ -73,7 +73,10 @@ public sealed class WTelegramSessionClient : ISessionClient /// api_id приложения Telegram. /// api_hash приложения Telegram (же ключ внутреннего шифрования сессии). /// Сохранённые байты сессии (null — новая сессия). - public WTelegramSessionClient(int apiId, string apiHash, byte[]? storedSession) + public WTelegramSessionClient( + int apiId, + string apiHash, + byte[]? storedSession) { _apiId = apiId; _apiHash = apiHash; @@ -257,7 +260,10 @@ public sealed class WTelegramSessionClient : ISessionClient } /// - public async Task> GetMessagesAsync(string dialogId, int limit, CancellationToken cancellationToken) + public async Task> GetMessagesAsync( + string dialogId, + int limit, + CancellationToken cancellationToken) { InputPeer peer = await ResolvePeerAsync(dialogId, cancellationToken).ConfigureAwait(false); Messages_MessagesBase result = await RunTlCallAsync(() => _client.Messages_GetHistory(peer, limit: limit), cancellationToken).ConfigureAwait(false); @@ -289,7 +295,10 @@ public sealed class WTelegramSessionClient : ISessionClient // --- Discovery (план Task 11; discovery_search/info/read/join/leave L622–873; Ruling 3/7) --- /// - public async Task> SearchAsync(string query, int limit, CancellationToken cancellationToken) + public async Task> SearchAsync( + string query, + int limit, + CancellationToken cancellationToken) { // contacts.SearchRequest(q, limit): результат — сущности chats/users (поиск каналов/групп/людей). Contacts_Found found = await RunTlCallAsync(() => _client.Contacts_Search(query, limit), cancellationToken).ConfigureAwait(false); @@ -347,7 +356,10 @@ public sealed class WTelegramSessionClient : ISessionClient } /// - public async Task ReadForEvalAsync(string dialogId, int limit, CancellationToken cancellationToken) + public async Task ReadForEvalAsync( + string dialogId, + int limit, + CancellationToken cancellationToken) { // discovery_read L718–760: limit ≤ 0 — пустой ok без сетевых вызовов (L730–732). if (limit <= 0) @@ -431,7 +443,11 @@ public sealed class WTelegramSessionClient : ISessionClient // rawId: Raw id канала. // unknown: Инфо по умолчанию (сущность недоступна). // cancellationToken: Отмена операции. - private async Task GetChannelInfoAsync(string dialogId, long rawId, TelegramSourceInfo unknown, CancellationToken cancellationToken) + private async Task GetChannelInfoAsync( + string dialogId, + long rawId, + TelegramSourceInfo unknown, + CancellationToken cancellationToken) { Channel? cached; lock (_entityCacheGate) @@ -465,7 +481,10 @@ public sealed class WTelegramSessionClient : ISessionClient // dialogId: Подписанный id. // rawId: Raw id канала. // cancellationToken: Отмена операции. - private async Task TryFetchChannelParticipantsAsync(string dialogId, long rawId, CancellationToken cancellationToken) + private async Task TryFetchChannelParticipantsAsync( + string dialogId, + long rawId, + CancellationToken cancellationToken) { try { @@ -485,7 +504,11 @@ public sealed class WTelegramSessionClient : ISessionClient // rawId: Raw id группы (положительный). // unknown: Инфо по умолчанию. // cancellationToken: Отмена операции. - private async Task GetChatInfoAsync(string dialogId, long rawId, TelegramSourceInfo unknown, CancellationToken cancellationToken) + private async Task GetChatInfoAsync( + string dialogId, + long rawId, + TelegramSourceInfo unknown, + CancellationToken cancellationToken) { Chat? cached; lock (_entityCacheGate) @@ -537,7 +560,10 @@ public sealed class WTelegramSessionClient : ISessionClient // dialogId: Подписанный id. // group: Entity группы. // participants: Число участников (null — определить не удалось). - private static TelegramSourceInfo DescribeGroup(string dialogId, Chat group, int? participants) + private static TelegramSourceInfo DescribeGroup( + string dialogId, + Chat group, + int? participants) => new( dialogId, DisplayName(dialogId, group), @@ -550,7 +576,10 @@ public sealed class WTelegramSessionClient : ISessionClient // dialogId: Подписанный id. // rawId: Raw id пользователя. // unknown: Инфо по умолчанию (сущность неизвестна — как entity-not-found прототипа). - private TelegramSourceInfo GetUserInfo(string dialogId, long rawId, TelegramSourceInfo unknown) + private TelegramSourceInfo GetUserInfo( + string dialogId, + long rawId, + TelegramSourceInfo unknown) { User? user; lock (_entityCacheGate) @@ -578,7 +607,10 @@ public sealed class WTelegramSessionClient : ISessionClient // limit: Размер выборки (раскладывается по темам). // cancellationToken: Отмена операции. // Возвращает: Плоский список сообщений тем (пуст — темы не прочитались; фолбэк на ленту делает вызывающий). - private async Task> ReadForumTopicsAsync(InputPeer peer, int limit, CancellationToken cancellationToken) + private async Task> ReadForumTopicsAsync( + InputPeer peer, + int limit, + CancellationToken cancellationToken) { var outMessages = new List(); try @@ -659,7 +691,10 @@ public sealed class WTelegramSessionClient : ISessionClient // dialogId: Подписанный id. // channel: Entity канала из ответа полного чата. // participants: Число участников (full_chat) либо null. - private static TelegramSourceInfo DescribeChannel(string dialogId, Channel channel, int? participants) + private static TelegramSourceInfo DescribeChannel( + string dialogId, + Channel channel, + int? participants) => new( dialogId, DisplayName(dialogId, channel), @@ -952,7 +987,10 @@ public sealed class WTelegramSessionClient : ISessionClient // dialogId: Подписанный id канала («-100…»). // rawId: Raw id канала. // cancellationToken: Отмена операции. - private async Task ResolveInputChannelAsync(string dialogId, long rawId, CancellationToken cancellationToken) + private async Task ResolveInputChannelAsync( + string dialogId, + long rawId, + CancellationToken cancellationToken) { lock (_entityCacheGate) { @@ -976,7 +1014,10 @@ public sealed class WTelegramSessionClient : ISessionClient // isChannel: Признак канала («-100…»). // isUser: Признак личного чата («+…»). // rawId: Raw id (канала/пользователя). - private bool TryImportPeerFromCollector(bool isChannel, bool isUser, long rawId) + private bool TryImportPeerFromCollector( + bool isChannel, + bool isUser, + long rawId) { if (!isChannel && !isUser) { @@ -1022,7 +1063,11 @@ public sealed class WTelegramSessionClient : ISessionClient // isChat: Признак базовой группы («-…»). // isUser: Признак личного чата («+…»). // rawId: Raw id (канала/группы/пользователя). - private InputPeer? BuildPeer(bool isChannel, bool isChat, bool isUser, long rawId) + private InputPeer? BuildPeer( + bool isChannel, + bool isChat, + bool isUser, + long rawId) { if (isChat) { @@ -1050,7 +1095,12 @@ public sealed class WTelegramSessionClient : ISessionClient // isChat: True — базовая группа. // isUser: True — личный чат. // rawId: Raw id без префикса. - private static bool TryParseSignedId(string dialogId, out bool isChannel, out bool isChat, out bool isUser, out long rawId) + private static bool TryParseSignedId( + string dialogId, + out bool isChannel, + out bool isChat, + out bool isUser, + out long rawId) { isChannel = false; isChat = false; diff --git a/src/telegram-service/Deal.Telegram/TelegramServiceImpl.cs b/src/telegram-service/Deal.Telegram/TelegramServiceImpl.cs index 540c480..e91f33d 100644 --- a/src/telegram-service/Deal.Telegram/TelegramServiceImpl.cs +++ b/src/telegram-service/Deal.Telegram/TelegramServiceImpl.cs @@ -473,7 +473,10 @@ public sealed class TelegramServiceImpl : TelegramService.TelegramServiceBase // tenantId: Id тенанта. // entries: Актуальный каталог диалогов. // cancellationToken: Отмена операции. - private async Task SyncCatalogSilentlyAsync(string tenantId, IReadOnlyList entries, CancellationToken cancellationToken) + private async Task SyncCatalogSilentlyAsync( + string tenantId, + IReadOnlyList entries, + CancellationToken cancellationToken) { try {