f0f178d77e
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>
52 lines
4.4 KiB
Markdown
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.
|