Skip to content

feat(op-work-packages): update path with optimistic locking#67

Merged
AdaWorldAPI merged 1 commit into
mainfrom
claude/beautiful-gates-dJo0u
Jul 1, 2026
Merged

feat(op-work-packages): update path with optimistic locking#67
AdaWorldAPI merged 1 commit into
mainfrom
claude/beautiful-gates-dJo0u

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

What

The WorkPackage update write path on top of the #66 CreateService, with OpenProject-style optimistic locking (lock_version).

  • UpdateWorkPackage — a sparse patch write-model (Deserialize); each Some field is applied, None leaves the current value untouched.
  • WorkPackageService::update(id, expected_lock_version, changes) — the guarded write:
    • NotFound if the id is absent,
    • Conflict { expected, actual } if the caller's lock_version is stale (someone wrote first),
    • Validation if the patch would blank the subject,
    • on success: apply patch → bump lock_version → refresh updated_at → persist → return the entity.
  • WorkPackageStore::update + the MemoryWorkPackageStore impl; WorkPackageError::Conflict variant.

Tests

4 new update tests (apply + version bump, stale-version → Conflict, NotFound, blank-subject rejection) → 16 tests pass total.

Gate

fmt --check clean; op-work-packages clippy-clean (0 warnings in this crate). As before, -D warnings transitively surfaces the pre-existing op-core lints already on main — untouched here.

Notes

  • Un-setting a nullable FK back to NULL (e.g. clearing an assignee) is a documented follow-up — the None-means-"no change" convention needs an Option<Option<_>> to also express "set to null".
  • Next: emit a change event on create/update so the composition layer can journal it (op-work-packagesop-journals) without coupling this crate to op-journals.

Branch note

Follows the merged #66; branch restarted from the new main (832004a) per the merged-branch rule, single follow-up commit, no overlap with the walled odoo-rs-transcode branch.

🤖 Generated with Claude Code


Generated by Claude Code

Add the WorkPackage update write path on top of the #66 CreateService.

- UpdateWorkPackage — a sparse patch write-model (Deserialize); each Some
  field is applied, None leaves the value untouched. (Un-setting a nullable
  FK to NULL is a documented follow-up — needs Option<Option<_>>.)
- WorkPackageStore::update — persist changes to an existing row.
- WorkPackageService::update(id, expected_lock_version, changes) —
  optimistic locking: NotFound if absent, Conflict{expected,actual} if the
  caller's version is stale, Validation if the patch would blank the
  subject; on success applies the patch, bumps lock_version, refreshes
  updated_at, persists, returns the entity.
- WorkPackageError::Conflict variant.
- MemoryWorkPackageStore::update.

4 new update tests (apply+bump, stale-version conflict, NotFound, blank-
subject rejection) -> 16 tests pass. fmt clean; op-work-packages clippy-clean
(0 warnings in this crate; transitive op-core -D-warnings lints pre-existing).

Follow-up: emit a change event on create/update so the composition layer
can journal it (op-work-packages <-> op-journals), without coupling the
crate to op-journals.
@AdaWorldAPI AdaWorldAPI merged commit 4a852b3 into main Jul 1, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 77e89ae4c0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
wp.lock_version += 1;
wp.updated_at = Utc::now();
self.store.update(&wp).await?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Make optimistic lock checks atomic with persistence

When two updates for the same work package run concurrently with the same expected_lock_version, both can read the old row in find(), pass the check above, then call store.update() and overwrite each other while both return success. Because the version predicate is not part of the write (and the store API only receives the already-bumped row), optimistic locking does not actually prevent lost updates for any asynchronous/DB-backed store or parallel in-memory access; the compare against the expected version needs to happen inside the store update/WHERE clause.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants