Commit Graph

463 Commits

Author SHA1 Message Date
cesnimda da1aa8bb2a fix(match): prioritize meaningful job terms 2026-08-09 18:47:21 +02:00
cesnimda f2d1963c61 docs(theme): record deterministic state evidence
CI and Deploy / test (pull_request) Failing after 1m31s
CI and Deploy / deploy (pull_request) Has been skipped
2026-08-09 18:37:52 +02:00
cesnimda 11734ee058 fix(theme): make preference state deterministic 2026-08-09 18:35:31 +02:00
cesnimda 378807b3b9 docs(auth): record unified sign-in evidence
CI and Deploy / test (pull_request) Failing after 1m30s
CI and Deploy / deploy (pull_request) Has been skipped
2026-08-09 17:06:08 +02:00
cesnimda 93b869259e feat(auth): unify sign-in options 2026-08-09 16:07:33 +02:00
cesnimda 83ddc0718e docs(cv): record durable processing evidence
CI and Deploy / test (pull_request) Failing after 1m38s
CI and Deploy / deploy (pull_request) Has been skipped
2026-08-09 15:14:39 +02:00
cesnimda c3c5af8329 feat(cv)!: queue durable processing
CI and Deploy / test (pull_request) Failing after 1m39s
CI and Deploy / deploy (pull_request) Has been skipped
CV upload now returns 202 with an owner-scoped operation instead of holding the request through parsing. Existing review approval remains required.

BREAKING CHANGE: profile-cv upload responses use the durable operation contract.
2026-08-09 15:11:03 +02:00
cesnimda 39e98049ea docs(cv): record durable processing trace
CI and Deploy / test (pull_request) Failing after 1m39s
CI and Deploy / deploy (pull_request) Has been skipped
2026-08-09 13:27:19 +02:00
cesnimda 89b9cdd92d docs(ai): record strategy queue evidence 2026-08-09 13:24:26 +02:00
cesnimda a62122640c feat(ai): queue strategy snapshots 2026-08-09 12:51:46 +02:00
cesnimda 5eb9b3cb96 feat(ai): enforce local-first routing
Keep external providers behind server consent, task, and prompt-cost gates while persisting actual provider provenance.
2026-08-09 12:30:11 +02:00
cesnimda c3f4a57195 feat/Update_Controllers_to_Allow_for_Premium_Membership 2026-08-03 09:17:28 +02:00
cesnimda de937d25dc fix(db): repair Jobs identity
CI and Deploy / test (pull_request) Successful in 4m40s
CI and Deploy / deploy (pull_request) Has been skipped
CI and Deploy / test (push) Successful in 4m11s
CI and Deploy / deploy (push) Successful in 1m36s
Production's SQLite-shaped Jobs table lacks AUTO_INCREMENT, causing the legacy opportunity backfill to abort startup. Reuse the existing idempotent MariaDB primary-key repair before backfill runs.
2026-08-02 19:22:23 +02:00
cesnimda 06518a7d52 fix: require verified production email
CI and Deploy / test (pull_request) Successful in 4m42s
CI and Deploy / deploy (pull_request) Has been skipped
CI and Deploy / test (push) Successful in 4m2s
CI and Deploy / deploy (push) Failing after 1m15s
Expose the existing email-verification setting through Compose and close the completed DataProtection rotation blocker.
2026-08-02 17:59:05 +02:00
cesnimda 955182b7c2 fix: close release preflight gaps
CI and Deploy / test (pull_request) Failing after 1m22s
CI and Deploy / deploy (pull_request) Has been skipped
Route public health checks to the API, backfill and synchronize job opportunities, stabilize SPA smoke tests, and document operator-only production steps.
2026-07-31 20:18:30 +02:00
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
cesnimda a23c3dfc97 docs: record remaining product decisions
CI and Deploy / test (push) Successful in 2m34s
CI and Deploy / deploy (push) Successful in 28s
2026-07-31 00:18:32 +02:00
cesnimda 6ed56fd493 fix: localize usage and salary insights
CI and Deploy / test (push) Successful in 2m40s
CI and Deploy / deploy (push) Successful in 56s
2026-07-31 00:17:33 +02:00
cesnimda 235d22c059 feat: add salary insights
CI and Deploy / test (push) Successful in 2m41s
CI and Deploy / deploy (push) Successful in 58s
2026-07-31 00:15:38 +02:00
cesnimda af3cf1cdaa fix: keep opportunity data synchronized
CI and Deploy / test (push) Successful in 2m46s
CI and Deploy / deploy (push) Successful in 54s
2026-07-31 00:12:10 +02:00
cesnimda 10d548f799 feat: show account storage usage
CI and Deploy / test (push) Successful in 2m37s
CI and Deploy / deploy (push) Successful in 58s
2026-07-31 00:10:15 +02:00
cesnimda 9f16a5675a feat: show account AI usage
CI and Deploy / test (push) Successful in 2m40s
CI and Deploy / deploy (push) Successful in 50s
2026-07-31 00:08:41 +02:00
cesnimda 2126a2db5c fix: reconcile orphan migration history
CI and Deploy / test (push) Successful in 2m42s
CI and Deploy / deploy (push) Successful in 55s
2026-07-30 23:51:08 +02:00
cesnimda 988a91a151 feat: persist job source and market
CI and Deploy / test (push) Successful in 2m34s
CI and Deploy / deploy (push) Successful in 56s
2026-07-30 23:38:22 +02:00
cesnimda efc9d83c25 fix: finish prospect workflow safeguards
CI and Deploy / test (push) Successful in 2m37s
CI and Deploy / deploy (push) Successful in 54s
2026-07-30 23:34:42 +02:00
cesnimda 792814b04e feat: cap per-user AI token spend
CI and Deploy / test (push) Successful in 2m43s
CI and Deploy / deploy (push) Successful in 57s
2026-07-30 23:32:13 +02:00
cesnimda 09fc2b03f7 feat: gate premium CV themes
CI and Deploy / test (push) Successful in 2m35s
CI and Deploy / deploy (push) Successful in 1m1s
2026-07-30 23:24:19 +02:00
cesnimda fd2c60e70f feat: complete public CV sharing
CI and Deploy / test (push) Successful in 2m34s
CI and Deploy / deploy (push) Successful in 1m0s
2026-07-30 23:19:17 +02:00
cesnimda 158970fa01 feat: enforce account usage limits
CI and Deploy / test (push) Successful in 2m39s
CI and Deploy / deploy (push) Successful in 55s
2026-07-30 23:14:42 +02:00
cesnimda 9cd2e5c2e3 feat: protect auth with Turnstile
CI and Deploy / test (push) Successful in 2m41s
CI and Deploy / deploy (push) Successful in 59s
2026-07-30 23:08:36 +02:00
cesnimda 6382e83e28 ci: report dependency vulnerabilities
CI and Deploy / test (push) Successful in 2m34s
CI and Deploy / deploy (push) Successful in 29s
2026-07-30 22:58:59 +02:00
cesnimda 405e6d833c feat: add NAV job discovery
CI and Deploy / test (push) Successful in 2m27s
CI and Deploy / deploy (push) Successful in 1m0s
2026-07-30 22:57:17 +02:00
cesnimda 7fab996407 docs: reconcile phase 6 status 2026-07-30 22:42:49 +02:00
cesnimda c08232b9d7 feat: meter AI usage
CI and Deploy / test (push) Failing after 58s
CI and Deploy / deploy (push) Has been skipped
2026-07-30 22:39:24 +02:00
cesnimda f8466c2ebc feat: advance phase 5 AI workflow 2026-07-30 22:27:10 +02:00
cesnimda e4acfbd0bf refactor: complete phase 4 builder cleanup 2026-07-30 22:23:55 +02:00
cesnimda 4cf26405f6 feat: complete phase 3 career workspace
CI and Deploy / test (push) Failing after 1m6s
CI and Deploy / deploy (push) Has been skipped
2026-07-30 22:19:13 +02:00
cesnimda f8a7cf5205 fix(deploy): repair backup directory permissions
CI and Deploy / test (push) Successful in 2m24s
CI and Deploy / deploy (push) Successful in 2m14s
2026-07-30 21:42:51 +02:00
cesnimda 56fed05d70 feat: complete phase 2 UX improvements
CI and Deploy / test (push) Successful in 2m42s
CI and Deploy / deploy (push) Failing after 9s
2026-07-30 21:35:52 +02:00
cesnimda 173187dcbb feat(cv): Phase 2.1-b — extract Projects, Certifications, and languages-from-prose
The structured model and StructuredCvProfileJson.FromSections already map
Projects/Certifications/Languages headings, but the AI normalize prompt
never emitted them, so on the benchmark CV the entire Projects section and
the in-summary languages (English Native, Norwegian B1) were silently
dropped. This closes that gap upstream — no backend schema or data change.

ai-service (tools/summarizer/app.py):
- /cv/normalize: added # Projects and # Certifications headings; a
  languages-from-prose rule (pull "native English", "Norwegian at B1" out
  of the summary even with no Languages section; ignore programming
  languages); and skill-group prefix stripping ("Development:",
  "DevOps & Infrastructure:", "Practices:" dropped, only the skills kept).
- /cv/classify-block: Projects and Certifications added to the section
  enum + rules (fallback path).

Backend:
- LooksLikeNormalizedMarkdownCv now recognises # Projects / # Certifications
  so those CVs still take the markdown assembly path.

Tests:
- CvExtractionCoverageTests (4) lock the C# mapping of Projects,
  Certifications and Languages sections into the structured profile.
- ai-service test_classify_block_supports_projects_section (1).
426 backend tests, 17 ai-service tests pass; app.py compiles.

The LLM behaviour (prompt -> headings) needs Ollama to observe and was not
run here; the C# side that consumes the headings is proven and the prompt
change is additive. Merge-not-replace + the review screen are the next
increment (2.1-a, approved: always-review, conservative merge).

Deployment: these prompts live in the ai-service container, which
deploy.sh does not rebuild by default -- deploy with
DEPLOY_BUILD_AI_SERVICE=true or the change won't take effect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 11:40:04 +02:00
cesnimda fe9cd4dda1 refactor(career): Phase 1 increment 2 — extract editor sections, hide duplicate CV concepts
UI-only. No change to APIs, save payloads, extraction behaviour, or data
models. The parent CareerProfilePage still owns loading, state, saving,
and all extraction/import actions; the new sections are presentational
(value + onChange, plus a getMetadata callback for review chips).

Extracted into src/views/career/CareerProfileSections.tsx:
PersonalInformation, ProfessionalSummary, Skills, Interests, Languages,
WorkExperience, Education, OtherSections. FieldReviewNote + confidenceTone
moved there verbatim and shared with the parent. CareerProfilePage went
from 1376 to ~1200 lines.

No Projects/Certifications sections were created -- the editor never had
them (they are not editable structured fields here). Inventing them would
add functionality, which this refactor avoids; noted for a product
decision later.

Hid the duplicate CV concepts behind an "Advanced CV tools" toggle,
collapsed by default: the CV Structure Overview parse block and the
Template-driven CV builder. Both stay mounted and functional (gated with
display:none), so no tested functionality is removed -- the real CV
Builder at /career/builder is the single generation surface. Future
removal plan documented.

Tests: added "editing a field in an extracted section updates parent
state and flows into save" (render -> edit -> PUT /career/profile
{profile,cvText}); existing parse/rewrite tests reveal the advanced tools
first. The increment-1 save-invariant test still pins the payload.

Verified: tsc clean, production build clean, 137 frontend tests pass.
Backend untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 11:00:26 +02:00
cesnimda 63473bae85 refactor(career): Phase 1 increment — user-facing terminology + component split
UI-only restructuring of the Career Profile surface. No change to
database models, CareerProfiles schema, CvVariants, extraction APIs, AI
services, CV rendering, or public CV.

Terminology -> user-facing (i18n strings):
- "Structured CV editor"     -> "Career information"
- "CV structure overview"    -> "Profile sections"
- "Summary bullets"          -> "Professional summary"
- "Core skills"              -> "Skills"
- "Analyze sections"         -> "Read sections"
- "Original extraction"      -> "Original import"
- hardcoded "Master career profile" -> "Career profile"
Help text de-jargoned; the Career information help now frames it as the
source the CV Builder consumes.

Component split (first step): extract ProfileCompleteness (completeness
meter + missing chips + version history) into src/views/career/. Display
only, props in, no state or API.

Save path untouched: api.put("/career/profile", { profile, cvText }). A
new test pins that exact call as the refactor invariant so the remaining
section extraction cannot silently change save behaviour. Existing
profile-page tests re-pointed to the new labels; every behavioural
assertion (save, parse, field values) kept.

Verified: tsc clean, production build clean, 136 frontend tests pass
(135 + 1 invariant). Sidebar fix from the previous task still passes.
Backend untouched.

The remaining Phase 1 work (per-section editor components, hiding the
template-driven builder and structure-overview blocks, actionable
per-section empty states) is staged in docs/career-workspace-ux-refactor.md
because it touches the live extraction test surface and is best verified
by driving the authenticated UI. This increment is a clean, non-regressing
checkpoint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 02:50:52 +02:00
cesnimda 4f69d395be fix(nav): only the most-specific sidebar item is active
On /career/builder/{id} both "Career Workspace" (/career) and "CV Builder"
(/career/builder) highlighted, because AppShell tested each item with
`pathname === to || pathname.startsWith(to + "/")` — so /career matched
every /career/... child. No "most specific wins" rule.

Add AppShell.activeNavTo(pathname, tos): the longest `to` that the path is
at or under wins, across both nav lists; every other item is inactive. A
child route never lights up a parent nav item. `selected` now compares
against that single computed activeTo. Exported as a pure function so the
ownership rule is unit-tested directly (sidebar-active-nav.test.ts):
exactly one active item for /career, /career/builder and
/career/builder/{id}, and no double-highlight.

Also give the breadcrumb/title in App.tsx explicit /career/builder ->
"CV Builder" ownership (it previously showed "Career Workspace"), and
reframe the Career Workspace header to the "Career Profile" product
framing: "This information powers your CVs, applications, cover letters
and AI assistance."

Frontend only — no change to CareerProfiles, CvVariants, CV generation,
extraction APIs, AI, permissions or tenant isolation. Plan for the deeper
information-architecture work is in docs/career-workspace-ux-refactor.md,
staged so the 1376-line CareerProfilePage and the live CV/extraction
pipeline are refactored incrementally with verification, not in one risky
rewrite.

Verified: tsc clean, frontend build clean, 135 frontend tests pass
(128 + 7 new nav tests). Backend untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 02:29:50 +02:00
cesnimda 4db8c08958 fix(security): evaluate tenant CurrentUserId live, not at construction
CI and Deploy / test (push) Successful in 2m38s
CI and Deploy / deploy (push) Failing after 7s
Production POST /api/cv/variants returned 200 but GET /api/cv/variants/{id}
returned 404, with the query logged as `... FROM CvVariants WHERE FALSE`
(no parameters). The created row had a correct OwnerUserId; the read was
excluded by the global query filter because CurrentUserId was null at
query time. Reproduced locally: it affected EVERY tenant-filtered read
(CV list returned 0 after creating 5, JobApplications returned total 0),
not just CV -- writes worked, reads came back empty.

Root cause: the "local" JwtBearer OnTokenValidated resolves the
request-scoped JobTrackerContext (to run LocalSessionValidator) BEFORE the
authentication middleware assigns HttpContext.User. JobTrackerContext
captured CurrentUserId in its constructor from ICurrentUserService.UserId,
which reads HttpContext.User -- still unauthenticated at that point -- so
CurrentUserId froze to null. That same scoped instance is reused by the
controller, so `CurrentUserId != null && OwnerUserId == CurrentUserId`
compiled to WHERE FALSE for the whole request. POST worked because
CreateAsync sets OwnerUserId from the controller-resolved user, and
inserts are not filtered.

Fix: make CurrentUserId a computed property that reads
ICurrentUserService.UserId live, so the query filters see the
authenticated user at query-execution time. Deny-on-null is preserved
(still null for an unauthenticated principal). LocalSessionValidator is
unaffected -- it already uses IgnoreQueryFilters and queries by explicit
sid.

Verified on a real MariaDB 11 container end to end: create then read a
variant returns 200, the variant list returns all rows, and
GET /api/jobapplications reads normally. Added
CurrentUserIdLiveEvaluationTests pinning the live-evaluation behaviour
(both fail against a constructor snapshot). 422 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 00:20:09 +02:00
cesnimda 474654b1c1 fix(cv): add LongTailJson to reconciler CareerProfiles schema
Production GET /api/cv/outline returned 500 "Unknown column
'c.LongTailJson'". CareerProfiles is reconciler-owned, but the
reconciler's CREATE TABLE (both the SQLite and MySQL branches) only
listed Id, OwnerUserId, ProfileJson, Version, CreatedAtUtc, UpdatedAtUtc.
LongTailJson was added to the CareerProfile model in Phase 3 but neither
CREATE was updated and no column-repair existed, so:

- existing databases (prod): the MySQL CREATE is guarded on
  !HasMySqlTable, so it never runs once the table exists, and nothing
  adds the column -> LoadStructuredAsync selects a column that isn't
  there.
- fresh databases: the CREATE itself omitted the column, so even a brand
  new MariaDB/SQLite was missing it. The 420 tests never caught this
  because they build tables from the EF model, not the reconciler DDL.
  The release audit missed it because it never exercised /api/cv/outline.

Add LongTailJson to both CREATE statements and add an additive repair
(EnsureColumn / EnsureMySqlColumn) for existing tables. DEFAULT ''
backfills existing rows and matches the non-nullable model property.
This is the sanctioned reconciler repair path, not a manual ALTER, and
preserves existing data (ADD COLUMN is non-destructive).

Verified on a real MariaDB 11 container: an existing 6-column
CareerProfiles gains LongTailJson on startup (repair path), a fresh DB
gets it from the CREATE (longtext), and GET /api/cv/outline returns 200.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 00:19:52 +02:00
cesnimda 83206df9d7 docs(release): final release report
CI and Deploy / test (push) Successful in 2m47s
CI and Deploy / deploy (push) Failing after 7s
Consolidates release-candidate verification: backend 420 (incl. the CI
runner's exact Ubuntu 20.04/libicu66 environment), frontend 128, all
four DB scenarios, backup/restore with a byte-exact æøå round trip,
health and auth checks.

Status: READY WITH DOCUMENTED RISKS. No open code blocker. Remaining
risks separated into code (none), infrastructure (runner instability
A/B, still unconfirmed-fixed; old runner ICU), and manual owner
verification (sign-in, production backup, production scale).

Recommendation: deploy with documented risks -- re-run CI with the ICU
fix, owner runs a real backup + scratch restore, deploy, then run the
manual smoke test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v1.0.0
2026-07-19 23:15:41 +02:00
cesnimda 57fabe9a97 docs(deployment): backup-restore, smoke test, runner finding C
- docs/deployment/backup-restore.md: production backup checklist; documents
  that deploy.sh loads the env, validates before backup, and validates the
  dump. Adds VERIFIED UTF-8/Norwegian-character round trip (æ ø å survive a
  real deploy.sh backup -> restore byte-exact; HEX compared). States plainly
  that no production database was reached and the owner must run one real
  backup + scratch restore.
- docs/deployment/manual-smoke-test.md: owner-run post-deploy checklist
  (auth, applications, career profile, CV builder, AI, files). Each item
  names what "wrong" looks like. Documents that login requires the owner.
- runner-investigation.md: Finding C -- the latest CI red was a real ICU
  code bug the runner caught correctly, not instability. Amends the blanket
  "outside the repository" conclusion. A and B stand as separate env issues.
- release-candidate-review.md: corrected drifted line refs after the index
  fix; noted the CI ICU finding so the "purely external" verdict is honest.

All claims reflect behaviour verified this session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 23:14:41 +02:00
cesnimda b705cbaf60 chore: pin shell scripts to LF via .gitattributes
deploy.sh and the other scripts run on the Linux deploy host and in
Docker. A CRLF checkout breaks them with "bad interpreter: bash\r".
The committed blobs are already LF, but nothing guaranteed it against a
host with core.autocrlf=true. `*.sh text eol=lf` makes it explicit.

Verified: git ls-files --eol shows attr/text eol=lf on all .sh files;
renormalize produced no index churn (already LF).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 23:14:27 +02:00
cesnimda fba858e8eb fix(cv): force language alias precedence over host culture data
The CI backend test job failed on HumanLanguageCatalogTests:
  nynorsk -> expected "Norwegian", actual "Norwegian Nynorsk"

This was a real bug, correctly caught by the runner -- not runner
instability. Reproduced on Ubuntu 20.04 / libicu66 (the CI runner's ICU)
with .NET 9 installed via dotnet-install.sh exactly as CI does.

Root cause: BuildLanguageLookup's explicit normalization aliases
(nynorsk/bokmål/norsk -> Norwegian) were added with map.TryAdd, which
loses to any key the culture enumeration already inserted. On libicu66
the "nn" culture's NativeName is the bare word "nynorsk", so enumeration
claimed key "nynorsk" -> "Norwegian Nynorsk" first and the explicit alias
silently lost. On libicu70+ (Debian/Ubuntu 22.04+, my earlier local
runs) the native name is "norsk nynorsk", so the key was free and the
alias won -- which is why it passed locally and only failed on the
runner's older ICU. Same host-ICU dependence class as 9681618.

Fix: add an Override helper (map[key] = value) and apply it to the
alias block so these mappings win regardless of insertion order. Also
collapse the full "Norwegian Nynorsk"/"Norwegian Bokmål" phrases to
"Norwegian" for consistency. Correct by construction for any ICU version.

Verified:
- reproduced the exact failure on libicu66 with the old code (probe)
- fix logic yields nynorsk/bokmål -> Norwegian on that same libicu66
- real net9 test DLL: 420/420 on focal libicu66 (CI mirror, dotnet
  9.0.316 via dotnet-install.sh), and 420/420 on libicu72 (Debian) and
  libicu74 (Ubuntu 24.04), plus locally
No test weakened, skipped, or relaxed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 23:08:37 +02:00
cesnimda 5c5a572cfc docs(ops): record release-candidate audit findings
CI and Deploy / test (push) Failing after 1m8s
CI and Deploy / deploy (push) Has been skipped
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>
2026-07-19 22:53:43 +02:00