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>
4.3 KiB
Data Model Review — Job Tracker
Companion to: SYSTEM_AUDIT_REPORT.md
1. Entities (verified in Models/)
JobApplication, Company, Correspondence, Attachment, JobEvent, TailoredCvDraft(+Json),
StructuredCvProfile(+Json), CvExtraction, GmailConnection, GmailReviewDecision, RuleSettings,
UserRuleSettings, HumanLanguageCatalog, SystemEmailSettings, ApplicationUser.
2. JobApplication — the god entity [Design flaw]
~40 columns spanning eight distinct concerns on one row:
- Identity/ownership (
Id,OwnerUserId) - Core role (
JobTitle,CompanyId,Status,DateApplied,Location) - Salary — both free-text (
Salary) and structured (SalaryMin/Max/Currency/Period) - Workflow (
NextAction,FollowUpAt,FeedbackRequestedAt,RecruiterMessageDraft) - Denormalised attachment flags (
HasResume,HasCoverLetter,HasPortfolio,HasOtherAttachment) - Soft delete (
IsDeleted,DeletedAt) - Imported content (
Description,TranslatedDescription,DescriptionLanguage,Tags,Deadline,ShortSummary) - Tailored CV — both inline (
TailoredCvText,TailoredCvUpdatedAt) and related (TailoredCvDraft)
2.1 Denormalisation hazard [Bug risk — High]
HasResume/HasCoverLetter/HasPortfolio/HasOtherAttachment duplicate information already derivable from the
Attachments collection. Any code path that adds/removes an attachment without updating the boolean (or
vice-versa) produces a silent inconsistency that the attachment-checklist UI will display wrong. These
booleans should be computed projections, not stored state. If kept for query performance, they must be
maintained in one place (a domain method) — verify no controller mutates them independently.
2.2 Dual tailored-CV source of truth [Design flaw — High]
TailoredCvText (string on JobApplication) vs TailoredCvDraft/TailoredCvDraftJson (related entities).
Two writable representations of "the tailored CV for this job" with no documented precedence. This is a
classic bug incubator: read one, write the other, and the workspace shows stale content.
2.3 CV "versioning" is not modelled [Design flaw — Medium]
Product step 8 promises "CV version is linked to job." The schema stores a single current tailored
text per job, not a version history. There is no CvVersion table with immutable snapshots. The
promised capability is only partially real. If versioning matters (it should, for A/B and audit), model it
explicitly: CvVersion(id, ownerUserId, sourceProfileId, jobApplicationId?, content, createdAt, label).
3. Relationships
JobApplication *→1 Company(FKCompanyId) — fine.JobApplication 1→* Correspondence / Attachment / JobEvent— fine, but FK columns are unindexed (Correspondence.JobApplicationId,JobEvent.JobApplicationId) → N+1 and slow joins.Correspondence.ExternalThreadIdpowers Gmail continuity (D007/D008) — good, but unindexed.
4. Indexing [Performance — High]
Only OwnerUserId is indexed. Every list/board/reminders/analytics query filters on IsDeleted
(unindexed), reminders/background jobs filter on FollowUpAt (unindexed), and detail loads join on the
unindexed child FKs. Add: IsDeleted, (IsDeleted, Status), FollowUpAt,
Correspondence.JobApplicationId, JobEvent.JobApplicationId. SQLite- and MySQL-safe.
5. Tags/JSON-as-string [Design flaw — Low]
Tags is a JSON-array string; TailoredCvDraftJson/StructuredCvProfileJson are JSON blobs. Workable
with EF value converters, but unqueryable. Acceptable given SQLite; revisit if moving fully to MySQL/Postgres
(use native JSON columns).
6. Recommended target schema (incremental)
- Split
JobApplicationintoJobApplication(core+workflow) +JobImportContent(description/translation/ summary/tags) — a 1:1 owned entity — so wide read paths don't drag import blobs. - Make attachment booleans computed (drop stored columns after a migration + backfill check).
- Pick one tailored-CV representation (
TailoredCvDraft) and deprecateTailoredCvText. - Introduce
CvVersionfor real versioning. - Add the five hot-path indexes (do this first — highest value, lowest risk).
All five are additive/behaviour-preserving migrations guarded by the existing test suite.