# Architecture Review — Job Tracker **Companion to:** [SYSTEM_AUDIT_REPORT.md](SYSTEM_AUDIT_REPORT.md) ## 1. Current topology (as built, verified) ``` React 19 / TS / MUI 7 (CRA) ──HTTP(cookie+CSRF)──▶ ASP.NET Core API (net9.0, EF Core 9) job-tracker-ui/ JobTrackerApi/ (+ JobTrackerBackend link-compile) │ ┌─────────────────────┼───────────────────────┐ ▼ ▼ ▼ EF Core / SQLite|MySQL Hosted services HttpClient ──▶ FastAPI AI svc (global query filters) (reminders, rules, tools/summarizer/ enrichment, export, Ollama | distilbart backup) (provider-swappable) ──▶ Gmail API (OAuth), LibreTranslate ``` ## 2. What is genuinely good (keep) - **AI service decoupling `[strength]`.** The .NET side (`SummarizerService`, `CvAiClassifier`, `CvAiNormalizer`) only speaks HTTP to the FastAPI service. Swapping Ollama→Gemini/Groq is a change in *one* Python file with *zero* .NET edits. This is textbook boundary placement. - **Multi-tenancy via global query filters** on `OwnerUserId` in `Data/JobTrackerContext.cs`. Centralised, hard to bypass accidentally, covered by `JobApplicationsAuthorizationTests`. - **SSRF-safe ingestion** (`JobImport/JobImportService.cs:133-210`): scheme allowlist, loopback/private/ CGNAT/link-local/IPv6-ULA blocklist *after DNS resolution*, redirect-averse fetch, 4 MB cap. - **Deterministic domain services** — `JobCvMatchService`, `JobPipeline`, `StageAnalytics`, `EmailStatusClassifier` are small, pure, unit-testable. This is the model the controllers should follow. - **Provider-agnostic persistence** — SQLite default, Pomelo MySQL/MariaDB for prod. ## 3. Architectural weaknesses ### 3.1 God controllers `[Architectural weakness]` — highest impact `JobApplicationsController` = **3,271 lines**, `ProfileCvController` = **2,265**, `GmailController` = **1,179**. These are transaction scripts: they hold orchestration, validation, AI-context assembly, persistence, and DTO shaping inline. Consequences: untestable in isolation, merge-conflict magnets, duplicated `RulesEngine.GetSettings` calls, and read paths that load whole tables then filter in memory. **Fix:** extract cohesive services (`JobStatsService`, `AnalyticsService`, `CvContextBuilder`, `GmailImportService`, `GmailThreadRefresher`) + DTO files. The 135 integration tests make this safe. ### 3.2 Build-layout footgun `[Architectural weakness]` Controllers/services compile through a **separate `JobTrackerBackend` library** that globs `../JobTrackerApi/Controllers/**/*.cs` and `../Services/**/*.cs`, *not* through `JobTrackerApi.csproj`. New files "just compile" from the right folder — invisible magic that will confuse every new contributor. **Fix:** document loudly (done in CLAUDE/README) or collapse the split; not urgent. ### 3.3 Polling background services, no event bus `[Design flaw, low severity]` Reminders/rules/enrichment run on timers. Fine for a single node and a personal/low-tenant load; would need an outbox/queue if this becomes real multi-tenant SaaS. Not a problem *today*. ### 3.4 Frontend build platform `[Architectural weakness]` CRA / `react-scripts 5` is EOL-ish and carries transitive-vuln debt (`.gsd` D019 remediated only the direct `axios` finding and explicitly deferred the framework migration). **And** `.gsd/OVERRIDES.md` records an **active** directive *"use next.js"* (2026-04-10) that was **never executed**. So the shipped stack contradicts the last recorded frontend decision. Resolve intentionally (Vite for least churn, or Next.js per the override if SSR/SEO for a public product matters). ### 3.5 Scraper-plugin fragility `[Architectural weakness]` HTML-structure-coupled plugins against adversarial targets (LinkedIn/Indeed) will rot. No plugin-health metric, so failures are silent (fall back to universal parser or manual). **Fix:** health telemetry + lean on the already-solid manual fallback; treat scraping as best-effort, not a guarantee. ## 4. Service-boundary map (target) | Concern | Today | Target owner | |---|---|---| | Job CRUD | `JobApplicationsController` | thin controller → `JobApplicationService` | | Stats/analytics | inline in controller (load-all) | `AnalyticsService` (server-side aggregation) | | CV context assembly | inline in `JobApplicationsController`/`ProfileCvController` | `CvContextBuilder` | | Gmail import/refresh | `GmailController` (N+1) | `GmailImportService` + `GmailThreadRefresher` | | Rule settings | repeated `RulesEngine.GetSettings` | cache in `IMemoryCache` (already registered) | ## 5. Verdict The **skeleton is correct**; the muscle is in the wrong place (controllers). This is the signature of a system that grew feature-first, not of one that is architecturally unsound. Refactor, do not rebuild.