Updated documentation

This commit is contained in:
2026-10-28 09:28:06 +02:00
parent b8c3201c58
commit 6114d8f457
4 changed files with 260 additions and 23 deletions
+131 -5
View File
@@ -22,12 +22,13 @@ The PFM platform is constructed using **Clean Architecture** and **Domain-Driven
│ API GATEWAY │ │ API GATEWAY │
│ PostFundManagement.Api (ASP.NET Core Minimal APIs / Carter) │ │ PostFundManagement.Api (ASP.NET Core Minimal APIs / Carter) │
│ Authentication: JWT Bearer + OpenTelemetry Observability │ │ Authentication: JWT Bearer + OpenTelemetry Observability │
│ Quartz Client: Scheduler-Only (ZeroSizeThreadPool / Enqueues Jobs) │
└───────────────────────────────────┬────────────────────────────────────┘ └───────────────────────────────────┬────────────────────────────────────┘
│ │
┌───────────────────────────────────▼────────────────────────────────────┐ ┌───────────────────────────────────▼────────────────────────────────────┐
│ APPLICATION LAYER │ │ APPLICATION LAYER │
│ PostFundManagement.Application │ │ 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.│ │ PostFundManagement.Domain │ │ PostFundManagement.Infrastr.│
│ Entities, Value Objects, Enums, │ │ PostgreSQL (EF Core 10), │ │ Entities, Value Objects, Enums, │ │ PostgreSQL (EF Core 10), │
│ Domain Invariants, Audit Models │ │ Skoruba STS, S3 Garage, │ │ 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 flowchart LR
A["Maker (User A)<br/>Creates Record"] --> B["Record Persisted<br/>with createdBy = User A"] A["Maker (User A)<br/>Creates Record"] --> B["Record Persisted<br/>with createdBy = User A"]
B --> C{"Checker Evaluation<br/>(Approver Login)"} B --> C{"Checker Evaluation<br/>(Approver Login)"}
C -->|Checker == User A| D["Approval BLOCKED<br/>Maker-Checker Violation"] C -->|"Checker is User A"| D["Approval BLOCKED: Maker-Checker Violation"]
C -->|Checker == User B<br/>with 'update' claim| E["Approval GRANTED<br/>Status -> Approved"] C -->|"Checker is User B with update claim"| E["Approval GRANTED: Status Approved"]
``` ```
### 4.2 Record Attribution Model ### 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. * **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. * **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<TNotification> │
│ * 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<TNotification>(notification)`: Schedules a durable, one-time execution in the `onetime-jobs` group with `StartNow()` and `DisallowConcurrentExecution()`.
- `ScheduleAsync<TNotification>(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<TNotification>`)**:
`MediatorJob<TNotification>` 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.
+19 -1
View File
@@ -71,6 +71,7 @@ PostFundManagement/
├── PostFundManagement.Infrastructure/ # EF Core 10, PostgreSQL (Npgsql), Migrations, Data Protection ├── PostFundManagement.Infrastructure/ # EF Core 10, PostgreSQL (Npgsql), Migrations, Data Protection
├── PostFundManagement.Application/ # CQRS Handlers, Mediator, DTOs, Validations, S3, Email, Quartz ├── PostFundManagement.Application/ # CQRS Handlers, Mediator, DTOs, Validations, S3, Email, Quartz
├── PostFundManagement.Api/ # Minimal APIs / Carter endpoints, Auth, Swagger/Scalar, Versioning ├── 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.Tests/ # Unit & Integration test suite (xUnit, Coverlet)
├── PostFundManagement.ConsoleUI/ # Internal Staff & Admin Web Portal (React 19 + Vite 6 + Tailwind 4) ├── 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) ├── 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.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.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.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. | | **`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) ### 2.2 Frontend Projects (React 19 / TypeScript)
@@ -120,6 +122,22 @@ PostFundManagement/
* **Authentication / STS**: Duende IdentityServer / Skoruba Administration (`https://sts.security.khongisa.co.za`). * **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. * **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<TNotification>` 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) ## 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. * **Implementation**: Managed by `HashService` (`PostFundManagement.Application/Services/Shared/HashService.cs`) using the `Hashids.net` (v1.7.0) library.
* **Shared Configuration (`HasherSettings`)**: * **Shared Configuration (`HasherSettings`)**:
* **Salt**: Sourced from configuration (`HasherSettings:Salt` or `HASHER_SALT`), defaulting to: * **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`). * **Min Hash Length**: `HasherSettings:MinHashLength` or `HASHER_MIN_HASH_LENGTH` (default: `11`).
* **Core Service Methods**: * **Core Service Methods**:
* `HashEncodeLongId(long id)` / `HashEncodeIntId(int id)`: Encodes integers into obfuscated HashID strings (e.g. `1` $\to$ `kY5aB9m...`). * `HashEncodeLongId(long id)` / `HashEncodeIntId(int id)`: Encodes integers into obfuscated HashID strings (e.g. `1` $\to$ `kY5aB9m...`).
+86 -6
View File
@@ -88,6 +88,16 @@ The PFM platform governs the lifecycle of grant, development, innovation, and im
4. Resolution: `Approved & Enacted` or `Rejected`. 4. Resolution: `Approved & Enacted` or `Rejected`.
5. **Maker-Checker Rule**: Change request initiator cannot approve or enact the change. 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<TNotification>` 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 ## 3. The Maker-Checker Operational Policy
@@ -139,20 +149,20 @@ To ensure that caching never impedes record lifecycle updates or displays stale
sequenceDiagram sequenceDiagram
autonumber autonumber
actor User as User / Maker actor User as User / Maker
participant UI as ConsoleUI (React Query) participant UI as ConsoleUI React Query
participant Client as Axios HttpClient participant Client as Axios HttpClient
participant OutputCache as Backend OutputCache (Redis/Memory) participant OutputCache as Backend OutputCache Redis/Memory
participant DB as PostgreSQL Database 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 UI->>Client: Dispatches POST / PUT / PATCH / DELETE
Client->>DB: Executes Mutation in Database Client->>DB: Executes Mutation in Database
DB-->>Client: Returns 200 OK / 201 Created DB-->>Client: Returns 200 OK / 201 Created
Client->>Client: Interceptor detects mutation: bumps activeMutationEpoch 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 UI->>Client: Refetches active queries with _v = activeMutationEpoch
Client->>OutputCache: GET /v1/api/*?_v=<newEpoch> Client->>OutputCache: GET /v1/api/* with new _v epoch
Note over OutputCache: Cache Miss (new _v key never seen before) Note over OutputCache: Cache Miss: new _v key never seen before
OutputCache->>DB: Queries PostgreSQL for fresh data OutputCache->>DB: Queries PostgreSQL for fresh data
DB-->>OutputCache: Returns updated entities DB-->>OutputCache: Returns updated entities
OutputCache-->>UI: Caches fresh data under _v and returns to UI 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. 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. 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.
+23 -10
View File
@@ -41,6 +41,16 @@ The application follows a modular, hierarchical architecture where high-level fu
### **5. Notifications & Platform Services** ### **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. * 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<TNotification>`.
* **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 ## 🧩 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) * **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 * **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 * **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 ```text
PostFundManagement/ PostFundManagement/
├── PostFundManagement.Domain/ # Core Domain Entities, Enums & Interfaces ├── PostFundManagement.Domain/ # Core Domain Entities, Enums, Value Objects & Abstractions (IEvent)
├── PostFundManagement.Infrastructure/ # EF Core 10, PostgreSQL, Skoruba STS, Migrations ├── PostFundManagement.Infrastructure/ # EF Core 10, PostgreSQL (Npgsql), Migrations, Data Protection
├── PostFundManagement.Application/ # CQRS Handlers, DTOs, Business Rules & Validations ├── PostFundManagement.Application/ # CQRS Handlers, Mediator, DTOs, Validations, S3, Email, Job Orchestrator
├── PostFundManagement.Api/ # REST Endpoints, Middleware, Auth & Swagger/Scalar ├── PostFundManagement.Api/ # ASP.NET Core Minimal APIs / Carter, Auth (OIDC), Swagger/Scalar, Versioning
├── PostFundManagement.Tests/ # Unit & Integration Test Suite ├── PostFundManagement.Jobs/ # Dedicated Quartz.NET Background Worker Host (Recurring & Event-Driven Jobs)
├── PostFundManagement.ConsoleUI/ # Internal Staff & Admin Web Portal (React 19 + Vite) ├── PostFundManagement.Tests/ # Unit & Integration Test Suite (xUnit, Coverlet)
├── PostFundManagement.PartnerUI/ # External Grantee / Recipient Portal (React 19 + Vite) ├── PostFundManagement.ConsoleUI/ # Internal Staff & Admin Web Portal (React 19 + Vite + Tailwind 4)
├── Architecture.md # System Architecture, RBAC & Maker-Checker Spec ├── PostFundManagement.PartnerUI/ # External Grantee / Recipient Portal (React 19 + Vite + WebGL OGL)
├── Processes.md # Operational Grant Lifecycle & Process Flows ├── Architecture.md # System Architecture, Security, RBAC, Caching & Quartz Spec
└── docker-compose.yml # Container orchestration ├── 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 ## 🚀 Getting Started