Files
jobtrackingapp/docs/phase-5-completion-report.md
T
cesnimda ce76046a29 feat: complete release readiness work
- consolidate API ownership and remove dead vendor code

- add Stripe billing, learning paths, and public CV hardening

- add migration, recovery, security, audit, and browser gates
2026-07-31 16:54:16 +02:00

8.2 KiB

Phase 5 — Intelligent Application Workspace: completion report

Historical snapshot (2026-07-19). Current release dependencies are tracked in BLOCKERS.md.

2026-07-19. What was built, what was decided, what is still risky, and what should come next. Companion to docs/architecture/application-workspace.md and docs/infrastructure/database-ownership.md.

Core principle

"The user should never ask: what should I do next?"

The overview answers it from the checklist — the first pending item in category priority order. Not a second ruleset, and not something that can recommend work the user already finished.

What shipped

Milestone Delivered
5.1 Foundation /applications/{id} route, nav shell, one aggregate overview read, next recommended action
5.2 Checklist Persisted ApplicationChecklistItem, auto-completion from readiness signals, custom items, reordering, dismissal; readiness refactored into a projection of it
5.3 Intelligence Timeline interpretation over JobEvent, structured job analysis, career matching with evidence — all deterministic and read-only
5.4 Assets CV variant association, tailoring suggestions, cover letter workflow with version history, documents
5.5 Interview & follow-up User-owned InterviewPrepItem, follow-up over the existing FollowUpAt + checklist, five more timeline event types
Follow-up Interview AI generation now receives the analysis and match context
Infrastructure Clean MariaDB initialization fixed; database ownership model documented

Architecture decisions

1. The checklist absorbed readiness rather than competing with it. /readiness keeps its DTO shape but now projects the checklist: score is completion percentage, completed/missing are live items by status. One workflow surface, two views. The alternative — two trackers kept in sync — was the thing the milestone brief explicitly forbade, and it would have drifted within a release.

2. Intelligence is deterministic; AI is a separate, explicit act. Analysis, matching, tailoring and timeline are computed from data the user already owns. The same input always gives the same number, so the score is checkable. Opening a section costs nothing and changes nothing. AI narrative stays in AiWorkspaceService, runs only when asked, and is versioned by the append-only AiInteraction history.

3. Suggestion-only is enforced by tests, not by convention. Match_never_writes_to_the_career_profile, Tailoring_never_writes_to_the_career_profile_or_the_variant, Generating_ai_history_does_not_create_prep_items, Existing_prep_items_are_never_overwritten_by_generation.

4. Two "interview prep" concepts exist, deliberately. InterviewPrepNote and AiWorkspaceNote are AI caches — they regenerate when their context signature changes. InterviewPrepItem is the user's durable content. Discovering that the first two would eventually overwrite anything a user typed is what forced the third. The DTO name InterviewPrepBoardDto exists because the cache already owned InterviewPrepDto.

5. Restore is always additive. Cover letter restore brings old text back as a new version, so what you restored from still exists. Nothing in this phase destroys history.

6. Reuse over new storage. Follow-up dates write JobApplication.FollowUpAt, the field RulesEngine and the reminder hosted service already read — so reminders work with no new wiring. Follow-up tasks are checklist items. Communication stayed entirely on Correspondence. Documents stayed on Attachment.

Ownership verification

Audited mechanically across all six Phase 5 services:

Service SaveChanges Reads profile Writes profile Writes variant content
ApplicationWorkspaceService 0 no no no
ApplicationChecklistService 6 yes no no
ApplicationTimelineService 0 no no no
ApplicationIntelligenceService 0 yes no no
ApplicationAssetsService 3 no no no
InterviewPrepService 4 no no no

The rules hold:

  • JobEvent — history source. Interpreted, never replaced, never written by the timeline service.
  • Checklist — workflow guidance. Owns completion state only.
  • Readiness — a projection of the checklist.
  • CareerProfile — source of truth. Read by two services, written by none.
  • CvVariant — derived output. The workspace moves a pointer; it never edits settings.
  • AI — assistant only. Appends to AiInteraction and nothing else.

Verification (local, 2026-07-19)

Check Result
Backend tests (Release) 379 passed
Frontend tests 128 passed, 36 suites
TypeScript clean
Frontend production build clean
Backend Docker image built
Frontend Docker image built
Fresh empty MariaDB 11 42 tables, starts, 0 exceptions
Existing populated MariaDB 42 tables, starts, rows preserved
Existing SQLite dev DB upgraded 34 → 44 tables, all rows preserved

Remaining risks

1. CI is red for an environmental reason — external blocker

Unresolved and outside the repository. The decisive evidence: run 531 on commit 8f73548 changed one markdown file — no code, no test, no dependency, no workflow — and its test job failed in the same duration band as every other run. A change that cannot affect compilation cannot fail a test job.

Everything above is therefore local verification only. Blocked on job logs (a read-scoped Gitea token), journalctl -u act_runner, and the runner container config. See docs/infrastructure/runner-investigation.md.

2. Production is behind — deployment blocker

Prod still runs the pre-Phase-4 build, because deployment is gated on CI. When it does deploy it will apply several no-op migrations and let the reconciler build the new tables. That path is verified on containers, not on your data. Take a database backup before the first deploy.

3. Authentication depends on a configuration flag — defence-in-depth gap

Authentication is enforced by a fallback policy that only activates when Auth:Require=true. It defaults to false, and docker-compose.yml hardcodes true, so every compose deployment is protected. But several core controllers — JobApplicationsController, CompaniesController, CorrespondenceController, RulesController, JobImportController — carry no explicit [Authorize], so a deployment that lost that flag would expose them entirely.

Every Phase 5 controller has an explicit [Authorize(AuthenticationSchemes = "local")] and is safe regardless. Adding explicit attributes to the older controllers is the right fix, but it changes local development behaviour (where the flag is off), so it is flagged rather than applied unilaterally.

PublicCvController is intentionally anonymous — it serves /cv/{slug} only for variants the user explicitly made public, and is noindex by default.

4. Prompt-quality is unmeasured

The interview module now receives the analysis and match context, and tests prove the context is present and correctly scoped. Whether it produces better questions is a judgement no test here makes.

Future improvements

This dated list is superseded by the current roadmap and debt ledger. Authorization and lifecycle event emission are implemented; the placeholder Portfolio/Notes workspace tabs were removed because projects belong in public CVs and application notes remain in the existing application editor.

  • Resolve the runner, then verify the full stack in CI and deploy. Everything else is downstream.
  • Explicit [Authorize] on the pre-Phase-5 controllers — implemented.
  • AI cover-letter actions — separate improve/shorten/expand actions remain intentionally unscoped; the shipped flow generates by tone and keeps user editing/version history authoritative.
  • Portfolio section — removed; public CV projects/custom sections cover the current scope.
  • Emit the new lifecycle events — implemented through JobLifecycleEvents.
  • Checklist GET writes on read (seeding + auto-completion sync). Correct and idempotent, but it makes a GET non-cacheable; worth revisiting if the workspace ever needs to scale reads.