Files
jobtrackingapp/docs/redesign/phase-9/p9-04/RD21_CONCURRENCY_ISSUE.md
T

3.5 KiB

RD21 — Protect canonical Career and generic job edits from stale overwrites

Severity: High. Status: reproduced / unresolved. Safety qualification: FAIL. This is an existing risk carried from Phase 7, now reproduced with two independently authenticated local clients on isolated synthetic SQLite. The characterization test passing means the observations were captured; it does not mean writes are safe.

Exact reproduction

  1. Both clients read the same saved Career Profile, whose summary is “Initial summary”.
  2. A changes the summary to “First editor committed summary” and receives 200.
  3. B changes a different field in its old snapshot and submits it. B receives 200; a fresh read now contains “Initial summary”. A's acknowledged edit is lost.
  4. Repeat with a saved job. A changes the title to “First editor committed title”. B changes notes in its stale full snapshot and receives 204. Readback restores “Original job title”.

Fresh native evidence records exact statuses/values. Reproducer: data.spec.ts, retained under the 18-case Phase 9 diagnostic lane. Ownership is the same account in both clients; this demonstrates stale-write loss, not a cross-account disclosure. Existing foreign-account protections and CV/Dashboard revision tests remain separate.

Responsible boundary and dependency

The existing CareerProfileController.Put and CareerProfileService save the current submitted profile without a caller-supplied revision comparison. The generic JobApplicationsController PUT likewise applies an old complete snapshot without a complete expected-version contract. UpdatedAtUtc by itself is not an atomic precondition. Do not copy the Dashboard version shape into unrelated domains without deciding the actual contract.

This is a cross-caller API/data contract change. The Phase 9 brief requires major architectural/contract work to become a separate issue. No partial client-only workaround, arbitrary retry, silent merge, data rewrite or weakening of existing assertions is included in QA.

Owner: backend/domain owner with the Career and Jobs frontend owners. Separate work must inventory canonical import/history/restoration, job forms/bulk/workspace/extension/automation callers and legacy API consumers; define compatibility, version creation, atomic conditional write and conflict semantics; retain canonical/CV/frozen document boundaries; and preserve recoverable drafts when rejected. The Career write and later Identity completion also need explicit transaction/partial-failure behavior.

Required closure evidence

  • Concurrent independent clients editing different and same fields cannot silently overwrite an acknowledged save.
  • Actual owner-and-revision preconditions on the database write; simultaneous submissions and first saves are covered.
  • SQLite and supported MariaDB runtime evidence, including constraints, held readers/writers and partial failure.
  • Conflicts preserve drafts and offer explicit current-data review/reload; account switch and stale session fail closed.
  • Imports, history restores, generic job updates, lifecycle/bulk operations and old callers follow the agreed compatibility contract.
  • Complete affected regression, frozen document byte preservation and no unrelated Identity replay.

Creation-intent idempotency is another retained RD21 concern; these stale-write observations do not claim to test or solve it. Production and unqualified data-integrity acceptance remain withheld. Phase 10 must not remove compatibility paths while this contract is unresolved.