diff --git a/docs/architecture/application-workspace.md b/docs/architecture/application-workspace.md index 96ca0c3..20a366f 100644 --- a/docs/architecture/application-workspace.md +++ b/docs/architecture/application-workspace.md @@ -375,4 +375,9 @@ belongs to the AI cache — a reminder that the two systems are genuinely differ version history, documents (Phase 5.4). 5. ✅ Interview and follow-up — user-owned interview preparation, follow-up over the existing FollowUpAt and checklist, five more timeline event types (Phase 5.5). +6. ✅ Completion and product readiness — ownership audit, security review, full local verification + (Phase 5.6). See `docs/phase-5-completion-report.md`. + +**Phase 5 is feature-complete locally.** It is not deployed: CI is red for a documented environmental +reason and deployment is gated on it. See the completion report's *Remaining risks*. 7. Cover letter workflow. 8. Documents. 9. Interview preparation. 10. Dashboard improvements. diff --git a/docs/phase-5-completion-report.md b/docs/phase-5-completion-report.md new file mode 100644 index 0000000..73d2bf3 --- /dev/null +++ b/docs/phase-5-completion-report.md @@ -0,0 +1,147 @@ +# 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.