Files
jobtrackingapp/docs/remaster/MIGRATION_PLAN.md
T
cesnimda f0f178d77e docs(remaster): full-system audit + rebuild-vs-refactor decision
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>
2026-07-05 10:04:03 +02:00

68 lines
4.6 KiB
Markdown

# Migration / Remaster Plan — Job Tracker
**Companion to:** [REMASTER_PROPOSAL.md](REMASTER_PROPOSAL.md) · **Decision:** [REBUILD_DECISION.md](REBUILD_DECISION.md)
Strategy: **incremental, test-guarded, feature-branch per unit** (matches `.gsd` D017 slice discipline and
the project's no-direct-main / conventional-commit rule). The 135 backend integration tests + 23 frontend
suites are the safety net that makes internal change low-risk. **No big-bang.**
## Guardrails per slice
1. Branch off `main`; conventional commit; no direct main pushes; no auto-merge.
2. `dotnet build -c Release` + `dotnet test JobTrackerApi.Tests` green **before** commit.
3. Frontend: full Jest suite green.
4. One PR per slice → one CI run on the Pi (single-capacity runner).
5. Behaviour preserved; add a targeted test if a slice exposes a coverage gap.
## Wave 1 — Performance (lowest risk, highest ROI) — *this was the paused Phase 7 work*
- **P1. Hot-path indexes** (`Data/JobTrackerContext.cs` + one migration): `IsDeleted`, `(IsDeleted,Status)`,
`FollowUpAt`, `Correspondence.JobApplicationId`, `JobEvent.JobApplicationId`. SQLite+MySQL safe.
- **P2. Server-side aggregation** for `GetStats`/`GetAnalyticsOverview` (no full-table `ToListAsync`).
- **P3. Gmail N+1 batch fixes** (:646, :701, :893) + `FirstOrDefaultAsync` for review decisions.
- **P4. `RuleSettings` cache** in `IMemoryCache` (short TTL, per user).
- **AI provider router** in `app.py` (`AI_PROVIDER={ollama|gemini|groq}`) + `/health` reports provider;
default stays `ollama` (keyless). Prod `.env` sets `AI_PROVIDER=gemini` + rotated key → offloads the 1060.
## Wave 2 — Safe refactors (behaviour-preserving)
- **R1. Extract services** from `JobApplicationsController`: `AnalyticsService`, `JobStatsService`,
`CvContextBuilder`. Controller shrinks to a thin adapter.
- **R2. Extract** `GmailImportService` + `GmailThreadRefresher` from `GmailController`.
- **R3. DTO extraction** for `JobApplicationsController`/`ProfileCvController`/`GmailController`.
- New files under `Controllers/`/`Services/` so the `JobTrackerBackend` glob picks them up; no `Program.cs`
DI churn beyond registering the new services.
## Wave 3 — Data-model evolution (additive migrations + backfill)
- **D1. Attachment booleans → computed.** Migration + backfill verification test; then drop stored columns.
- **D2. Single tailored-CV source.** Migrate `TailoredCvText``TailoredCvDraft`; deprecate the string.
- **D3. Split `JobImportContent`** 1:1 off `JobApplication`.
- **D4. `CvVersion` + `CoverLetter`** first-class tables (enables real versioning promised by the product).
Each is a reversible EF migration; run against a SQLite dev DB and a MariaDB staging copy before prod.
## Wave 4 — AI hardening + UX
- **A1.** Prompt-injection delimiters + input normalisation; factuality diff vs `StructuredCvProfile`.
- **A2.** Match-score synonym map + relabel; matched/missing breakdown in the UI.
- **U1.** Import partial-parse state; dedicated application-answer field; AI-fabrication confirm UI.
## Wave 5 — Frontend platform (decide first)
Resolve the `.gsd` "use next.js" override deliberately:
- **Least churn:** CRA → **Vite** (drops most transitive-vuln debt, keeps React/MUI, fast).
- **If public/SEO product:** **Next.js** (honours the override; SSR/routing/metadata) — larger effort.
Do this as its own milestone, not coupled to backend work.
## Risk assessment
| Risk | Likelihood | Mitigation |
|---|---|---|
| Migration data loss (Wave 3) | Low | additive + backfill + staging dry-run on MariaDB copy + backups (already automated) |
| Behaviour regression in extraction | Low | 135 integration tests lock the API contract |
| Single-runner CI bottleneck | Medium | one PR per slice; keep slices small |
| Provider-router auth leak | Low | key from env only; never logged/committed; rotate the pasted key |
| Frontend migration churn | Medium | isolate as its own milestone; feature-flag if needed |
## Preserve vs discard
- **Preserve unchanged:** auth (cookie+CSRF), SSRF ingestion guard, global query filters, deterministic
services, AI HTTP boundary, background-service model (single-node), test suites, deploy pipeline.
- **Refactor before reuse:** the three god controllers, `JobApplication` entity, prompt construction.
- **Discard:** attachment boolean columns (after backfill), inline `TailoredCvText`/`CoverLetterText`
strings (after migration), scraper reliance as a *guarantee* (keep as best-effort).
- **`.gsd` logic:** treat as historical design intent (already mostly realised); resolve the two open items
(next.js override, notes-block workaround). The `.gsd` folder is **git-ignored** and stays out of the repo.