Implement B01-B06 with regression coverage and retained validation evidence. Preserve the production ingress preflight; F04 runtime verification remains open.
28 KiB
Product coherence, operational readiness and reusable quality gates
This is a source/observational assessment plus a proposed acceptance programme. Verification states what actually ran. No live uptime, provider price, current vulnerability catalogue or legal requirement was verified externally.
Critical paths and time to value
Priority order: sign in/recover account → view/find tracked jobs → save/update application → edit Career Profile/CV → download/share an approved document → communication/follow-up → optional AI/discovery/analytics. Optional AI/email outages should not block the first four paths.
The existing manual add-job browser test clicks Add Job, Enter details manually, fills company/title, Continue, skips three optional steps, then Create job. That is a measurable interaction sequence, not a timed usability study. Offer a short “Save job” path after required details and let the user enrich it later; keep the reviewed import path. Never require AI or a complete CV before tracking a first job.
Registration depends on configuration, verification and Turnstile. Default-disabled registration is an operational rollout choice, not inherently a bug. The landing CTA must explain when signup is unavailable. First-run empty states should offer three small actions: save a job, add career details, create a CV. Explain that Career Profile supplies facts and a CV is a chosen presentation for an application. Dismissible hints beat a mandatory tutorial. Verify registration → email verification → first saved job against a local mailbox sink; this whole journey was not executed in the audit.
Navigation and domain language
Current routes are visible in src/App.tsx: dashboard, jobs/detail, discovery, reminders, operations, Kanban, companies, offers, correspondence, profile, career, CV builder, trash, settings and admin. Deep job routes and career subroutes are valuable; legacy application/correspondence routes redirect. The existing browser workspace deep-link/back/dirty-navigation test passes. Not every tab/filter/modal was exercised.
Proposed information architecture, retaining working routes:
Dashboard: next useful actions
Jobs: list / board / job workspace / reminders
Career: facts / CVs / import review / evidence
Communication: correspondence and send/review status
Discovery: optional acquisition workflow
Settings: identity, connected providers, privacy, notifications, plan, data
Admin: account/security/health diagnostics, hidden from ordinary users
Offer comparison, companies and operation status need not compete equally with daily job actions in primary navigation. Keep direct links for frequent users; make secondary entries contextual. Do not remove features solely because usage analytics are absent. Before deleting an apparent duplicate, inspect dynamic imports, stored template IDs, compatibility routes, migrations and deployment scripts.
| Concept | English / recommended Bokmål | Meaning / consistency rule |
|---|---|---|
| Job / opportunity | Job / Stilling | Employer opportunity; not proof of an application |
| Tracked application | Job application / Jobbsøknad | User's pursuit of a job, with status/history |
| Application document | Cover letter / Søknadsbrev | Written letter, not the tracked workflow itself |
| Career Profile | Career Profile / Karriereprofil | Reusable factual source; account Profile is identity/settings |
| CV | CV / CV | Saved presentation/settings; define draft versus submitted snapshot |
| Interview | Interview / Intervju | Scheduled or logged interview; not interview-preparation notes |
| Reminder | Reminder / Påminnelse | A due action; distinguish notification channel from task |
| Correspondence | Correspondence or Job email / Korrespondanse or Jobb-e-post | Choose one label per navigation context, explain manual versus connected mail |
| Offer | Offer / Tilbud | Employer outcome; no implicit monetary currency conversion |
| Rejected | Rejected / Avslått | Application outcome; avoid confusing with archive |
| Withdrawn | Withdrawn / Trukket | User ended pursuit |
| Archive / trash / delete | Archive / Arkiv; Trash / Papirkurv; Delete / Slett | Distinguish inactive, recoverable deletion and permanent erasure |
| Save / share / download | Save / Lagre; Share / Del; Download / Last ned | Explicit object on destructive/ambiguous actions; sentence case |
Current terminology is partly established in docs/01-glossary.md, translations.ts and JobPipeline. Reconcile with that vocabulary rather than creating a competing glossary. The observed Bokmål builder still shows English template descriptions and “Dashboard”; button capitalization includes “Last Ned PDF” and “AI-Verktøy”. Treat these as editorial inconsistencies to review with a native Bokmål speaker. Keep document language distinct from interface language; the English synthetic CV inside a Norwegian UI is not itself a translation failure.
Visual, accessibility and professional polish review
Observed: the current static builder was inspected at 375 and 1440 pixels in both themes and both actual language states (en / nb-NO). The surrounding page had zero measured horizontal overflow. Desktop has a useful editor/preview split; mobile provides an edit/preview alternative. Shared navigation, toolbar and template thumbnails have consistent placement. These are strengths worth preserving.
Confirmed problems: F14's success/ATS chips use green text with measured 3.29:1 contrast on white in one light-theme sample; desktop scrolling regions were not keyboard focusable. All eight inspected states produced at least one serious contrast finding. F15's combined iframe sandbox flags produce repeated browser warnings. A short synthetic CV also produced a preview overflow warning; investigate the measurement/rendered document width rather than hiding the warning. External Google Fonts were blocked by the harness and fallback rendering was inspected; compare with approved self-hosted-font rendering before attributing every document-layout discrepancy to a product defect.
The template gallery uses small badges and substantial descriptive copy. Keep selection predictable, use stable action positions, and ensure Pro-disabled cards have an explanatory accessible name rather than opacity alone. Do not conflate “ATS-friendly” with a guarantee for every recruiter system. The saved status, language and theme should not move focus unexpectedly. No native screen-reader or actual browser-zoom certification was performed.
Canonical component choices should build on MUI and existing shared theme.ts, layout/AppShell.tsx, layout surfaces, feedback context/dialogs and spacing tokens. GradientButton.tsx is a special treatment to inventory, not justification for another general button package. Keep normal controls on MUI, standardize PageHeader/empty/error/loading patterns at their actual common callers, and migrate one page before broad rollout. Add a lightweight component state showcase only if repeated visual review warrants it; Storybook is not a launch dependency.
| Component / state | Review and target |
|---|---|
| Button/input/select | Clear accessible names; focus visible; Enter/paste/autofill work; busy does not imply permission; use native/MUI semantics |
| Dialog/menu/portal | One focus trap; Escape semantics; focus return; no nested modal stack for routine edits; theme inherited |
| Cards/badges/status | Text conveys status independent of color; keyboard equivalents for clickable cards; translated long names wrap |
| Toast/error/empty/loading | Persistent actionable error for failed saves; important failures not transient toast only; live region announces state changes, not every token |
| Tables/Kanban/charts | Paged data; allow keyboard change-status/reorder; meaningful zero-data explanation; charts have textual values/summary |
| CV editor/preview | Dirty/saving/saved/failed states; recoverable conflict; accessible scrolling; output language metadata and reading order tested separately |
| Responsive layout | 320 effective width, 375/768/1440, 200%/400% actual zoom, landscape, on-screen keyboard, safe area, text spacing; no hidden-only-on-hover action |
| Interaction details | Real links support middle/right click; copy feedback only after clipboard success; focus/title after SPA navigation; Back restores useful list/filter/scroll state |
Copy action in CvBuilderEditor.tsx shows a success toast without awaiting clipboard completion; make success contingent on the browser result. History/preview/unsaved dialogs deserve keyboard and screen-reader runs, not only snapshots. Shared page title/focus/scroll behavior was not proven by source search; add explicit route-level checks before claiming polish.
Professional polish checklist (open until visually verified): consistent loading indicators and skeleton ARIA; favicon/page titles and missing assets; margins/spacing tokens; both themes; translated empty states and errors; tooltips for icon-only controls; stable actions; no layout shift; no unexplained console/network/backend warnings; no broken route/404 fallback hiding missing assets; sensible scrollbars and z-index layers; no accidental nested modals; copyable text; no forced orientation; reduced motion/forced colors; clear failed/disabled/long-content states. A screenshot is not proof of every item.
Generated documents require a separate gate: selectable/searchable text, intended page size/margins, logical reading order, headings/lists, document language, meaningful links, metadata, contrast and absence of hidden server paths. Existing PDF browser tests prove portions of rendering/searchability, not tagged PDF/PDF-UA or screen-reader reading order. DOCX extraction support does not imply a complete accessible DOCX generation/export feature. Verify current offered export formats before promising them. Browser Print must hide application navigation and preserve page boundaries independently of PDF export.
Feature maintenance and simplification
Scores are judgments (high/medium/low), not usage telemetry.
| Subsystem | User value / maintenance / risk | Decision |
|---|---|---|
| Job tracking and routed workspace | High / medium / high correctness | Keep and prioritize reliable save/search/history |
| Career Profile and CV builder | High / high / high privacy/data loss | Keep; strengthen revision/time/preview contracts before more customization |
| Mail connectors/provider abstraction | Potentially high / high / high privacy/external effects | Retain working seams; measure adoption before adding providers or full mailbox features |
| AI assistant/work queues | Useful / high / high privacy/cost | Keep human review, gates and current queue; narrow workloads/evaluate models |
| Discovery/import/translation | Medium / high / high SSRF/source churn | Manual fallback, limits and per-source tests; no bespoke scraping platform |
| Company CRM/offer comparison/analytics | Medium / medium / medium | Contextual access; measure adoption before more domain entities |
| Public CV | Medium / medium / high publication/render abuse | Explicit review/revocation and bounded rendering; optional expiry when needed |
| Custom auth/billing/backup orchestration | Essential / high / high | Keep Identity and Stripe primitives; repair small missing protocols; compare managed options only with requirements and costs |
Remain a modular monolith. A parser service boundary is justified by unsafe documents and different runtime/resource needs. A separate renderer may be justified by measurable resource/privilege isolation; explain its queue, artifact-storage, failure and deployment costs first. No reason yet for Redis, Elasticsearch, Kubernetes, a PWA/service worker or independently deployed domain microservices. An offline-capable PWA would add sensitive cache/upgrade complexity before F01/F09 are solved.
Existing configuration switches cover workers, registration, deletion, provider routing and paid authorization. Maintain a flag register with owner, purpose, default, rollback behavior, test of both states and review/removal date. Server-side entitlement policy remains authoritative; a feature flag does not grant access. Percentage rollout is unnecessary for a small closed cohort; explicit internal/beta allowlisting and kill switches suffice.
Performance, capacity and cost model
Use measured traffic/service time before setting SLO commitments. The audit's localhost navigation-to-main timings exclude real WAN latency, complete data/render readiness and production contention. They are not LCP or production p95 evidence. Existing owner/page limits help, but list-of-all board/analytics/history/exports and per-request Chromium are the likely first bottlenecks.
Proposed initial internal targets for a declared synthetic dataset and reference device/network: core read/save API p95 <500ms and p99 <2s; authentication p95 <2s excluding provider challenge; dashboard ready <3s; LCP <2.5s, INP <200ms, CLS <0.1; search p95 <500ms; bounded PDF p95 <10s for an agreed 2-page CV; queue acceptance <1s; an immediate truthful queued/progress state for slow AI. These are acceptance hypotheses, not measured guarantees. Benchmark on the actual host before promising them. Record p50/p95/p99 and error rate, and budget the initially loaded JS based on measured gzip/Brotli bytes; fail unexpected >10% route-bundle growth unless justified.
| Scale | What must be measured first | Likely trigger / next step |
|---|---|---|
| 10 users | Real daily workflow failures, restore success, single PDF RSS | Fix correctness/UX before scaling |
| 100 | Concurrent PDF/AI requests, monthly storage/mail growth, worst-owner queries | Bounded admission and alerting; single host may suffice but capacity unproven |
| 1,000 | DB p95/query plans, pool use, queue age, file backup duration | Tune projections/indexes, batch/stream exports, independent worker capacity if measured |
| 10,000 | Real active concurrency, arrival/service rates, DB recovery/availability | DB-backed quota reservation before replicas; separate durable storage/compute as needed |
| 100,000 | Workload partitioning, retained history, operational staffing/cost | Reassess architecture with production telemetry; no exact capacity claim here |
Suggested scaling triggers: sustained DB CPU >70% plus missed latency targets; pool utilization >80% with connection waits; queue wait exceeds user-facing SLO for 15 minutes; disk forecast under 30 days; backup/restore exceeds target; repeated PDF admission saturation. Tune alert thresholds against baseline and avoid paging for every transient failure. First inspect query plans on the actual SQLite/MariaDB provider; SQL text or InMemory tests do not establish production query cost.
Cost model variables (record actual invoices/provider metering):
monthly cost = fixed host + database + monitoring + stored_GB × storage_rate + backup_GB × backup_rate + egress_GB × egress_rate + email_count × email_rate + external_AI_input_tokens × input_rate + external_AI_output_tokens × output_rate + operator_time × internal_rate.
Local AI substitutes memory/compute/energy and operator cost for a provider token bill; it is not free. PDF consumes CPU/process time and retains output bytes. For workload w, estimate cost_per_success = (successful + retried + failed_attempt_cost) / successful_results. Separate reservations, actual estimates, failures and external fallback. Character/4 estimates are not provider billing truth, especially for Bokmål and unusual text.
Storage planning example, assumption not telemetry: 1,000 users × 20 documents × 1MB = 20GB original bytes before generated files, versions, package copies and backups. Thirty full daily copies alone could multiply that substantially. Measure dedup/compression/retention rather than promising a flat quota covers all copies. Database growth includes JobEvents, CV/Profile/letter versions, mail, AI operations/usage and deletion/security records. Set retention before partition/archive infrastructure.
Useful privacy-preserving telemetry: workflow starts/success/failure, safe task/version/category, elapsed time, approximate size bucket, queue wait, retry count, upload bytes, quota rejection, empty-state completion. Exclude CV/letter/mail/note text, full URLs/query strings, tokens and unredacted screenshots. Keep support diagnostics usable without viewing career content. Monitor externally, but never make application health depend on the telemetry collector being online.
Operational ownership and alerts
For a solo project, the repository owner can initially own all roles; explicitly name the backup contact before paid launch. Record actual people/contact routes outside public source. Proposed availability goal is 99.5% monthly for core reads/saves after baseline measurement; adopt a stricter promise only with operational evidence. Track separate generation/delivery success SLOs so optional AI errors do not disguise or inflate core reliability.
| Signal | Severity / initial rule | Owner / action |
|---|---|---|
| External HTTPS/readiness | P1 after 3 failed probes over ~3min; immediate if broad confirmed outage | Operator: proxy/DB runbook |
| Core save errors | P1 sustained >1% or any confirmed data loss | App owner: freeze risky rollout, inspect safe trace IDs |
| Worker stalled | P1 if accepted jobs miss deadline; otherwise P2 sustained lag | App operator: F20 lease/backoff runbook |
| Backup age/failure/off-site failure | P1 beyond RPO; warning at 26h for daily schedule | Recovery owner: preserve prior set, rerun safely, verify restore |
| Disk/inode forecast | Warn 80%, urgent 90% or <7 days forecast; adjust for volumes | Operator: stop expensive admission, inspect approved cleanup |
| PDF/AI cost or saturation | P2 sustained queue/cost anomaly; P1 if affecting core API | App owner: kill switch, admission/abuse investigation |
| Billing reconciliation/deletion | P1 unresolved paid-state/cancellation divergence | Billing owner: sandbox-validated reconciliation, user follow-up |
| Privilege changes / unusual export | Security review; urgent on unauthorized activity | Security owner: account/session containment and evidence |
| TLS/DNS | Warn certificate <21 days, urgent <7; sustained DNS mismatch | Domain/proxy owner: renewal/DNS runbook |
Deduplicate alerts by incident, include last good release, safe reference/metric, impact, runbook and recovery notification. Ensure a missing scheduler/monitor itself produces a dead-man alert outside the app host. Check backups becoming suspiciously small/large and off-site encryption failure, not only exit codes.
Incident response and technical runbooks
Common sequence: identify impact and start an incident record; preserve UTC timeline, release IDs and sanitized logs; contain further writes/exposure when justified; keep a known recovery point; repair/rotate; verify user journeys and integrity; communicate factually through the approved channel; document cause and regression prevention. Do not email users automatically from this audit. Determine legal notification obligations with the responsible qualified advisor.
| Incident | Checks and diagnosis | Safe recovery / escalation | Verification |
|---|---|---|---|
| 502/site unreachable | External DNS/TLS/HTTP, frontend route, backend /health and /ready, recent deploy |
Correct proxy/service/network or roll back only schema-compatible image; do not restart DB blindly | External auth/config + synthetic core journey and readiness |
| DB unavailable | Connection reachability/pool/server health, provider identity, disk, credential changes; no credential dumps | Restore connectivity; reduce admission; recover worker leases; escalate to DB owner if integrity uncertain | Read/save synthetic record, FK/integrity checks, queued work progresses |
| Bad migration / partial deploy | Migration history and exact failing statement/release; schema compatibility | Stop further deploys; preserve checkpoint; forward fix or restore isolated verified set; explicit approval before production restore | Old/new client contracts, data counts/hashes, app-ready with intended version |
| Disk full | Per-volume/inode usage, DB/WAL, logs, exports, backups | Disable PDF/AI/upload admission; rotate/prune only documented disposable outputs; add capacity; never delete active DB/WAL or sole backup | Free headroom, DB integrity, safe new upload/export and backup |
| High CPU/RSS/OOM | Active Chromium/model workers, queue size, request classes, container limits | Apply admission/kill switch, terminate only identified stuck disposable worker, restore service; inspect missing results | Resource baseline, queue recovery, no partial artifacts reported ready |
| AI/model outage | Sidecar health vs model availability, task/model route, consent and timeout category | Keep manual product usable; disable failing route; switch only to evaluated permitted model; queue/retry bounded | Synthetic factuality/format/locale checks, no unauthorized external fallback |
| Email unavailable | SMTP/provider response category, queue/attempt state, token scope, sender config | Reconnect/rotate as needed; retry known unsent only, reconcile uncertain sends; use local sink during diagnosis | Delivery accepted/received in safe mailbox; pending verification/reminders resolved |
| Backup failed/corrupt | Last independently verified set, manifest/hash, backup/copy logs, disk | Preserve prior verified set; recreate consistent snapshot; restore isolated copy first | DB+files+keys+tombstones usable within RPO/RTO; record actual minutes/bytes |
| TLS expired / DNS wrong | Cert chain/SAN/expiry, renewal job, authoritative vs recursive DNS | Renew correct certificate/fix intended record; escalate registrar/proxy access to owner | External HTTPS from independent network and renewal alert |
| Leaked JWT signing secret | Scope/token issuance window, affected sessions, safe auth logs | Rotate signer, revoke sessions/trusted devices as required, redeploy configured key; plan forced sign-in | Old tokens rejected, new sign-in works, no secret in logs |
| DB/SMTP/AI/OAuth/Stripe key leak | Identify specific credential, usage anomalies and accessible data | Revoke/replace at issuer and app, test dependent workflows, reconcile mail/billing side effects; rotate webhook secret with provider overlap policy | Old credential rejected, correct service restored, anomaly monitoring quiet |
| Data Protection key exposure/loss | Which encrypted tokens/TOTP depend on ring; backup custody | Treat exposed reversible values as compromised; revoke/relink providers or re-encrypt with controlled key transition; never casually delete ring | Token decryptability/re-enrollment and restored backups tested |
| Admin compromise / cross-user leak | Actor/session/role timeline, affected owners/objects, access logs | Revoke compromised sessions, disable account/route as needed, preserve evidence, patch authorization with failing test | Complete cross-owner suite; scoped security review; appropriate user notice process |
| Malicious dependency / host compromise | Release provenance/SBOM, runner/host boundaries, secret exposure | Isolate host/runner; rebuild from clean trusted artifacts; rotate reachable credentials; do not trust in-place cleanup alone | Clean deployment, restored validated data, external/user smoke, evidence retained |
Operator inspection can use read-only service listings and /health//ready, sanitized logs and backup manifests. Do not paste docker compose config, full environment, connection strings or tokens into an incident ticket. A production restart, restore, destructive cleanup or credential change is a separate authorized action. The audit did not execute these runbooks.
Release gates and production-readiness checklist
Choose measurable targets with the owner. Proposed initial recovery target: RPO <=24h and RTO <=4h for the current daily-backup service; paying-user expectations may require a shorter RPO. Record restored snapshot timestamp, last committed synthetic marker, restoration duration and file/key verification. Never claim this target is achieved until a drill passes.
- No known P0; all applicable P1 findings closed with reproduction and regression evidence.
- Backend build/tests, frontend tests, standalone strict types, lint and static build pass using declared locked toolchains.
- Real relational tests cover owner predicates, revision conflicts, byte/usage reservation, idempotency and deletion; MariaDB behavior is tested when deploying MariaDB.
- Browser critical paths pass on the production export; both languages/themes and editor failure states included; no unexplained console/network errors.
- Zero serious/critical axe findings on core routes; manual keyboard/screen-reader/zoom/text-spacing checks recorded, with documented exceptions if unavoidable.
- Bounded PDF/parser/AI/import/export load proves process/memory/request ceilings and recoverable errors.
- Current dependency/advisory/container/secret scans and SBOM retained; each exception has owner, mitigation and expiry; supported toolchain verified.
- Migration rehearsed on realistic synthetic shape; old/new compatibility window and rollback boundary stated; verified pre-deploy recovery point retained.
- Actual DB+files+keys+tombstones restore meets chosen RPO/RTO; off-site copy and alert routing tested.
- Host ingress/firewall, TLS renewal, trusted proxies, least privilege and production secrets validated; development fallbacks cannot activate silently.
- Stripe sandbox proves duplicate/order/concurrency/deletion; mail sandbox proves known-unsent vs uncertain delivery; OAuth uses intended tenant/scopes.
- Privacy/retention/export/deletion/support wording matches implemented behavior; external/legal decisions resolved by responsible owner.
- Release version observable, post-deploy external smoke succeeds and a responsible operator can respond to the alerts.
Definition of Done and practical PR checklists
Every feature: explicit acceptance behavior; correct owner/permission/validation; appropriate regression test; recoverable loading/error/empty states; no private log content; useful documentation only where behavior changes. A cosmetic edit does not need the same gates as billing or a migration.
PR checklist
- Problem, resulting behavior and smallest affected boundary explained.
- Relevant tests/checks pass; omitted checks and actual risks stated.
- Owner/security/duplicate/failure behavior considered for changed paths.
- UI/locales/themes/accessibility checked when changed.
- Data/migration/rollout/rollback and behavior docs updated when relevant.
Endpoint/security checklist
- Identity and owner come from trusted server context; test another owner's identifiers on each verb.
- DTOs reject unauthorized fields/oversized inputs; enum/null/numeric boundaries and query allowlists tested.
- Cookie mutations have CSRF protection; auth/admin/export changes require appropriate recent auth and safe audit.
- Uploads have byte/type/expansion/storage limits; URLs validate connected destinations; downloads have safe headers/names.
- AI uses bounded consent-approved data, validated output and human review; actions cannot come from untrusted prompt instructions.
- Side effects have operation identity, correct quota admission and safe failure/retry semantics.
UI checklist
- Shared spacing/type/color/control conventions; every action has a meaningful name.
- Keyboard, focus return, contrast, touch and no hover-only requirements.
- 375/768/1440 and applicable reflow/zoom; both EN/NB and light/dark.
- Loading/empty/error/disabled/success/long-text states; save failures retain edits.
- Back/deep links/clipboard/scroll/title behavior and console/network cleanliness.
Database-change checklist
- One schema owner; migration and model snapshot agree; both supported providers handled.
- Correct FK/unique/check/index constraints; owner scope and query plan tested.
- Existing rows preserved; deterministic backfill; locking/disk/time measured on realistic shape.
- Expand/contract compatibility and irreversibility documented; no blind time-zone conversion.
- Verified backup, rehearsal, rollback/forward-repair plan and integrity check.
Deployment checklist
- Intended commit/artifact/config and environment verified; no secrets printed.
- Required quality gates, backup/recovery checkpoint and migration review complete.
- Previous compatible image/recovery path available; feature kill switches understood.
- Deploy, then public TLS/health/readiness/auth/config + synthetic core smoke; correlate version.
- Check safe logs/metrics and queue recovery; stop/repair if release gate fails.
These are repository recommendations, not evidence that branch protection, deployment permissions, legal policies or production operations currently meet them.