# Technical debt

Last reconciled: 2026-08-15

This ledger contains verified engineering debt only. Product ideas belong in the roadmaps and
operator/external dependencies belong in `BLOCKERS.md`.

## Resolved in the 2026-07-31 debt pass

- Removed the 232 MB unused `vendor/saasable-ui-main` snapshot.
- Retired the link-compiling `JobTrackerBackend` project. The API now owns its controllers,
  services, models, and `JobTrackerContext`; tests and tools reference the API directly.
- Added standard Problem Details responses with trace IDs and structured JSON production logs.
- Made trusted reverse-proxy headers explicit so rate limits see the real client address in the
  production nginx topology.
- Made queued CV processing recover queued/running database work after restart and bypass the
  request-only tenant filter with explicit owner checks.
- Pruned expired CV extraction runs and orphaned upload artifacts while preserving the current CV.
- Added configurable PDF export retention (`CvExports:RetainDays`, default 30 days).
- Corrected the remaining CRA-era environment-variable names after the Next.js migration.
- Removed React Router and `act(...)` warnings from the frontend tests.
- Replaced placeholder documentation stubs with concise, code-linked guidance.
- Removed stale locals and reconciled old TODO claims against the implemented code.
- Added a CI-gated Playwright smoke suite for login/session cookies, saved-job creation, Career
  Workspace routing, and anonymous public-CV rendering/PDF download.
- Updated Axios and Next.js and overrode Next's vulnerable bundled Sharp/PostCSS versions; the only
  remaining npm findings are the non-applicable/mitigated React Router items in `BLOCKERS.md`.
- Pinned SQLitePCLRaw 2.1.12 so the native SQLite runtime is no longer in the high-severity
  CVE-2025-6965 range; the NuGet transitive vulnerability audit is clean.
- Made fresh SQLite startup reconcile schema-owned columns between historical EF migrations, so
  strict SQLite identifier handling no longer breaks the later table rebuild.
- Updated Jest and Playwright, pinned the patched transitive brace-expansion package, and promoted
  the full npm high-severity audit to a blocking CI gate.
- Corrected Chromium PDF export argument handling, bounded hung exports, and verified the returned
  public artifact is a real PDF in the browser smoke suite.
- Sandboxed authenticated CV preview iframes without enabling scripts, isolated public-PDF request
  budgets by client and slug, and removed/ignored the tracked expired acceptance JWT artifact.
- Replaced the admin user list's per-user role lookup with a fixed two-query relational projection,
  and moved the Job email UI to an additive paged inbox endpoint while preserving the legacy route.
- Removed unfinished Portfolio/Notes workspace navigation promises; existing project, attachment,
  and application-note surfaces remain authoritative, and stale section links fall back to Overview.

## Remaining engineering debt

| Priority | Debt | Current decision / trigger |
|---|---|---|
| P1 | EF migrations and startup reconciliation still share historical schema ownership. Blank SQLite EF migration, populated upgrade, and fresh/restarted MariaDB now pass, but the two mechanisms remain tightly coupled. | Keep the compatibility bootstraps and provider-specific ordering covered by `MigrationChainTests`. Consolidate ownership only through an expand/verify/contract migration after a production restore rehearsal; do not rewrite applied migration history. |
| P1 | `JobApplication` still duplicates opportunity data now owned by `Job`. | Startup now backfills missing `Job` rows and both create paths dual-write. Keep compatibility reads until the production report and restore rehearsal pass; observe one release, then remove the legacy columns. |
| P1 | Background workers assume one API instance. Restart recovery is durable, but there is no row lease for concurrent workers. | Add database leasing only before deploying more than one backend replica. |
| P2 | Production log aggregation is still deployment-owned; Compose now bounds each container's local logs to 3 × 10 MB. | Add an OTLP/Seq sink only before multi-host operation or when incident-response needs exceed `docker logs`. |
| P3 | `Tags` remains a JSON string and attachment-purpose booleans remain compatibility columns. | Normalize tags only when server-side tag querying becomes slow. Remove attachment flags only with an API/schema compatibility release; recomputation tests currently prevent drift. |
| P3 | Checklist reads seed and synchronize system-generated items, so the GET is intentionally non-cacheable. | Split read/write paths only if workspace read volume or caching makes the current idempotent behavior measurably costly. |
| P3 | Several orchestration files are large (`JobApplicationsController`, `StartupInitializationExtensions`, `JobDetailsDialog`). | File length alone is not a defect. Extract a cohesive slice only when the next behavior change touches it; avoid a standalone rewrite. |

## Deliberate non-debt

- Free-text pipeline status is intentional so custom values are not destroyed.
- Next.js is the static build shell and React Router owns client navigation; changing routers has no
  demonstrated user benefit.
- The CV queue is intentionally process-local for the current single-replica deployment.
- User-local untracked files at the repository root were not removed or modified.
