Deep, code-grounded audit of Job Tracker producing the mission deliverables under docs/remaster/: system audit, bug report, architecture/data-model/AI/UX reviews, remaster proposal, migration plan, competitor research, and the gated REBUILD_DECISION. Verdict: Incremental Refactor (no full rebuild). Evidence: no Critical defects; hardened cookie/CSRF auth (token never in JS storage), real SSRF defence, enforced multi-tenancy via global query filters, decoupled provider-swappable AI service, 135 backend tests. Debt is localised (god controllers/entity, missing hot-path indexes, prompt-injection hardening, CRA build debt) and reachable by in-place, test-guarded refactors. Also harden .gitignore: exclude agent tooling (.claude/, .bg-shell/, .agent.md) and restore/broaden the runtime-secrets block (**/keys/, **/backups/, exports, CV artifacts) so nested DataProtection keys can't be committed accidentally. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.3 KiB
Architecture Review — Job Tracker
Companion to: 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
OwnerUserIdinData/JobTrackerContext.cs. Centralised, hard to bypass accidentally, covered byJobApplicationsAuthorizationTests. - 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,EmailStatusClassifierare 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.