Plain asyncio.gather on one event loop could let the first upload commit
before the second's pre-check, making the second a legitimate replace (2
successes, 1 live row) -- correct behavior that fails the '1 conflict'
assertion. Same Event-synchronization pattern as the auto's HTTP race tests:
hold the first past its pre-check until the second also pre-checks, so both
read None before either writes and the partial-unique index decides
deterministically. Code under test untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Port certificate lifecycle (parse/encrypt/upload/deactivate) and
FiscalSeries CRUD from the auto, adapted to (product_id, tenant_ref,
branch_ref) tenancy. Closes the "guard retroativo" PATCH /v1/series
next_number regression check that Task 3 deferred to this task.
Routes: POST/GET/DELETE /v1/certificados, POST/GET/PATCH /v1/series.