fix(backend): fence deferred conversation enrichment ownership - #12287
fix(backend): fence deferred conversation enrichment ownership#12287aryanorastar wants to merge 1 commit into
Conversation
Claim deferred rows with a flag CAS, atomically re-arm failed enrichment, and guard the lifecycle owner with transaction, router, static, and emulator contention coverage.\n\nFailure-Class: FC-split-mutation-authority\nFixes: BasedHardware#12286
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Verification evidence:
The complete backend suite was additionally audited against a clean No physical-device verification is required: this PR changes backend Firestore lifecycle ownership only and contains no iOS or Android code. |
|
The Six unit files fail, all inherited from Five are mechanical, and #12292 fixes them. Hand-written module stubs that never gained names the production code started importing:
One is not. So this PR goes green once #12292 lands, except for |
|
Reviewed the full diff at ecb75b6 — this is a well-constructed fix. The ownership-fencing analysis is right, and the tests are the kind this lifecycle deserves. File by file:
On the red One semantics question for a maintainer (non-blocking): Docs/agent-guidance note: Leaving for human maintainer review before merge: the lifecycle state-machine choice above, and merging against a green (or harness-fixed) unit suite — this touches the core conversation data path, so it should get maintainer eyes on top of the automated pass. by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with |
kodjima33
left a comment
There was a problem hiding this comment.
Confirmed the race is real and not yet fixed on main (current _reacquire_deferred_processing_txn only checks status==processing, not deferred) — fixes #12286 legitimately. Holding merge: Backend unit suite check is failing.
|
Yes — still in progress. The lifecycle fix itself is complete and approved; I am clearing the inherited backend-suite gate. On the maintainer semantics question: accepting
The compare-and-swap still fails closed for The documentation statement is also intentional: both the claim ( For CI, the red remains outside this diff. The authoritative backend job currently has three upstream pieces: the stale module stubs in #12292, the current BYOK-contract assertions in #12302, and two base-branch unused imports in |
Summary
deferred=truethe compare-and-swap ownership token for lazy conversation enrichment, so concurrent first opens cannot both launch LLM processingcompleted + deferred=true, allowing the UI to stop polling and a later open to retrydeferredRoot cause
The first-open transaction checked only
status=processing. Two requests could both read the deferred row, then each transaction could succeed even after the first had cleareddeferred, duplicating summaries, tasks, memories, app results, and LLM cost.Failure recovery was also split between a raw flag update and a separate status transition. Besides permitting a partial write to strand a row in
processing, it producedcompleted + deferred=trueeven though reacquisition rejected every completed row, so the documented retry path could never run.Fix
The storage transaction now claims only an explicit deferred row and writes
status=processing,deferred=false, and the renewed admission lease together. It also accepts the explicitcompleted + deferred=trueretry terminal. A second transaction owned by the lifecycle service atomically re-arms a failed attempt ascompleted + deferred=true; the router no longer performs lifecycle writes directly and no longer silently drops recovery errors.Proof
Regression-first
The new contracts failed against current
mainwith four failures:processing + deferred=falsecompleted + deferred=truecould not be retriedAutomated
cd backend && .venv/bin/python -m pytest tests/unit/test_conversation_finalization_jobs.py tests/routers/test_conversations_processing_rollback.py tests/unit/test_check_conversation_lifecycle_writes.py -q— 64 passedpython3 backend/scripts/check_conversation_lifecycle_writes.py— passedpytest backend/tests/unit/test_stale_processing_emulator_concurrency.py -k deferred_reacquire -q— 2 passed, including a real two-open contention race with exactly one winnerOMI_PR_BODY_FILE=/tmp/omi-pr-12286.md make preflight— 26 checks passedFull-suite baseline audit
backend/test.shran all 935 selected unit-test files. It reported 13 unrelated failing files under concurrent load. Twelve reproduced on a detached cleanorigin/mainworktree with the same runner (module-stub/import drift, STT expectation drift, BYOK expectation drift, and the runtime-manifest CPU timing guard). The thirteenth,test_share_email_routes.py, passed all 21 tests when rerun alone on this branch, confirming a load-sensitive suite failure. None of those files or their production surfaces are changed here.Scope
Backend and lifecycle documentation only; no iOS or Android code changes. This repairs the backend lifecycle root associated with stuck-processing reports in #11295 and complements the UI timeout treatment in #11856.
Fixes #12286
Failure-Class: FC-split-mutation-authority
Product-Invariants: none
Line-Count-Exception: backend/database/conversation_finalization_jobs.py | 2081 -> 2112 | keep the paired Firestore transaction primitives beside the existing deferred ownership transition
Line-Count-Exception: backend/routers/conversations.py | 1917 -> 1926 | surface atomic recovery loss and exceptions at the existing deferred enrichment call site