Skip to content

Persist user PATCH before publishing it to the live user map - #81

Merged
VibeSWE merged 4 commits into
mainfrom
fix/user-patch-persist-before-publish
Oct 25, 2026
Merged

VibeSWE merged 4 commits into
mainfrom
fix/user-patch-persist-before-publish

Conversation

@VibeSWE

@VibeSWE VibeSWE commented Oct 25, 2026 •

Copy link
Copy Markdown

Summary

Follow-up from the #77 review (pre-existing, Medium): user PATCH changed the live user before persisting.

  • PATCH /user/{userid} (in rbac.rs, shared by the Query and standalone servers) called UpdateUser::update(). That writes the change, such as the email via Users.put_email, into the live map before get_metadata/put_metadata run.
  • If storage failed, the request returned an error, but this node kept the new value in memory while durable state didn't have it.

Fix:

  • The change is prepared on a cloned user, the metadata is persisted, and only then is the clone published to the live map, all under UPDATE_LOCK.
  • Validation order, HTTP responses and active sessions are unchanged.
  • UpdateUser::update() had no other production callers. The ingestor sync handlers are separate.

Tests

The preparation step is factored out so it can be tested without storage:

  • the pre-storage step must leave the live user unchanged (committed red first);
  • preparation for native and OAuth users;
  • existing validation behaviour.

Verification

  • cargo fmt --check ✅, cargo clippy -- -D warnings ✅
  • cargo test --lib: 1024 passed, 0 failed, 2 ignored

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

VibeSWE and others added 3 commits October 24, 2026 22:56
Call the handler preparation step without touching storage and require the live user to stay unchanged. The targeted test fails on the original implementation: the live email is after@example.com instead of before@example.com (cargo test exit 101).

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Prepare email changes on a cloned user using value-only insert_email. Keep UPDATE_LOCK across preparation, metadata read/write, and publication; only update the live user map after put_metadata succeeds. Preserve active sessions and the existing validation order and HTTP errors.

Validation: three targeted PATCH tests pass; cargo fmt --check and cargo clippy -- -D warnings pass; cargo test --lib -q passes (1024 passed, 2 ignored).

Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Copying the live user into parseable.json also wrote live-only state such
as OAuth tokens. The stored entry now gets only the patched email, and the
live user is replaced in place, so a user removed concurrently is not
recreated. Drops the now unused Users::put_email.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@VibeSWE VibeSWE left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Adversarial review of PR #81 (at 1b85d00a, fixes in 8fa492a0)

One Sol 6.1 and one Opus 5.5 reviewer. Neither found anything blocking or newly introduced.

Both confirmed:

  • Every error path (validation, missing user, API-key user, storage read or write) returns before the live map is touched.
  • The HTTP contract and validation order are identical to main.
  • Every handler that mutates a live User holds UPDATE_LOCK from read to publish, so nothing changes the user between the clone and the publish.
  • No other handler publishes before persisting.
Severity Finding Outcome
Medium (pre-existing) PATCH wrote the whole live user into parseable.json. For a logged-in OAuth user that includes bearer tokens, and it caused an extra metadata write on the next OIDC reconcile Fixed: the stored entry now gets only the patched email (UpdateUser::apply)
Low mut_users().insert after an .await could recreate a user removed concurrently (latent: the only unlocked remover has no callers) Fixed: new Users::replace_user updates the live user in place and never recreates one; test added
Low Nothing tests that the change is published after a successful persist Partly covered: replace_user is tested directly. The handler's storage paths can't be reached from tests
Low (pre-existing) A Query-node PATCH is never synced to ingestors Follow-up: profile changes don't affect authorization
Nit Users::put_email is now unused Removed

Local at 8fa492a0: fmt and clippy -- -D warnings clean; cargo test --lib 1026 passed, 0 failed.

@VibeSWE
VibeSWE merged commit 6af59b6 into main Oct 25, 2026
9 checks passed
@VibeSWE
VibeSWE deleted the fix/user-patch-persist-before-publish branch October 25, 2026 04:49
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.

1 participant