Skip to content
This repository was archived by the owner on May 25, 2026. It is now read-only.

feat(agentic): implement MAF-based agentic PDF processing pipeline with auth, structured logging, and comprehensive tests - #8

Merged
cherninkiy merged 19 commits into
mainfrom
dev
May 14, 2026
Merged

feat(agentic): implement MAF-based agentic PDF processing pipeline with auth, structured logging, and comprehensive tests#8
cherninkiy merged 19 commits into
mainfrom
dev

Conversation

@cherninkiy

Copy link
Copy Markdown
Owner

Summary

This PR introduces a major evolution of the PDF processing system — migration from a simple message-driven pipeline to a Microsoft Agent Framework (MAF) based agentic architecture with checkpoint/resume capabilities, JWT authentication, structured logging, and significantly expanded test coverage.

What's Changed

Agentic Architecture (MAF)

  • Shared abstractions: introduced IAgent, IAgentOrchestrator, ICheckpointStore, AgentContext, AgentDefinition, AgentResult, WorkflowCheckpoint models in Shared project
  • DocumentProcessingAgent: new MAF-based agent in Worker with full checkpoint support for resilient multi-step PDF processing
  • PostgreSQL checkpoint store: PostgreSqlCheckpointStore for durable agent state persistence
  • Consumer refactoring: PdfProcessingConsumer now delegates to DocumentProcessingAgent via MAF pipeline
  • DI registration: MAF services registered in Worker DI container
  • .NET 10 migration: updated Worker to .NET 10 and installed Microsoft.Agents.AI packages

Authentication

  • JWT authentication added to ApiGateway with AuthenticationExtensions and TokenModels
  • AuthController with dev token endpoint for development/testing
  • Configuration updated in appsettings.json and appsettings.Development.json

Structured Logging

  • Replaced default ILogger with Serilog structured logging across ApiGateway and Worker

Testing (846+ new test lines)

  • DocumentProcessingAgentTests (465 lines): unit tests covering checkpoint scenarios (create, resume, complete)
  • ConcurrencyTests (171 lines): concurrency and idempotency tests
  • PdfProcessingConsumerRetryTests (182 lines): retry and dead-letter queue behavior
  • OutboxPublisherTests (208 lines): outbox pattern reliability tests
  • DocumentProcessingServiceTests: minor fixes and improvements

Infrastructure & Reliability

  • Typed exceptions: DocumentProcessingException for better error handling
  • CancellationToken checks throughout processing pipeline
  • Docker Compose healthcheck for Worker service
  • Prometheus MetricsHostedService: wrapped MetricServer in `IHostedService

cherninkiy added 17 commits May 13, 2026 14:25
- update all projects TargetFramework from net8.0 to net10.0
- add Microsoft.Agents.AI 1.5.0 package to Worker
- add Microsoft.Agents.AI.Abstractions 1.5.0 package to Shared
- update Microsoft.EntityFrameworkCore to 10.0.* across all projects
- update Microsoft.Extensions.Diagnostics.HealthChecks to 10.0.*
- update Microsoft.Extensions.Hosting to 10.0.*
- update Npgsql.EntityFrameworkCore.PostgreSQL to 10.0.*
- update Microsoft.AspNetCore.OpenApi to 10.0.* (replaces Swashbuckle)
- update Microsoft.AspNetCore.Mvc.Testing to 10.0.* in test projects
- update Microsoft.EntityFrameworkCore.InMemory to 10.0.*
- update Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore to 10.0.*
- replace Swashbuckle.AspNetCore with Scalar.AspNetCore 2.14.11
- replace AddSwaggerGen() with AddOpenApi() in Program.cs
- replace UseSwagger()/UseSwaggerUI() with MapOpenApi()/MapScalarApiReference()
- add using Scalar.AspNetCore to Program.cs
- update EFCore.NamingConventions from 8.0.3 to 10.0.1
- fix CS8625 warning in DocumentProcessingServiceTests (null literal)
- fix CS8604 warning in DocumentProcessingServiceTests (possible null reference)
- remove redundant System.Text.Json package from IntegrationTests
- remove redundant Microsoft.Extensions.Diagnostics.HealthChecks from ApiGateway
- add WorkflowCheckpoint model for durable agent workflow state
- add AgentDefinition model for agent registry and discovery
- add DbSet<WorkflowCheckpoint> and DbSet<AgentDefinition> to Worker AppDbContext
- configure EF Core mappings for workflow_checkpoints and agent_definitions tables
- add SQL schema for new tables in db/init.sql
- seed DocumentProcessing agent definition in init.sql
…AgentContext)

- add AgentResult class for activity execution results
- add AgentContext class for passing state between workflow activities
- add IAgent interface for implementing agent workflows
- add ICheckpointStore interface for checkpoint persistence
- add IAgentOrchestrator interface for agent pipeline orchestration
- add PostgreSqlCheckpointStore implementing ICheckpointStore
- SaveCheckpointAsync uses upsert semantics (insert or update)
- LoadCheckpointAsync returns most recent checkpoint for resume
- LoadCompletedCheckpointsAsync returns all completed activities
- DeleteCheckpointsAsync cleans up after workflow completion
- add DocumentProcessingAgent implementing IAgent interface
- 5 workflow activities: DownloadDocument, ParseDocument, ExtractText, SaveResult, UpdateStatus
- each activity saves a checkpoint after execution for durable execution
- resume support: skips already-completed activities after worker crash
- reuses existing PdfTextExtractor, IDocumentRepository, IFileStorage services
- cleanup checkpoints after successful workflow completion
- replace DocumentProcessingService with DocumentProcessingAgent in consumer
- add ICheckpointStore and IDocumentRepository dependencies to consumer
- build AgentContext from MassTransit command for agent execution
- preserve MassTransit retry/DLQ as base-level protection
- add message-level idempotency check before agent execution
- update XML docs to explain hybrid MassTransit + MAF architecture
- register DocumentProcessingAgent as scoped service
- register ICheckpointStore -> PostgreSqlCheckpointStore as scoped
- add Worker.Agents namespace using directive
- update Program.cs comments to explain hybrid MassTransit + MAF architecture
- keep DocumentProcessingService for backward compatibility
…scenarios

- add 5 unit tests for DocumentProcessingAgent
- test AgentName and Activities properties
- test full workflow execution with all 5 checkpoints saved
- test resume after crash (skips completed activities)
- test failure handling with failure checkpoint saved
- make PdfTextExtractor.ExtractTextAsync virtual for Moq compatibility
…scenarios

- Add 5 new checkpoint resume scenarios (middle, last, all-completed)
- Add Base64 bytes roundtrip test for checkpoint state data
- Add failure checkpoint error message preservation test
- Total: 9 tests in DocumentProcessingAgentTests, all passing
- Update README.md: agentic architecture diagram, MAF+MassTransit hybrid
  explanation, new agent example (TranslationAgent), updated metrics
- Fill AGENTIC_READINESS.md: migration results, architecture details,
  MAF system requirements (.NET 10.0+), checkpoint test coverage,
  TranslationAgent example with DI registration
- Update AGENTIC_ROADMAP.md: mark all 9 stages as completed,
  add completion status banner
- add MetricsHostedService that integrates metric server lifecycle with generic host
- remove manual MetricServer creation from Program.cs
- register via AddHostedService for graceful shutdown on SIGTERM
…tension

- move if/else DB selection logic from Program.cs into a dedicated extension method
- add AddDatabase(IServiceCollection, IConfiguration, IHostEnvironment) to ServiceCollectionExtensions
- keep all three branches: Testing→in-memory, connection string→PostgreSQL, fallback→in-memory
- improve SRP compliance — Program.cs now only composes services
- add AddGatewayAuthentication() extension in Authentication/ directory
- add TokenRequest/TokenResponse models in Authentication/ directory
- add dev-only AuthController with POST /auth/token endpoint
- configure Jwt:SecretKey in appsettings.Development.json for local testing
- skip auth in Testing environment to avoid breaking unit tests
- add Microsoft.AspNetCore.Authentication.JwtBearer package
- add Serilog.AspNetCore and Serilog.Formatting.Compact packages to both projects
- configure Serilog + CompactJsonFormatter in ApiGateway and Worker Program.cs
- remove .ConfigureLogging() block from Worker (now handled by Serilog)
- structured JSON output enables log aggregation tools like Loki, Elasticsearch
…mpose healthcheck

- add DocumentProcessingException in Shared/Exceptions for typed error handling
- replace bare new Exception() in PdfProcessingConsumer with DocumentProcessingException
- add CancellationToken.ThrowIfCancellationRequested() at the start of each agent activity
  to enable graceful shutdown during long-running Tesseract OCR operations
- worker already had postgres healthcheck dependency (verified)
…, and concurrency tests

- add OutboxPublisherTests: publish pending, corrupt JSON, publish failure, cancellation
- add PdfProcessingConsumerRetryTests: agent failure re-throws, sets Failed status, idempotency
- add ConcurrencyTests: duplicate detection via TryUpdateStatusAsync, two workers skip duplicate
- total tests increased from 28 to 37
- CI/.NET/License badges
- SOTA-solution description
- 37 tests, Serilog, JWT in summary

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request migrates the PDF processing system to an 'Agentic' architecture using the Microsoft Agent Framework (MAF) and upgrades the entire solution to .NET 10. Key changes include the implementation of a checkpoint-based workflow in the Worker service to enable durable processing and resume capabilities, the addition of JWT authentication in the ApiGateway, and the integration of Serilog for structured logging. The test suite has been significantly expanded to cover the new MAF agent, checkpoint persistence, and concurrency scenarios. Feedback identifies several security and reliability improvements, including the need for secret length validation, environment-based restrictions for the development authentication endpoint, and more flexible configuration for the metrics server port. Additionally, a potential issue with exception message lengths exceeding database column limits in the checkpoint store was noted.

ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
System.Text.Encoding.UTF8.GetBytes(jwtSecret)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The jwtSecret is used to create a SymmetricSecurityKey. For the HMAC SHA256 algorithm, the key must be at least 128 bits (16 bytes), but 256 bits (32 bytes) is recommended for security. If the configured secret is too short, the application will throw an exception at runtime during token validation. Consider adding a validation check for the secret length in the configuration logic.

[ApiController]
[Route("auth")]
[AllowAnonymous]
public class AuthController : ControllerBase

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The AuthController allows generating JWT tokens using a symmetric key. This endpoint should be restricted to the Development environment to prevent security risks in production. You can inject IHostEnvironment and check env.IsDevelopment() in the constructor or use a conditional attribute.

Comment on lines +20 to +25
public MetricsHostedService(ILogger<MetricsHostedService> logger)
{
_logger = logger;
// Prometheus metrics endpoint — separate port for worker metrics
_metricServer = new MetricServer(port: 5091);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The Prometheus metric server port is hardcoded to 5091. It is recommended to inject IConfiguration and retrieve the port from settings to allow flexibility across different environments.

    public MetricsHostedService(IConfiguration configuration, ILogger<MetricsHostedService> logger)
    {
        _logger = logger;
        var port = configuration.GetValue<int>("Metrics:Port", 5091);
        _metricServer = new MetricServer(port: port);
    }

existing.StateData = result.OutputData;
existing.IsCompleted = result.IsSuccess;
existing.IsFailed = !result.IsSuccess;
existing.ErrorMessage = result.ErrorMessage;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The ErrorMessage column in the workflow_checkpoints table is limited to 4096 characters. If an exception message is longer, saving the checkpoint will fail. It's safer to truncate the message before assignment.

            existing.ErrorMessage = result.ErrorMessage?.Length > 4096 ? result.ErrorMessage[..4096] : result.ErrorMessage;

@cherninkiy
cherninkiy merged commit c5a978d into main May 14, 2026
2 checks passed
@cherninkiy
cherninkiy deleted the dev branch May 14, 2026 10:42
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant