# Phase 5 — Intelligent Application Workspace: completion report > 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 - **Resolve the runner**, then verify the full stack in CI and deploy. Everything else is downstream. - **Explicit `[Authorize]`** on the pre-Phase-5 controllers, paired with a dev-mode decision. - **AI cover-letter actions** — improve, shorten, expand, tone, tailor. `CoverLetterVersion.AiAction` already models them; only the modes and UI are missing. - **Portfolio section** (workspace milestone 8) is still a placeholder. - **Emit the new lifecycle events.** `InterviewScheduled`, `InterviewCompleted`, `OfferReceived`, `FollowUpCompleted` are *interpreted* by the timeline but only `FollowUpSet` and `StatusChanged` are currently emitted. Wiring the emitters is small and would make the timeline noticeably richer. - **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.