6.3 KiB
Save and conflict contract
Implemented contract; final qualification is recorded separately in the dated acceptance addendum.
Canonical Career Profile
GET /api/career/profile returns { profile, completeness, cvText, revision }.
The profile comes from canonical relational children/LongTail with the existing legacy blob
fallback. A short read transaction keeps source, revision and raw CV text coherent. Reads do not
backfill/write unversioned children. The public owner-specific load remains read-only.
revision is CareerProfile.Version; 0 means there is no canonical row. It is independent of
the structured document's schema string profile.version and extraction metadata profileVersion.
| Writer | Required source precondition | Commit boundary |
|---|---|---|
PUT /career/profile |
Body expectedVersion from the draft's GET |
Canonical row, replace-all children, append-only history, Identity projection and optional raw text |
POST /career/profile/versions/{version}/restore |
Body expectedVersion for current canonical source, independent of selected historic version |
Restored snapshot appended as a new guarded revision and projection |
POST /profile-cv/parse |
Body expectedVersion and source text |
Applied extraction run, canonical aggregate, projection and import pointers |
POST /profile-cv/runs/{id}/accept |
Body expectedVersion from the actual reviewed diff |
Reviewed merge, canonical aggregate, applied-run metadata and projection |
Missing or negative Career preconditions return 400. A valid stale revision returns 409 with
code: revision_conflict. Competing first saves use the existing unique owner constraint;
specific owner-duplicate errors become conflicts. There is no blind-save service overload or
automatic stale retry. Existing-row writes also compare the original version in EF's database
UPDATE predicate, protecting the interval after controller validation.
Controller transactions include Identity updates. A failed Identity result throws before commit; canonical rows/children/history and import pointers roll back together. Failed tracked changes are cleared, so a later SaveChanges cannot publish a rejected operation. Successful responses are constructed within this boundary and returned only after commit. Post-commit parse-artifact pruning is logged best-effort maintenance and cannot turn the committed save into an error.
Null/omitted cvText preserves imported raw text; whitespace explicitly clears it. Identity fields
remain owned by the identity endpoint. The obsolete /auth/profile CV projection fields now fail
closed with 400 and guidance to use /career/profile; identity-only updates remain supported.
Job snapshots and related text
Job reads expose integer version. Workspace overview and cover-letter reads expose revision
for that same job row. Tailored CV draft GET/generation also returns its job revision.
| Writer | Required body field | Success |
|---|---|---|
PUT /jobapplications/{id} |
expectedVersion from the full editor snapshot |
Existing 204 status, X-Job-Version committed revision |
PUT /jobapplications/{id}/application-drafts |
expectedVersion with the answer/recruiter/notes draft source |
204 and X-Job-Version |
PUT /jobapplications/{id}/cover-letter |
expectedVersion with current text/history source |
Cover-letter DTO including new revision |
POST /jobapplications/{id}/cover-letter/versions/{version}/restore |
expectedVersion for current job, independent of history version |
Cover-letter DTO including new revision |
PUT /jobapplications/{id}/tailored-cv[-draft] |
expectedVersion with the legacy text/structured draft source |
204 and X-Job-Version |
Missing/invalid/stale job preconditions fail with 409. Every tracked JobApplication mutation, including partial lifecycle and background writers, advances the application-managed version. EF's original-version predicate guards the database write; full replacement/no-op saves force that guard even if the submitted fields compare equal. An overlapping winner cannot be silently replaced after a slow optional AI summary. Related opportunity fields and timeline events remain inside the same SaveChanges transaction. No history/events are appended for a rejected write.
Generic job PUT still preserves the separately embedded application answer when editing ordinary notes. It validates company ownership under tenant filters. Partial application-draft null fields retain their prior semantics. Source guards do not alter CV-specific null-as-inherit overrides or submitted/frozen documents.
Client acknowledgement and recovery
Each draft retains the revision belonging to its actual source. A failed/409 save leaves user text and choices available. The client never reads the latest revision just before PUT to authorize an older body, never silently merges/retries a conflict, and never labels a rejected request saved. The editor excludes overlapping mutations and late responses from older modal/read sessions.
Recovery requires deliberate reload/review. A network failure may leave commit outcome unknown; the original revision cannot commit twice. Refresh and compare before resubmitting a retained draft. This repair does not claim recovery of already overwritten historical edits.
Compatibility and qualification
Older unversioned replacement clients intentionally fail closed. Active application callers and test fixtures are updated; the extension/bookmarklet create flow does not perform these replacement writes. An external client must read a source snapshot and carry its revision before replacing it. No missing-revision compatibility fallback is accepted.
Migration 20261023090000_AddJobRevision adds the job integer column with default 1. SQLite tests
upgrade populated synthetic storage and roll back/reapply while preserving job content. Career's
existing version column becomes an EF concurrency token without a new physical column. A schema
rollback does not preserve revision identity and is not a qualified production rollback procedure.
SQLite runtime tests and provider SQL generation do not prove MariaDB runtime behavior. The local Docker Linux engine was unavailable. Mixed old/new server or client deployment, actual MariaDB, production storage durability and release/rollback remain separate qualifications. No deployment or production migration was performed.