Files
jobtrackingapp/docs/redesign/phase-9/rd21-remediation/ROOT_CAUSE.md
T

5.9 KiB

RD21 investigation (23 October 2026)

Status: SQLite repair implemented and locally qualified. Formal supported-provider closure remains UNRESOLVED; see the dated acceptance addendum.

Original failure

The unchanged Phase 9 characterization was executed on the exact baseline 8e7e3f6387544e24082ebe2c107c0b868897959e. Both independently authenticated editors read the same synthetic record. Editor A changes and successfully saves one field. Editor B changes a different field in its older complete snapshot. B's request is accepted and replaces A's field with its original value. Subsequent authenticated reads show the loss. See original-reproduction.json. Career stale response: 200; job stale response: 204. Both lostFirstEdit values are true. The characterization's one passing test establishes that the unsafe behavior was reproduced; it is not a safety pass.

New native SQLite tests additionally query persisted rows through independent DbContexts immediately after acknowledgement. The original job safety assertion failed because the stale request returned NoContentResult instead of a conflict. Career safety tests failed because the stale request returned success and because an injected Identity failure left canonical rows committed despite the failure response. These are separate deterministic regression assertions.

Mechanisms

  1. Generic job PUT reads the current row, maps the client's complete replacement over it and calls SaveChanges. The request has no source revision; the entity has no concurrency token. A fresh request context therefore accepts an arbitrarily old snapshot. This is persisted data replacement, independently of client display state. The optional awaited summary generation also creates an interval between read and write; checking only at controller entry is unsafe.
  2. Career PUT and restore similarly lack an editor revision. The canonical service increments Version but does not compare the editor's version or use it as an EF concurrency token. Children and history are transactionally saved, then the transaction commits. The controller updates ApplicationUser.ProfileCvStructureJson/CvText afterward. Identity failure can leave canonical and legacy projections inconsistent despite a failed operation response.
  3. Career parse and reviewed import acceptance also call the canonical replacement service and update the projection/application metadata separately. They must participate in the same guard and transaction; fixing only manual PUT would leave alternate replacement paths open.

SQLite tests reproduce the same persisted replacement through cover-letter save and application drafts: a first acknowledged First approved text becomes Stale snapshot text after a later request from the stale editor. These routes replace fields also writable through the generic job snapshot. They are included in the bounded repair. Cover-letter history alone does not reject stale approval or keep the current approved text intact. Legacy tailored-CV text replacement is also an application-owned snapshot writer and must fail closed without its source revision.

Client lifecycle

Career has an existing synchronous mutation guard and disables its editor while saving. Its error paths retain drafts, but it cannot detect another session's change without server revision data. The shared job dialog does not guard older GET responses, and remains editable/closable during PUT; a successful submitted snapshot can close a dialog containing later unsent edits. The frontend correction preserves the revision belonging to each draft, blocks conflicting in-flight actions, rejects old responses, and displays recoverable conflicts without blind retry.

Provider limitation

The read-only rtk proxy docker info --format '{{json .ServerVersion}}' probe on 23 October failed: dockerDesktopLinuxEngine named pipe does not exist. Additional default and desktop-windows context info probes also failed with their named pipes absent. Command lookup found no mariadbd/mysqld/mariadb/mysql executable, and neither Program Files installation root contained a MariaDB/MySQL directory. No daemon installation/restart was attempted. MariaDB runtime qualification is outstanding; SQLite and provider script generation cannot substitute for a MariaDB persistence/concurrency run.

Integration review findings during repair

The first repair passed its backend and component suites, but whole-branch connected review found two integration gaps before final qualification. Neither is hidden by those passing totals:

  • The application-drafts client consumes X-Job-Version after 204, while the existing cross-origin CORS policy does not expose that response header. APIRequest tests can read it, but browser Axios cannot. The database can therefore commit while the browser reports a missing committed revision. Actual cross-origin browser proof is required in addition to raw HTTP assertions.
  • Cover-letter saves advance the shared job version. Answers initially consumes a cached overview snapshot; switching from a successful letter save to Answers can therefore submit an older source revision and receive an avoidable 409. A new Answers editor needs a coherent fresh source before editing. Updating only its revision would incorrectly authorize stale body fields; dirty drafts must retain their original source through later parent refreshes.

The broad browser run was stopped after 22 passing cases while these findings were addressed. It is an interrupted attempt, not a full-suite pass. Final results must qualify the corrected source.

Historical recovery

No evidence establishes recovery of edits already overwritten before this repair. Canonical history may contain a prior profile snapshot, but availability and user intent must be examined per record. No production database has been accessed and no historical recovery is claimed.