ce76046a2957fef327f70faef578afbcceda4338
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5c5a572cfc |
docs(ops): record release-candidate audit findings
Adds the two issues found and fixed during the release-candidate audit to release-candidate-review.md: the follow-up reminder index that never created on MariaDB (fix in the preceding commit), and the nondeterministic timeline day-grouping test. Corrects database-ownership.md drift: the MariaDB startup scenarios now report 42 tables (measured in every scenario this audit), not the stale 40 from before the last Phase 5 tables were added, and adds the partially-migrated heal scenario (35 -> 42) that was verified. All claims reflect behaviour verified in this audit: 420 tests on Windows and Linux in both ICU modes, all three Docker images built, four database startup scenarios against live MariaDB 11 and SQLite, and a full backup -> restore -> app-start cycle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
3d74baef78 |
feat(workspace): interview and follow-up workflow
Phase 5.5. Completes the lifecycle after submission: prepare, communicate, chase. Interview preparation gets a durable, user-owned store. There were already two per-application AI stores, InterviewPrepNote and AiWorkspaceNote, but both are caches that regenerate when their context signature changes — anything a user typed into them would eventually be overwritten. InterviewPrepItem is the side nothing regenerates, covering company research, technical notes, behavioural answers, STAR examples and the user's own questions in one table, because those categories differ only by label and adding one must not need a migration. Each item records whether the user wrote it or accepted a suggestion, and an IsPrepared flag makes the section double as the preparation checklist. Generation stays in the existing AiWorkspaceService "interview" module, appended to AiInteraction as before. A suggestion is history until the user adds it as a prep item; opening the section generates nothing. Follow-up reuses what exists rather than adding a tracker. The date is JobApplication.FollowUpAt, the same field RulesEngine and the reminder hosted service already act on, so reminders keep working with no new wiring. The task stays an ApplicationChecklistItem in the follow-up category — the section counts open tasks without owning them. The record is a FollowUpSet JobEvent, the same type the rest of the app emits. Communication is untouched: Correspondence already owns recruiter contacts, history and notes, and the workspace already mounted it. The timeline interpreter learned five more types — InterviewScheduled, InterviewCompleted and OfferReceived as milestones, FollowUpCreated and FollowUpCompleted as routine, deliberately outside the milestone spine so it stays a summary of what actually happened. JobEvent remains the history source. InterviewPrepItems is reconciler-owned with a no-op migration, guarded on JobApplications, and verified on a fresh MariaDB 11: int AUTO_INCREMENT primary key, varchar owner and title, tinyint flag, datetime(6), composite index inside the key limit. 371 backend tests, 128 frontend tests, Release build and the production build all pass locally. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
02b38f7acb |
feat(workspace): application assets workflow
Phase 5.4. Connects the career outputs a user already has to one job
application, without building a second copy of any of them.
The flow is strictly one-directional — CareerProfile -> CvVariant ->
application output — and nothing writes back up. No code path in this phase
touches CareerProfile or its children.
CV integration re-points rather than duplicates. GET/PUT /{id}/cv attaches one
variant to an application via CvVariant.JobApplicationId; replacing detaches the
previous variant instead of deleting it. Creating, duplicating, editing, theming,
previewing, exporting PDF and version history all stay in the existing CV
builder, which the section links into. There is no second CV system.
Tailoring composes the Phase 5.3 analysis and match into skills to highlight,
experience to prioritise, projects to emphasise, keywords to include and gaps to
address. Deterministic and advisory: it says what the user could emphasise and
the user edits the variant themselves. Nothing auto-applies.
Cover letters gain the history they were missing. JobApplication.CoverLetterText
stays the current text with its API contract unchanged; CoverLetterVersions
records what it used to be, so an AI rewrite is never destructive. Restore is
additive — the old text comes back as a new version, so what you restored from
still exists. Source and AiAction record whether the user wrote a version or
approved it from a suggestion, and an AI generation only becomes a version once
the user saves it.
Documents are untouched: the existing Attachment system already covers CV, cover
letter, certificates and portfolio files with a Purpose field, so the workspace
mounts that component rather than adding a second upload path.
CoverLetterVersions is the only new table — reconciler-owned, no-op migration,
guarded on JobApplications, and verified on a fresh MariaDB 11: int
AUTO_INCREMENT primary key, varchar(255) owner, datetime(6), composite index
inside the key limit.
360 backend tests, 115 frontend tests, type check, Release build and the
production build all pass locally.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
7f426e255c |
fix(infrastructure): support clean MariaDB initialization
A completely empty MariaDB database could not start: the reconciler assumed migration-owned tables already existed, and migrations assumed reconciler-owned tables already existed. Neither could go first. Existing databases worked, so only fresh installs were affected. Startup is now an explicit sequence: connect, reconcile, migrate, reconcile, start. The reconciler runs twice because neither position alone works — pass 1 repairs legacy schemas and creates the reconciler-owned tables that migrations reference, pass 2 picks up everything that could not exist yet on a fresh database. Every statement is existence-guarded, so the second pass is a no-op scan on a correct database. Untangled the overlapping ownership: - RuleSettings is migration-owned. The reconciler also created it, which made a clean install fail with "Table 'RuleSettings' already exists". It now only seeds the default row, and only once the table exists. - The six CareerProfile child tables are reconciler-owned. Their migration was scaffolded against SQLite and indexed an unbounded longtext OwnerUserId, which exceeds MariaDB's 3072-byte key limit; it is now a no-op and the reconciler carries correct per-provider DDL. OwnerUserId and ItemKey are bounded to varchar(255) in the model so the index fits. - Reconciler tables that reference another table are guarded on their parent, so pass 1 skips them on an empty database instead of failing on the foreign key. - All index creation goes through one EnsureMySqlIndex helper, guarded on table existence as well as index existence. This removes ten copies of the raw block that crashed on a missing table. - The DbContext-owned connection is no longer disposed by the reconciler, and Open() is guarded on connection state, so the second pass can reuse it. Verified against MariaDB 11 and SQLite: empty MariaDB (40 tables, starts), restart on the populated database (idempotent, rows preserved), empty MariaDB via the Docker image, fresh SQLite (42 tables), and an existing partially migrated SQLite dev database (34 tables upgraded to 44 with all 13 applications and 8 companies intact). 329 backend tests pass in Release. Ownership rules, startup order, fresh install and production upgrade are documented in docs/infrastructure/database-ownership.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |