From 6114d8f457ba221d638af34cfd6318683ba45248 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Wed, 28 Oct 2026 09:28:06 +0200 Subject: [PATCH] Updated documentation --- Architecture.md | 136 ++++++++++++++++++++++++++++++++++++++++++++++-- GEMINI.md | 20 ++++++- Processes.md | 94 ++++++++++++++++++++++++++++++--- README.md | 33 ++++++++---- 4 files changed, 260 insertions(+), 23 deletions(-) diff --git a/Architecture.md b/Architecture.md index 14b8374..56decaa 100644 --- a/Architecture.md +++ b/Architecture.md @@ -22,12 +22,13 @@ The PFM platform is constructed using **Clean Architecture** and **Domain-Driven │ API GATEWAY │ │ PostFundManagement.Api (ASP.NET Core Minimal APIs / Carter) │ │ Authentication: JWT Bearer + OpenTelemetry Observability │ +│ Quartz Client: Scheduler-Only (ZeroSizeThreadPool / Enqueues Jobs) │ └───────────────────────────────────┬────────────────────────────────────┘ │ ┌───────────────────────────────────▼────────────────────────────────────┐ │ APPLICATION LAYER │ │ PostFundManagement.Application │ -│ CQRS (Mediator), Business Validators, Domain Services, Quartz Jobs │ +│ CQRS (Mediator), Business Validators, Domain Services, Event Handlers │ └──────────────────┬─────────────────────────────────┬───────────────────┘ │ │ ┌──────────────────▼──────────────────┐ ┌──────────▼───────────────────┐ @@ -35,8 +36,15 @@ The PFM platform is constructed using **Clean Architecture** and **Domain-Driven │ PostFundManagement.Domain │ │ PostFundManagement.Infrastr.│ │ Entities, Value Objects, Enums, │ │ PostgreSQL (EF Core 10), │ │ Domain Invariants, Audit Models │ │ Skoruba STS, S3 Garage, │ -└─────────────────────────────────────┘ │ Redis HybridCache │ - └──────────────────────────────┘ +└─────────────────────────────────────┘ │ Redis, Quartz (pfm-scheduler)│ + └──────────────┬───────────────┘ + │ +┌────────────────────────────────────────────────────────▼───────────────┐ +│ BACKGROUND JOBS WORKER HOST │ +│ PostFundManagement.Jobs (.NET 10 Background Host / Quartz Runner) │ +│ Recurring Job Registration (JobsWorker) & Mediated Execution Engine │ +│ Quartz Runner: Full Execution (DefaultThreadPool MaxConcurrency = 5) │ +└────────────────────────────────────────────────────────────────────────┘ ``` --- @@ -113,8 +121,8 @@ Even if an `Executive`, `Manager`, or `Supervisor` possesses the `update` claim flowchart LR A["Maker (User A)
Creates Record"] --> B["Record Persisted
with createdBy = User A"] B --> C{"Checker Evaluation
(Approver Login)"} - C -->|Checker == User A| D["Approval BLOCKED
Maker-Checker Violation"] - C -->|Checker == User B
with 'update' claim| E["Approval GRANTED
Status -> Approved"] + C -->|"Checker is User A"| D["Approval BLOCKED: Maker-Checker Violation"] + C -->|"Checker is User B with update claim"| E["Approval GRANTED: Status Approved"] ``` ### 4.2 Record Attribution Model @@ -189,4 +197,122 @@ Following the introduction of fine-grained RBAC and Maker-Checker checks, startu * **Immediate Non-Blocking Session Render**: If valid session tokens and user profiles exist in `localStorage`, `AuthProvider` immediately unblocks UI rendering (`isLoading: false`) and runs tenant lockout checks, account synchronization, and claim revalidation asynchronously in the background. * **In-Memory User Sync & Lockout Caching**: `isUserSyncedInBrowser` and `verifyTenantLockout` maintain in-memory references to eliminate synchronous `JSON.parse` operations from the request pipeline. +--- + +## 6. Distributed Background Job Processing Architecture (PostFundManagement.Jobs) + +The PFM platform decouples synchronous API request handling from asynchronous, long-running, and scheduled workloads using a distributed **Quartz.NET (v4.6+)** clustering topology backed by PostgreSQL (`pfm-scheduler`). + +### 6.1 Split-Node Topology: Scheduler Client vs. Execution Host + +To maintain high API throughput, low memory footprint, and complete process isolation, PFM implements a split-node architecture between `PostFundManagement.Api` and `PostFundManagement.Jobs`: + +``` +┌──────────────────────────────────────────────┐ +│ PostFundManagement.Api │ +│ (API Gateway / Scheduler Client) │ +│ │ +│ * AddQuartzSchedulerClient() │ +│ * ZeroSizeThreadPool (0 execution threads) │ +│ * SchedulerStatus.Created (Standby mode) │ +│ * Strictly schedules / enqueues events │ +│ * Never acquires triggers or executes jobs │ +└──────────────────────┬───────────────────────┘ + │ Schedules IEvent jobs via IJobOrchestrator + ▼ +┌──────────────────────────────────────────────┐ +│ PostgreSQL JobStore │ +│ (pfm-scheduler) │ +│ │ +│ * Clustered tables (QRTZ_TRIGGERS, etc.) │ +│ * System.Text.Json serialized IEvent payload│ +│ * Distributed locking & check-in heartbeat │ +└──────────────────────▲───────────────────────┘ + │ + │ Acquires triggers & executes (Polling loop) +┌──────────────────────┴───────────────────────┐ +│ PostFundManagement.Jobs │ +│ (Dedicated Worker Service) │ +│ │ +│ * AddQuartzScheduler() │ +│ * DefaultThreadPool (MaxConcurrency = 5) │ +│ * SchedulerStatus.Running (Started loop) │ +│ * JobsWorker background service: │ +│ - Registers recurring cron jobs on start │ +│ - Enqueues one-time jobs when needed │ +│ * Executes MediatorJob │ +│ * Deserializes and publishes via IMediator │ +└──────────────────────────────────────────────┘ +``` + +| Characteristic | `PostFundManagement.Api` | `PostFundManagement.Jobs` | +| :--- | :--- | :--- | +| **Architectural Role** | API Gateway & Event Producer | Dedicated Execution Daemon & Consumer | +| **Registration Extension** | `AddQuartzSchedulerClient(...)` | `AddQuartzScheduler(...)` | +| **Thread Pool** | `ZeroSizeThreadPool` (0 threads) | `DefaultThreadPool` (`MaxConcurrency = 5`) | +| **Hosted Service** | None (`AddQuartzHostedService` omitted) | `AddQuartzHostedService(WaitForJobsToComplete = true)` | +| **Lifecycle State** | `SchedulerStatus.Created` (Standby) | `SchedulerStatus.Running` (Active execution loop) | +| **Readiness Check** | Verifies DB connectivity without requiring `Running` | Verifies active execution engine & DB connectivity | +| **Trigger Acquisition** | ❌ **Disabled** (Cannot acquire triggers or run jobs) | ✅ **Active** (Polls DB, acquires triggers, runs jobs) | + +### 6.2 Event-Driven Mediated Orchestration (`IEvent` & `MediatorJob`) + +Background tasks are modeled as strongly-typed domain events implementing `IEvent : INotification` (`PostFundManagement.Domain.Abstractions`). + +```mermaid +sequenceDiagram + autonumber + participant API as PostFundManagement.Api + participant Orchestrator as IJobOrchestrator + participant DB as PostgreSQL pfm-scheduler + participant Host as PostFundManagement.Jobs + participant QuartzThread as Quartz Execution Engine + participant Mediator as IMediator MediatorJob + participant Handler as Domain Event Handlers + + Note over API,Orchestrator: 1. Event Scheduling (API Node) + API->>Orchestrator: SendAsync(notification) / ScheduleAsync(notification, cron) + Orchestrator->>DB: Stores JobDetail and Trigger with serialized Payload JSON + Note over DB,Host: 2. Distributed Acquisition (Jobs Node) + QuartzThread->>DB: Polls and acquires due trigger with row-level locks + QuartzThread->>Host: Dispatches to local thread pool + Host->>Mediator: Instantiates MediatorJob[TNotification] + Mediator->>Mediator: Deserializes payload from MergedJobDataMap + Mediator->>Handler: Publish(notification, cancellationToken) + Handler-->>Host: Execution complete and updates business data in pfm DB + Host->>DB: Releases trigger and commits execution state +``` + +1. **Scheduling via `IJobOrchestrator`**: + - `SendAsync(notification)`: Schedules a durable, one-time execution in the `onetime-jobs` group with `StartNow()` and `DisallowConcurrentExecution()`. + - `ScheduleAsync(notification, cronExpression)`: Configures a durable, cron-driven recurring job in the `scheduled-jobs` group with automatic misfire handling (`CronTriggerMisfireInstruction.IgnoreMisfires`). + - `InterruptAsync(eventName, correlationId)`: Allows runtime cancellation of active job executions across the cluster. +2. **Transparent Deserialization & Dispatch (`MediatorJob`)**: + `MediatorJob` resolves `IMediator` from DI, deserializes the JSON payload, initiates OpenTelemetry distributed tracing (`MediatorTelemetry.Source`), and publishes the event to all registered in-process handlers. + +### 6.3 Recurring vs. One-Time Job Patterns + +* **Recurring Jobs**: Scheduled by `JobsWorker` (`BackgroundService` in `PostFundManagement.Jobs`) during application startup. These jobs govern periodic health sweeps, SLA tracking, notification batching, and risk evaluations on predictable cron intervals. +* **One-Time Jobs**: Primarily scheduled by `PostFundManagement.Api` as immediate or deferred side-effects of HTTP requests and lifecycle mutations (e.g. user invites, award registration triggers). Furthermore, a running recurring job in `PostFundManagement.Jobs` can dispatch a one-time job (`jobOrchestrator.SendAsync`) when handling fan-out tasks or invoking decoupled activities that cannot be triggered directly by the originating job. + +### 6.4 Core Background Processing Capabilities + +The background job runner powers four essential operational workloads: + +1. **Automated Project Monitoring & Reminders**: + - Sweeps active projects, contractual deliverables, and milestone deadlines. + - Computes planned vs. actual progress variance and escalates overdue milestones (`FR-M04-017`, `FR-M07-009`). + - Dispatches automated reminders to Grantees/Partners prior to milestone due dates and submission cut-offs. +2. **Risk Monitoring & Automated Notifications**: + - Evaluates project risk registers, Key Risk Indicators (KRIs), and issue escalation thresholds (`FR-M09-001` - `011`). + - Detects risk score creep (Likelihood $\times$ Impact) and triggers automated alerts when risk tolerance thresholds are breached. + - Monitors overdue mitigation action plans and escalates unresolved high-severity risks to Programme Managers. +3. **Email Notification Processing**: + - Continuously consumes and batches pending notification records (`Platform.Email` / `NotificationPlatform.Email`). + - Dispatches transactional emails via MailKit / MimeKit SMTP with exponential backoff and retry policies. + - Updates delivery status (`Sent`, `Failed`, retry count) and logs dispatch telemetry for audit compliance (`FR-M25-001`). +4. **Platform Extensibility**: + - New background jobs and recurring routines can be introduced by creating an `IEvent` model in `PostFundManagement.Application` and scheduling it via `JobsWorker` or `IJobOrchestrator`, with zero impact on the API surface. + + diff --git a/GEMINI.md b/GEMINI.md index cd26b55..c6fc956 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -71,6 +71,7 @@ PostFundManagement/ ├── PostFundManagement.Infrastructure/ # EF Core 10, PostgreSQL (Npgsql), Migrations, Data Protection ├── PostFundManagement.Application/ # CQRS Handlers, Mediator, DTOs, Validations, S3, Email, Quartz ├── PostFundManagement.Api/ # Minimal APIs / Carter endpoints, Auth, Swagger/Scalar, Versioning +├── PostFundManagement.Jobs/ # Dedicated Quartz.NET Background Worker Host (Recurring & One-Time Execution) ├── PostFundManagement.Tests/ # Unit & Integration test suite (xUnit, Coverlet) ├── PostFundManagement.ConsoleUI/ # Internal Staff & Admin Web Portal (React 19 + Vite 6 + Tailwind 4) ├── PostFundManagement.PartnerUI/ # External Grantee / Partner Portal (React 19 + Vite 8 + Tailwind 4) @@ -85,6 +86,7 @@ PostFundManagement/ | **`PostFundManagement.Infrastructure`** | `net10.0` | `Microsoft.EntityFrameworkCore` 10.0.12, `Npgsql.EntityFrameworkCore.PostgreSQL` 10.0.3, `Duende.AccessTokenManagement` 4.2.0, EF Core Tools & Design | Houses `PostFundManagementDbContext`, EF Core fluent configurations, entity-to-table mappings, PostgreSQL schema definitions, multitenancy filters, and migrations. | | **`PostFundManagement.Application`** | `net10.0` | `Mediator.SourceGenerator` / `Mediator.Abstractions` 3.0.2, `FluentResults` 4.0.0, `Microsoft.Extensions.Caching.Hybrid` 10.10.0, `Microsoft.Extensions.Caching.StackExchangeRedis` 10.0.12, `Refit` 15.2.0, `Duende.AccessTokenManagement.OpenIdConnect` 4.2.0, `Quartz` 4.1.0, `AWSSDK.S3` | Implements CQRS command/query handlers, domain validators, S3 object storage clients (Garage S3), background jobs (Quartz), notification dispatchers, and business logic. | | **`PostFundManagement.Api`** | `net10.0` | `Microsoft.AspNetCore.OpenApi` 10.0.12, `Scalar.AspNetCore` 2.17.4, `Asp.Versioning.Http` 10.2.3, `IdentityModel.AspNetCore` 4.3.0, `Microsoft.AspNetCore.Authentication.JwtBearer` 10.0.12, `AspNetCore.HealthChecks.NpgSql` 9.0.0, `AspNetCore.HealthChecks.UI` | Minimal API endpoint definitions divided by business slices (`Grants`, `Projects`, `Execution`, `Governance`, `Evidence`, `Communications`, `Users`). Handles routing, auth policies, error problem details, and OpenAPI metadata. | +| **`PostFundManagement.Jobs`** | `net10.0` | `Quartz` 4.6.2, `Quartz.Extensions.DependencyInjection` 4.6.2, `Quartz.Serialization.SystemTextJson` 4.6.2, `Mediator.Abstractions` 3.0.2 | Dedicated background execution host. Executes recurring Quartz jobs (registered on startup by `JobsWorker`), processes asynchronous domain events (`IEvent`), and executes jobs concurrently (`MaxConcurrency = 5`). | | **`PostFundManagement.Tests`** | `net10.0` | `xunit` 2.9.3, `xunit.runner.visualstudio` 3.1.4, `Microsoft.NET.Test.Sdk` 17.14.1, `coverlet.collector` 6.0.4 | Unit and integration test coverage for domain rules, CQRS handlers, financial calculation models, and gate condition validations. | ### 2.2 Frontend Projects (React 19 / TypeScript) @@ -120,6 +122,22 @@ PostFundManagement/ * **Authentication / STS**: Duende IdentityServer / Skoruba Administration (`https://sts.security.khongisa.co.za`). * **Observability**: OpenTelemetry exporters streaming traces, metrics, and logs to Aspire Dashboard / OTLP collectors. +### 2.4 Distributed Background Processing Architecture (Quartz.NET & PostFundManagement.Jobs) +* **Dedicated Execution Daemon**: `PostFundManagement.Jobs` is a standalone background host responsible for recurring job scheduling, lifecycle sweeps, and asynchronous event execution. +* **API Isolation (Scheduler Client)**: `PostFundManagement.Api` is strictly configured as a scheduler client via `AddQuartzSchedulerClient(...)` using `ZeroSizeThreadPool` (0 worker threads) and running in standby (`SchedulerStatus.Created`). The API enqueues jobs and triggers into the clustered PostgreSQL database (`pfm-scheduler`) but never acquires triggers or executes jobs locally. +* **Worker Execution Engine**: `PostFundManagement.Jobs` configures full Quartz execution via `AddQuartzScheduler(...)` using `DefaultThreadPool` (`MaxConcurrency = 5`) and `AddQuartzHostedService(options => options.WaitForJobsToComplete = true)`. +* **Event-Driven Orchestration (`IEvent` & `MediatorJob`)**: + - The API acts as the primary source of one-time events triggered by user actions and lifecycle transitions (implementing `IEvent : INotification`). + - Events are enqueued via `IJobOrchestrator.SendAsync` or `ScheduleAsync` and persisted in `pfm-scheduler` as `MediatorJob` with serialized JSON payloads. + - When acquired by `PostFundManagement.Jobs`, `MediatorJob` deserializes the event payload, propagates OpenTelemetry distributed traces, and publishes the event via `IMediator.Publish(...)`. +* **Startup Recurring Registration**: On daemon startup, `JobsWorker : BackgroundService` initializes and registers recurring cron jobs across tenant portfolios. +* **Cascading One-Time Execution**: If a recurring job identifies complex follow-up items that cannot be processed in the recurring execution window, it dispatches decoupled one-time jobs (`jobOrchestrator.SendAsync`) across worker threads. +* **Core Job Workloads**: + 1. *Automated Project Monitoring & Reminders*: Sweeps milestones, checks contractual SLA deadlines, sets overdue flags (`FR-M07-009`), and sends reminder notices to Grantees/Partners. + 2. *Risk Monitoring & Automated Notifications*: Evaluates risk score creep ($\text{Likelihood} \times \text{Impact}$), flags tolerance breaches, and sends escalation alerts for overdue mitigation actions. + 3. *Email Notification Processing*: Batches and dispatches pending notification entries (`Platform.Email` / `NotificationPlatform.Email`) via MailKit/MimeKit SMTP and records delivery telemetry (`FR-M25-001`). + 4. *Future Jobs*: Seamlessly supports future scheduled workloads (e.g. drawdown reconciliations, compliance expiries, portfolio aggregations) simply by adding `IEvent` handlers. + --- ## 3. Master Business Rules (BR-001 to BR-010) @@ -848,7 +866,7 @@ The platform uses **Hashids** to obfuscate internal sequential database primary * **Implementation**: Managed by `HashService` (`PostFundManagement.Application/Services/Shared/HashService.cs`) using the `Hashids.net` (v1.7.0) library. * **Shared Configuration (`HasherSettings`)**: * **Salt**: Sourced from configuration (`HasherSettings:Salt` or `HASHER_SALT`), defaulting to: - `"TGlnaHQgQ2hhcm1zLCBNaWRyYW5kQm9va3MgbWFrZSBsb3RzIG9mIG1vbmV5IGFuZCBhcmUgYSBzdWNjZXNzZnVsIHZpcmFsIHN0b3J5IGluIFNvdXRoIEFmcmljYQ=="` + `"salt-random-string"` * **Min Hash Length**: `HasherSettings:MinHashLength` or `HASHER_MIN_HASH_LENGTH` (default: `11`). * **Core Service Methods**: * `HashEncodeLongId(long id)` / `HashEncodeIntId(int id)`: Encodes integers into obfuscated HashID strings (e.g. `1` $\to$ `kY5aB9m...`). diff --git a/Processes.md b/Processes.md index 7f1ffb1..856c03c 100644 --- a/Processes.md +++ b/Processes.md @@ -12,7 +12,7 @@ The PFM platform governs the lifecycle of grant, development, innovation, and im ┌────────────────────────────────────────────────────────────────────────────────────────┐ │ POST-FUND LIFECYCLE │ │ │ -│ [1. Award Intake] ──> [2. Contracting] ──> [3. Onboarding] │ +│ [1. Award Intake] ──> [2. Contracting] ──> [3. Onboarding] │ │ External Approval Draft Agreement KYC & Banking Gate │ │ Recorded (BR-001) Auto-created (BR-002) Required for Disb. (BR-003) │ │ │ │ @@ -88,6 +88,16 @@ The PFM platform governs the lifecycle of grant, development, innovation, and im 4. Resolution: `Approved & Enacted` or `Rejected`. 5. **Maker-Checker Rule**: Change request initiator cannot approve or enact the change. +### 2.7 Process 7: Automated Background Workflows & Scheduled Jobs (PostFundManagement.Jobs) +* **Pre-condition**: Background jobs host (`PostFundManagement.Jobs`) active and connected to `pfm-scheduler`. +* **Actors**: System Engine, `JobsWorker` Daemon, Domain Event Handlers. +* **Workflow**: + 1. **Event Triggering**: API operations emit strongly typed events (`IEvent`) and enqueue them into `pfm-scheduler` via `IJobOrchestrator`. + 2. **Cron Schedule Execution**: `JobsWorker` initializes recurring schedules on daemon startup for periodic monitoring, risk assessments, and communication batches. + 3. **Job Execution**: Quartz cluster dispatches acquired triggers to `MediatorJob` within `PostFundManagement.Jobs` (concurrency limit = 5). + 4. **Mediated Processing**: Event payload is deserialized and published via `IMediator` to execute domain logic (project monitoring, risk threshold checks, email dispatch). + 5. **Cascading One-Time Jobs**: When a recurring job identifies an action item requiring separate asynchronous execution, it triggers a decoupled one-time job (`jobOrchestrator.SendAsync`). + --- ## 3. The Maker-Checker Operational Policy @@ -139,20 +149,20 @@ To ensure that caching never impedes record lifecycle updates or displays stale sequenceDiagram autonumber actor User as User / Maker - participant UI as ConsoleUI (React Query) + participant UI as ConsoleUI React Query participant Client as Axios HttpClient - participant OutputCache as Backend OutputCache (Redis/Memory) + participant OutputCache as Backend OutputCache Redis/Memory participant DB as PostgreSQL Database - User->>UI: Submits Record Mutation (Create / Update / Approve) + User->>UI: Submits Record Mutation Create/Update/Approve UI->>Client: Dispatches POST / PUT / PATCH / DELETE Client->>DB: Executes Mutation in Database DB-->>Client: Returns 200 OK / 201 Created Client->>Client: Interceptor detects mutation: bumps activeMutationEpoch - UI->>UI: onSuccess triggers queryClient.invalidateQueries() + UI->>UI: onSuccess triggers invalidateQueries UI->>Client: Refetches active queries with _v = activeMutationEpoch - Client->>OutputCache: GET /v1/api/*?_v= - Note over OutputCache: Cache Miss (new _v key never seen before) + Client->>OutputCache: GET /v1/api/* with new _v epoch + Note over OutputCache: Cache Miss: new _v key never seen before OutputCache->>DB: Queries PostgreSQL for fresh data DB-->>OutputCache: Returns updated entities OutputCache-->>UI: Caches fresh data under _v and returns to UI @@ -164,4 +174,74 @@ sequenceDiagram 2. **Deterministic Invalidation**: Calling standard `queryClient.invalidateQueries({ queryKey: [...] })` or `invalidateAndBustCache(queryClient, filters)` guarantees that the subsequent refetch bypasses stale ASP.NET Core OutputCache entries. 3. **Optimized Read Cache**: When users navigate across unchanged views or switch tabs, identical `_v` tokens are reused, allowing backend OutputCache and React Query in-memory caches to fulfill requests in < 5ms without unnecessary database hits. +--- + +## 6. Background Job Lifecycle & Event Execution Workflows + +The dedicated worker host (`PostFundManagement.Jobs`) operates as a decoupled daemon executing recurring maintenance routines and asynchronous domain events dispatched from the API or internally from other jobs. + +### 6.1 Event Ingestion & Job Persistence Flow + +```mermaid +flowchart TD + subgraph Producer ["PostFundManagement.Api (Event Source)"] + A["User Action / Lifecycle Transition"] --> B["Domain Command / Mutation"] + B --> C["Instantiate IEvent (e.g. SyncUserEvent, ProjectReminderEvent)"] + C --> D["IJobOrchestrator.SendAsync() or ScheduleAsync()"] + end + + subgraph Store ["PostgreSQL Clustered Store (pfm-scheduler)"] + D --> E[("QRTZ_JOB_DETAILS and QRTZ_TRIGGERS")] + end + + subgraph Consumer ["PostFundManagement.Jobs (Execution Host)"] + E -->|"Acquire Trigger Lock"| F["Quartz Scheduler Engine (MaxConcurrency = 5)"] + F --> G["MediatorJob[TNotification]"] + G --> H["Deserialize JSON Payload from JobDataMap"] + H --> I["IMediator.Publish(notification)"] + I --> J["Domain Event Handler: Business Logic in pfm DB"] + J -.->|"Cascading Sub-Task Needed"| K["IJobOrchestrator.SendAsync: Dispatch One-Time Job"] + K -.-> E + end +``` + +### 6.2 Standard Background Processes + +#### 1. Automated Project Monitoring & Reminders Process +* **Trigger**: Scheduled cron job registered on `JobsWorker` startup. +* **Operational Cycle**: + 1. Job queries active projects and non-completed milestones across tenant partitions. + 2. Compares milestone contractual due dates against current UTC time: + - **T - 14 / T - 7 / T - 1 Days**: Emits reminder notification events to Grantee/Partner project managers. + - **T + 0 Days (Overdue)**: Automatically flags the milestone as `Overdue` (`FR-M07-009`) and updates project RAG status to `At Risk` (`FR-M04-017`). + 3. Escalates overdue notifications to internal Programme Managers for intervention. + +#### 2. Risk Monitoring & Automated Escalation Process +* **Trigger**: Periodic recurring schedule. +* **Operational Cycle**: + 1. Queries all active project risk registers and Key Risk Indicators (KRIs) (`FR-M09-001` - `011`). + 2. Evaluates current severity score ($\text{Risk Score} = \text{Likelihood} \times \text{Impact}$). + 3. If risk score breaches the configured programme tolerance threshold: + - Automatically logs a risk escalation event. + - Dispatches alerts to Risk Managers and Portfolio Executives. + 4. Sweeps assigned risk mitigation actions; identifies overdue mitigation plans and emits reminders to responsible assignees. + +#### 3. Email Notification Processing Workflow (`Platform.Email`) +* **Trigger**: High-frequency recurring polling job or deferred one-time dispatch. +* **Operational Cycle**: + 1. Queries pending notifications from the transactional database where `Platform == NotificationPlatform.Email` and `Status == NotificationStatus.Pending` or `Queued`. + 2. Resolves tenant context using `HeadlessTenantContextService`. + 3. Formats email templates with recipient credentials and branding. + 4. Transmits messages via MailKit/MimeKit SMTP engine with rate-limiting and connection pooling. + 5. On successful delivery: Updates record status to `Sent`, stamps `DeliveredAt` timestamp, and logs dispatch telemetry (`FR-M25-001`). + 6. On failure: Increments retry counter; if retry limit exceeded, marks as `Failed` with diagnostic error trace. + +### 6.3 Recurring-to-One-Time Job Cascading Pattern + +When a recurring job evaluates a broad batch of records (e.g., scanning thousands of project indicators or verifying compliance expirations), it must not execute long-running operations synchronously in the recurring job thread: +1. The recurring job identifies items requiring heavy processing (e.g. generating an executive PDF digest or calling external webhooks). +2. For each identified item, it enqueues an isolated one-time job via `IJobOrchestrator.SendAsync(specificEvent)`. +3. This releases the recurring job quickly, distributes execution across the 5 available worker threads in `PostFundManagement.Jobs`, and guarantees that a single failed sub-task can be retried independently without stalling the entire recurring cycle. + + diff --git a/README.md b/README.md index e8e88d9..bf112f1 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,16 @@ The application follows a modular, hierarchical architecture where high-level fu ### **5. Notifications & Platform Services** * Multi-channel delivery engine (`NotificationPlatform`) tracking candidate invitations, reminders, and status updates (`NotificationStatus`) across Email, SMS, WhatsApp, In-App, and Webhooks. +### **6. Background Processing & Distributed Jobs Engine** +* **`PostFundManagement.Jobs`**: Dedicated background execution host powered by **Quartz.NET (v4.6+)** with a PostgreSQL clustered JobStore (`pfm-scheduler`). +* **Decoupled Architecture**: `PostFundManagement.Api` serves strictly as a scheduler client (`ZeroSizeThreadPool`), dispatching events into the cluster while leaving execution exclusively to `PostFundManagement.Jobs` (`MaxConcurrency = 5`). +* **Event-Driven Execution**: Domain events implementing `IEvent : INotification` are mediated and serialized into `MediatorJob`. +* **Core Automated Operations**: + - **Automated Project Monitoring & Reminders**: Tracks milestones, evaluates planned vs actual deadlines, flags overdue statuses, and emits reminders to Grantees/Partners. + - **Risk Monitoring & Automated Notifications**: Evaluates risk registers, tracks Key Risk Indicators (KRIs), flags score creep, and escalates overdue mitigation actions. + - **Email Processing**: Asynchronously processes `NotificationPlatform.Email` records via MailKit/MimeKit SMTP. + - **Recurring & Cascading Jobs**: `JobsWorker` registers recurring cron schedules on startup and can trigger isolated one-time jobs when sub-activities cannot be executed directly by another job. + --- ## 🧩 Key System Enums @@ -68,6 +78,7 @@ The application follows a modular, hierarchical architecture where high-level fu * **Object Storage**: S3-compatible storage (Garage S3 / AWS S3) * **Caching & OutputCache Invalidation**: Redis StackExchange + ASP.NET Core `OutputCaching` (2-minute base policy) coupled with TanStack Query v5 and Axios client-side mutation versioning (`_v` parameter) for instantaneous reads and guaranteed zero-staleness on mutations * **Telemetry**: OpenTelemetry Metrics, Traces & Logs with Aspire / OTLP +* **Distributed Background Jobs**: Quartz.NET 4.6+ with clustered PostgreSQL JobStore (`pfm-scheduler`), separating the API (Scheduler Client with `ZeroSizeThreadPool`) from `PostFundManagement.Jobs` (Worker Execution Daemon with `MaxConcurrency = 5`) --- @@ -105,16 +116,18 @@ For in-depth architectural and operational specifications, see: ```text PostFundManagement/ - ├── PostFundManagement.Domain/ # Core Domain Entities, Enums & Interfaces - ├── PostFundManagement.Infrastructure/ # EF Core 10, PostgreSQL, Skoruba STS, Migrations - ├── PostFundManagement.Application/ # CQRS Handlers, DTOs, Business Rules & Validations - ├── PostFundManagement.Api/ # REST Endpoints, Middleware, Auth & Swagger/Scalar - ├── PostFundManagement.Tests/ # Unit & Integration Test Suite - ├── PostFundManagement.ConsoleUI/ # Internal Staff & Admin Web Portal (React 19 + Vite) - ├── PostFundManagement.PartnerUI/ # External Grantee / Recipient Portal (React 19 + Vite) - ├── Architecture.md # System Architecture, RBAC & Maker-Checker Spec - ├── Processes.md # Operational Grant Lifecycle & Process Flows - └── docker-compose.yml # Container orchestration + ├── PostFundManagement.Domain/ # Core Domain Entities, Enums, Value Objects & Abstractions (IEvent) + ├── PostFundManagement.Infrastructure/ # EF Core 10, PostgreSQL (Npgsql), Migrations, Data Protection + ├── PostFundManagement.Application/ # CQRS Handlers, Mediator, DTOs, Validations, S3, Email, Job Orchestrator + ├── PostFundManagement.Api/ # ASP.NET Core Minimal APIs / Carter, Auth (OIDC), Swagger/Scalar, Versioning + ├── PostFundManagement.Jobs/ # Dedicated Quartz.NET Background Worker Host (Recurring & Event-Driven Jobs) + ├── PostFundManagement.Tests/ # Unit & Integration Test Suite (xUnit, Coverlet) + ├── PostFundManagement.ConsoleUI/ # Internal Staff & Admin Web Portal (React 19 + Vite + Tailwind 4) + ├── PostFundManagement.PartnerUI/ # External Grantee / Recipient Portal (React 19 + Vite + WebGL OGL) + ├── Architecture.md # System Architecture, Security, RBAC, Caching & Quartz Spec + ├── Processes.md # Operational Grant Lifecycle, Maker-Checker & Background Workflows + ├── GEMINI.md # Master Technical & Architecture Roadmap (SRS v2.0 Alignment) + └── docker-compose.yml # Container orchestration for API and Frontends ``` ## 🚀 Getting Started