// ml-service — точка входа gRPC-хоста (план Task 3, L240–248; Ruling 1/2/4/12). // // Kestrel HTTP/2 на порту 5103 (env GRPC_PORT, затем PORT) + AddGrpc с интерцепторами service-token // и access-лога + стандартный gRPC-health (grpc.health.v1.Health). Транспорт: dev — plaintext // (Ruling 2); mTLS (TLS + клиентский сертификат) — при DEAL_MTLS_ENABLED=1 (Ruling 6, план Task 13; // сертификаты deploy/certs — scripts/mtls-certs.sh, env передаёт compose-prod Task 14); fail-closed: // Production без mTLS не стартует (GrpcHostEnvironment.RequireMtlsInProduction). // Серверная обвязка (Kestrel/AddGrpc/health) — общий Deal.Grpc.Hosting (C31): хост-фабрика // MlServiceHost.Create используется и интеграционными тестами (Deal.Ml.Tests), которые поднимают // его в своём процессе на эфемерном порту. Реальная логика (план Task 5/6): инкрементальный // наивный Байес по терминам per-tenant (файлы data/ml/.sqlite, Ruling 4) — движок/пул // в Model/, хранилище в Storage/, gRPC Predict/Status/Reset/TrainBatch — MlServiceImpl. using Deal.Grpc.Hosting.Interceptors; using Deal.Grpc.Hosting.Models; using Deal.Grpc.Hosting.Options; using Deal.Grpc.Hosting.Services; using Deal.Ml; // Порт по умолчанию — 5103 (Ruling 12, compose.dev.yml); переопределяется env GRPC_PORT (контейнер) // или PORT (общий конвенциональный env хостинг-платформ) — см. GrpcHostEnvironment.ResolveGrpcPort. const int defaultGrpcPort = 5103; // Имя процесса для rolling-файла логов (Ruling 7, Task 14): data/logs/deal-ml-<дата>.json. const string mlProcessName = "ml"; int grpcPort = GrpcHostEnvironment.ResolveGrpcPort(defaultGrpcPort); // Порт эндпоинта метрик /metrics (HTTP/1.1, отдельно от gRPC HTTP/2; этап 12, пакет A). int metricsPort = DealMetricsHosting.ResolveMetricsPort(DealMetricsHosting.DefaultMetricsPort); // Serilog (Ruling 7, план Task 14): JSON-консоль + rolling-файл data/logs/deal-ml-*.json — // конфигурируется production-точкой входа через configureBuilder-хук хоста (тесты хост поднимают // без Serilog, DealLogging.Configure в MlServiceHost/Create вызывается только здесь). Метрики // (OTel → Prometheus, /metrics) — тем же хуком до builder.Build(). WebApplication app = MlServiceHost.Create( grpcPort, configureBuilder: builder => { DealLogging.Configure(builder, mlProcessName); DealMetricsHosting.AddDealMetrics(builder, metricsPort); }); // Эндпоинт метрик /metrics (HTTP/1.1 на отдельном порту): формат Prometheus (этап 12, пакет A). DealMetricsHosting.MapDealMetrics(app); // Режим транспорта — из тех же env, что читал хост (Ruling 6, Task 13): mTLS при DEAL_MTLS_ENABLED=1. MtlsOptions mtlsOptions = MtlsOptions.FromConfiguration(app.Configuration); // Fail-closed (замечание code-review): отсутствие/опечатка DEAL_MTLS_ENABLED не должны давать // «тихого» plaintext в Production; Development (и прочие не-prod окружения) — как раньше. GrpcHostEnvironment.RequireMtlsInProduction(mtlsOptions); app.Logger.LogInformation( "ml-service стартует: gRPC {Transport} 0.0.0.0:{Port} (health /grpc.health.v1.Health/Check)", mtlsOptions.Enabled ? "mTLS (TLS + клиентский сертификат)" : "plaintext + service-token", grpcPort); await app.RunAsync();