Files
jobtrackingapp/docs/remaster/BUG_REPORT.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

52 lines
4.4 KiB
Markdown

# Bug Report — Job Tracker
**Companion to:** [SYSTEM_AUDIT_REPORT.md](SYSTEM_AUDIT_REPORT.md)
Severity: **Critical / High / Medium / Low.** Each item is code-grounded or explicitly `[Speculative]`.
"Speculative" = a plausible defect I did not fully reproduce; verify before fixing.
## Critical
_None found._ No auth bypass, no tenant-isolation break, no RCE/SSRF hole surfaced in the audited paths.
(Auth uses HttpOnly cookie + CSRF; tenancy uses global query filters; ingestion has SSRF defence.) This is
itself strong evidence against "rebuild".
## High
| ID | Tag | Location | Description | Fix |
|----|-----|----------|-------------|-----|
| H-1 | [Bug] | `Models/JobApplication.cs:28-31` + attachment write paths | Denormalised `HasResume/HasCoverLetter/HasPortfolio/HasOtherAttachment` can drift from the actual `Attachments` collection, so the checklist UI can show a resume attached when none is, or vice-versa. | Make them computed projections, or maintain via one domain method; add a test. |
| H-2 | [Design flaw] | `JobApplication.TailoredCvText` vs `TailoredCvDraft` | Two writable representations of the tailored CV with no precedence rule → stale-content reads. | Pick `TailoredCvDraft`, deprecate the inline string. |
| H-3 | [Perf/Bug] | `Data/JobTrackerContext.cs` (indexes) | Missing indexes on `IsDeleted`, `FollowUpAt`, child FKs → full scans on every list/board/reminder/analytics query; degrades non-linearly with data. | Add the 5 hot-path indexes. |
| H-4 | [Perf] | `JobApplicationsController.GetStats` (~:1848), `GetAnalyticsOverview` (~:2851) | Loads the whole table into memory then filters/`GroupBy().Count()` in .NET. | Aggregate server-side (EF `GroupBy`/`CountAsync`). |
## Medium
| ID | Tag | Location | Description | Fix |
|----|-----|----------|-------------|-----|
| M-1 | [Perf/Bug] | `GmailController` :646-657, :701-711, :893-918 | N+1 loops: per-message `AnyAsync` in `CreateSuggestedJob`; redundant re-loop after a HashSet is already built in `RelinkThread`; message-by-message import in `RefreshLinkedThreads`. | Batch with a single set-based query. |
| M-2 | [Bug] | `GmailController` :659, :713 | `GmailReviewDecisions` loaded with `ToListAsync` then scanned where `FirstOrDefaultAsync` suffices. | Use `FirstOrDefaultAsync`. |
| M-3 | [Design flaw] | `tools/summarizer/app.py:469,527,581` | Prompt injection via raw interpolation of scraped JD + instruction (capped by human-review boundary). | Delimit untrusted inputs; add factuality check. |
| M-4 | [Design flaw] | `JobCvMatchService` | "Match score" is keyword-literal; mislabels semantic matches as gaps. | Relabel + synonym map. |
| M-5 | [Design flaw] | job import UX | Scrape failure silently degrades to manual entry with no explanation/pre-fill. | Explicit partial-parse state. |
| M-6 | [Speculative] | repeated `RulesEngine.GetSettings` across list/detail/reminders | Same per-user settings re-read many times per request cycle. | Cache in the already-registered `IMemoryCache` (short TTL). |
| M-7 | [Speculative] | JS-rendered boards | Static `HttpClient` fetch returns hydration-only HTML → empty parse. | Document limitation; optional headless fetch. |
## Low
| ID | Tag | Location | Description |
|----|-----|----------|-------------|
| L-1 | [Design flaw] | `JobApplication.Salary` (free-text) + structured salary | Two salary representations; ensure writes keep them consistent or drop free-text after backfill. |
| L-2 | [Architectural weakness] | `JobTrackerBackend` link-compile glob | Non-obvious build layout; onboarding hazard. |
| L-3 | [Design flaw] | `Tags`/`*Json` stored as JSON strings | Unqueryable; fine for SQLite, revisit on MySQL/Postgres. |
| L-4 | [Speculative] | scraper plugins | Silent rot with no health telemetry. |
## Cross-reference with `.gsd`
- `.gsd` D007/D008 (Gmail full-thread continuity) is **implemented** — not a bug, a delivered decision.
- `.gsd` D006 (notes-block workaround) is a *known* UX debt the register itself flags — Medium, schema fix.
- `.gsd/OVERRIDES.md` "use next.js" is **unimplemented** — a plan/impl divergence, not a runtime bug, but it
means the recorded frontend decision and the shipped stack disagree. Resolve deliberately.
## Notes on what is NOT broken (verified, to prevent false alarms)
- Auth token is **not** in localStorage/sessionStorage (asserted by `login-page.test.tsx:70-71`).
- SSRF blocklist covers IPv4 private/CGNAT/link-local/benchmark + IPv6 ULA/link-local/Teredo.
- Match scoring is deterministic — no AI in the trust-bearing number.