b67581ce80
extensions.config is a single jsonb blob shared by every flag/hash the L1 onboarding wizards (capital, chairman) write to. Every write site did the classic read-modify-write: findByName() the whole row, spread+mutate one key in app memory, then update() the whole config back. Two DecisionTrackedEvent handlers firing concurrently (two council decisions tracked near-simultaneously) each read the same stale snapshot and each write their own flag back — whichever write lands last wins and silently reverts the other one's flag to its old value. Symptom hit live: onboarding_generator_program_template_done reverted to false (with its hash still present, proving the decision really was tracked) after onboarding_generation_contract_template_done raced it to true. Added ExtensionDomainRepository.patchConfig(name, patch), implemented as a single `UPDATE extensions SET config = config || patch::jsonb ... RETURNING *` — no app-side read before the write, Postgres serializes concurrent UPDATEs on the row so two different-key patches can no longer clobber each other. Replaced every config read-modify-write in capital's and chairman's onboarding services (the events handler's flag write, loadPlugin's init/expire timestamps, the hash writes in completeStep/completeGeneralMeet, saveProgramDocDataHash) with this atomic patch. update() is untouched for other callers. Live data recovered manually: onboarding_generator_program_template_done was patched back to true for voskhod/capital (hash was already present, confirming the decision had in fact been tracked before the race reverted the flag).