Implement role-based account journeys with database and browser acceptance suites
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Container Image / build-and-push (push) Successful in 20s
Account journey acceptance / journeys (push) Failing after 0s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
This commit is contained in:
tegwick 2026-09-13 12:20:02 +02:00
parent 75750c0036
commit 1127f852dd
24 changed files with 1554 additions and 148 deletions

View file

@ -8,6 +8,7 @@ pooling, securing, and observing those connections.
from __future__ import annotations
import json
from threading import RLock
from contextlib import contextmanager
from importlib.resources import files
from typing import Any, Iterable, Iterator, Mapping, Protocol, cast
@ -80,6 +81,32 @@ class PostgresUserEngineStore:
def __init__(self, connection: PostgresConnection) -> None:
self.connection = connection
self._lifecycle_lock = RLock()
self._transaction_depth = 0
self._transaction_failed = False
@contextmanager
def tenant_lifecycle_guard(self, tenant: str) -> Iterator[None]:
# Session locks survive the service's local commits and cover provider
# side effects. Separate processes use the same tenant-scoped DB lock.
with self._lifecycle_lock:
key = "user-engine:tenant-lifecycle:" + tenant
with self._cursor() as cursor:
cursor.execute("SELECT pg_advisory_lock(hashtextextended(%s, 0))", (key,))
try:
yield
except BaseException:
# An aborted transaction cannot execute the unlock query.
self.connection.rollback()
raise
finally:
with self._cursor() as cursor:
cursor.execute("SELECT pg_advisory_unlock(hashtextextended(%s, 0))", (key,))
def outbox_history(self) -> tuple[OutboxEvent, ...]:
with self._cursor() as cursor:
cursor.execute("SELECT payload FROM user_engine_outbox_events ORDER BY occurred_at, event_id")
return tuple(cast(OutboxEvent, self._decode_payload_row("outbox_events", row)) for row in cursor.fetchall())
@property
def schema_version(self) -> str | None:
@ -97,16 +124,26 @@ class PostgresUserEngineStore:
@contextmanager
def transaction(self) -> Iterator[None]:
begin = getattr(self.connection, "begin", None)
if callable(begin):
begin()
outer = self._transaction_depth == 0
if outer:
self._transaction_failed = False
begin = getattr(self.connection, "begin", None)
if callable(begin): begin()
self._transaction_depth += 1
try:
yield
except Exception:
self.connection.rollback()
except BaseException:
self._transaction_failed = True
if outer: self.connection.rollback()
raise
else:
self.connection.commit()
if outer:
if self._transaction_failed:
self.connection.rollback()
raise RuntimeError("nested transaction failed")
self.connection.commit()
finally:
self._transaction_depth -= 1
def save_user(self, user: User) -> None:
self._upsert_record(user)