diff --git a/CHANGELOG.md b/CHANGELOG.md
index c6f108c..bba1168 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,18 @@
All notable changes to **flycanon** are documented here.
+## [26.10.5] - 2026-10-17
+
+### Fixed
+
+- Select the operator's working-directory `alembic.ini` for `flycanon migrate`. Installed wheels now use the reviewed release scaffold instead of looking relative to `site-packages`. Missing or non-file configuration returns a nonzero exit with setup guidance before migration starts; parent and package directories are not searched. The configured database URL and `upgrade head` behavior are retained.
+- Document the working-directory contract and the direct Alembic workaround for retained `26.10.4` wheels. The source archive remains the migration/configuration scaffold; wheel contents, migrations and listener behavior are unchanged.
+- Give concurrency unit tests independent SQLite connections through a module-local temporary file database. Causal checks cover distinct connections and rollback isolation; existing service assertions, production transactions and ordinary unit fixtures are unchanged.
+
+### Distribution
+
+- Align server and Python/Java SDK version metadata at `26.10.5`. SDK behavior, HTTP contracts and runtime dependency pins are unchanged.
+
## [26.10.4] - 2026-10-14
### Fixed
diff --git a/README.md b/README.md
index 866b8d4..eb193a9 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ over whole documents with citations — all behind a single HTTP service.
[](https://github.com/fireflyframework/fireflyframework-agentic)
[](docs/api-reference.md)
[](docs/architecture.md#retrieval-backend-pluggable-dense-store)
-[](#)
+[](#)
[](LICENSE)
diff --git a/docs/deployment.md b/docs/deployment.md
index 3784612..97914ec 100644
--- a/docs/deployment.md
+++ b/docs/deployment.md
@@ -43,7 +43,7 @@ For a host installation using the released wheel, follow [Install the released s
```bash
docker pull ghcr.io/fireflyframework/flycanon:latest # latest main
-docker pull ghcr.io/fireflyframework/flycanon:26.10.4 # specific release
+docker pull ghcr.io/fireflyframework/flycanon:26.10.5 # specific release
docker pull ghcr.io/fireflyframework/flycanon:26.10 # latest 26.10.x
```
@@ -306,6 +306,8 @@ GRANT ALL ON SCHEMA public TO canon;
Alembic migrations live under [`migrations/`](../migrations). Run
once per deploy:
+For a host installation, run `flycanon migrate` from the reviewed release scaffold containing `alembic.ini` and `migrations/`. The CLI selects that working directory's configuration and keeps `FLYCANON_DATABASE_URL` as the database selection. See [Install the released server](released-server-install.md) for the retained `26.10.4` wheel workaround.
+
```bash
docker run --rm \
--env FLYCANON_DATABASE_URL=postgresql+asyncpg://canon:canon@db.internal:5432/flycanon \
@@ -681,12 +683,12 @@ The recommended upgrade dance:
```bash
# 1. Pull the new image
-docker pull ghcr.io/fireflyframework/flycanon:26.10.4
+docker pull ghcr.io/fireflyframework/flycanon:26.10.5
# 2. Run migrations against the live database
docker run --rm \
--env-file .env \
- ghcr.io/fireflyframework/flycanon:26.10.4 migrate
+ ghcr.io/fireflyframework/flycanon:26.10.5 migrate
# 3. Roll the API + worker deployments
docker compose --env-file .env up -d
diff --git a/docs/released-server-install.md b/docs/released-server-install.md
index f42155a..c0ba4a5 100644
--- a/docs/released-server-install.md
+++ b/docs/released-server-install.md
@@ -6,13 +6,13 @@ Use the released source archive for the operator configuration and migration fil
## Download and verify
-Download `flycanon-26.10.4-py3-none-any.whl`, `flycanon-26.10.4.tar.gz`, `runtime-requirements-26.10.4.txt`, and `SHA256SUMS` from the [26.10.4 release](https://github.com/fireflyframework/flycanon/releases/tag/v26.10.4). Verify the downloaded files against their matching checksum entries before extracting or installing them.
+Download `flycanon-26.10.5-py3-none-any.whl`, `flycanon-26.10.5.tar.gz`, `runtime-requirements-26.10.5.txt`, and `SHA256SUMS` from the [26.10.5 release](https://github.com/fireflyframework/flycanon/releases/tag/v26.10.5). Verify the downloaded files against their matching checksum entries before extracting or installing them.
```bash
-tar -xzf flycanon-26.10.4.tar.gz
-cd flycanon-26.10.4
+tar -xzf flycanon-26.10.5.tar.gz
+cd flycanon-26.10.5
uv venv --python 3.13 .venv
-uv pip install --python .venv/bin/python -r ../runtime-requirements-26.10.4.txt
+uv pip install --python .venv/bin/python -r ../runtime-requirements-26.10.5.txt
```
The requirements file selects the released server wheel and these canonical framework references:
@@ -26,12 +26,12 @@ The equivalent installation without a requirements file is:
```bash
uv pip install --python .venv/bin/python \
- 'flycanon @ https://github.com/fireflyframework/flycanon/releases/download/v26.10.4/flycanon-26.10.4-py3-none-any.whl' \
+ 'flycanon @ https://github.com/fireflyframework/flycanon/releases/download/v26.10.5/flycanon-26.10.5-py3-none-any.whl' \
'pyfly @ git+https://github.com/fireflyframework/fireflyframework-pyfly.git@v26.09.04' \
'fireflyframework-agentic @ https://github.com/fireflyframework/fireflyframework-agentic/releases/download/v26.10.1/fireflyframework_agentic-26.10.1-py3-none-any.whl'
```
-A fresh installation with these references resolved all runtime dependencies without `--no-deps` or a separate resolver override. Installing optional providers may require additional extras and configuration; this command does not verify cloud accounts or model-provider credentials.
+The runtime dependency references are unchanged from `26.10.4`. Installing optional providers may require additional extras and configuration; installation does not verify database access, cloud accounts or model-provider credentials.
## Framework migration compatibility
@@ -39,11 +39,11 @@ This release retains pyfly `v26.09.04` and the existing PostgreSQL EDA outbox. A
## Configure the runtime
-Keep `pyfly.yaml`, `alembic.ini`, `migrations/`, and `env_template` from the source archive in the working directory. The wheel does not supply the root-level `pyfly.yaml`. The release also provides a byte-identical `pyfly-26.10.4.yaml` copy for operators who already have a migration scaffold; install it as `pyfly.yaml` in the runtime working directory.
+Keep `pyfly.yaml`, `alembic.ini`, `migrations/`, and `env_template` from the source archive in the working directory. The wheel does not supply the root-level `pyfly.yaml`. The release also provides a byte-identical `pyfly-26.10.5.yaml` copy for operators who already have a migration scaffold; install it as `pyfly.yaml` in the runtime working directory.
The configuration selects the PostgreSQL event adapter and separate API/worker consumer groups. Starting without it can activate framework defaults, including an attempted Kafka connection to `localhost:9092` and a separate management listener. Do not rely on those defaults for the released service.
-Set the database URL, embedding model and dimensions, provider credentials, platform API keys, object-storage location, and optional Redis settings for your environment. Use [`env_template`](https://github.com/fireflyframework/flycanon/blob/v26.10.4/env_template) and the [deployment guide](https://github.com/fireflyframework/flycanon/blob/v26.10.4/docs/deployment.md) for the exact settings. Store real secrets outside published configuration and supply them through your operator's environment or secret manager.
+Set the database URL, embedding model and dimensions, provider credentials, platform API keys, object-storage location, and optional Redis settings for your environment. Use [`env_template`](https://github.com/fireflyframework/flycanon/blob/v26.10.5/env_template) and the [deployment guide](https://github.com/fireflyframework/flycanon/blob/v26.10.5/docs/deployment.md) for the exact settings. Store real secrets outside published configuration and supply them through your operator's environment or secret manager.
Provision the PostgreSQL database, required extensions and roles using the deployment guide. Apply migrations as a separate operator step before serving a new database:
@@ -51,6 +51,16 @@ Provision the PostgreSQL database, required extensions and roles using the deplo
.venv/bin/flycanon migrate
```
+`flycanon migrate` uses `alembic.ini` in the current working directory and applies `upgrade head` with `FLYCANON_DATABASE_URL`. Run it from the reviewed source archive's root, even when the wheel is installed elsewhere. A missing configuration file returns a nonzero exit and identifies the required scaffold before migration starts. The command does not search parent directories or the installed package for another configuration.
+
+For retained `26.10.4` wheel installations, use Alembic directly from that release's scaffold until upgrading the CLI:
+
+```bash
+.venv/bin/python -m alembic -c alembic.ini upgrade head
+```
+
+Keep the original configuration, migration files and database URL together. Updating the CLI does not migrate a database automatically.
+
Run from the directory containing the reviewed `pyfly.yaml`:
```bash
@@ -72,4 +82,4 @@ The standard `flycanon serve` command listens on all interfaces using `FLYCANON_
curl -fsS http://127.0.0.1:8500/api/v1/version
```
-Verify authenticated source ingestion, source retrieval and canonical-memory retrieval in the intended tenant and workspace. A version response confirms the running package, while those operations exercise the configured database, retrieval and authorization boundaries. The release checks include a fresh wheel installation and native PostgreSQL tests for tenant/workspace isolation. A newly provisioned operator environment still needs authenticated ingestion and retrieval checks with its own configuration.
+Verify authenticated source ingestion, source retrieval and canonical-memory retrieval in the intended tenant and workspace. A version response confirms the running package, while those operations exercise the configured database, retrieval and authorization boundaries. Repository integration tests cover tenant/workspace isolation; each released artifact and newly provisioned operator environment still needs installed-server and authenticated ingestion/retrieval checks with its own configuration.
diff --git a/openapi.json b/openapi.json
index b9caf24..bab1293 100644
--- a/openapi.json
+++ b/openapi.json
@@ -5103,7 +5103,7 @@
"url": "https://github.com/fireflyframework/flycanon/blob/main/LICENSE"
},
"title": "flycanon",
- "version": "26.10.4"
+ "version": "26.10.5"
},
"openapi": "3.1.0",
"paths": {
diff --git a/pyproject.toml b/pyproject.toml
index 96a44b6..ac2b5ae 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@
name = "flycanon"
# CalVer YY.MM.PP -- bumped per release. Note that PEP 440 normalises
# ``26.05.04`` -> ``26.5.4`` in the built wheel filename.
-version = "26.10.4"
+version = "26.10.5"
description = "Operational Knowledge Repository -- versioned, provenance-tracked canonical knowledge with hybrid retrieval and RAG, exposed as a standalone HTTP microservice over fireflyframework-pyfly and -agentic. Part of Firefly Framework, platform-agnostic by design."
readme = "README.md"
requires-python = ">=3.13,<3.14"
diff --git a/sdks/java/CHANGELOG.md b/sdks/java/CHANGELOG.md
index c011f09..565dba8 100644
--- a/sdks/java/CHANGELOG.md
+++ b/sdks/java/CHANGELOG.md
@@ -6,6 +6,12 @@ The SDK uses CalVer (`YY.MM.Patch`). Breaking changes are signalled
in the entry header and the **Breaking** section, not in the version
number (CalVer has no "major" axis to bump).
+## [26.10.5] - 2026-10-17
+
+### Changed
+
+- Align Maven and User-Agent version metadata with the server's installed migration CLI fix. SDK behavior and HTTP contracts are unchanged; the `26.10.4` SDK remains compatible.
+
## [26.10.0] - 2026-10-12
### Changed
diff --git a/sdks/java/README.md b/sdks/java/README.md
index 9bb3deb..0a301aa 100644
--- a/sdks/java/README.md
+++ b/sdks/java/README.md
@@ -41,7 +41,7 @@ Compatible with **flycanon service version `26.10.x`**.
| SDK | Service |
|-----|---------|
-| `26.10.4` | `26.10.x` (same request/response schemas; workspace creation requires matching scope) |
+| `26.10.5` | `26.10.x` (same request/response schemas; workspace creation requires matching scope) |
| `26.5.7` | `26.5.x` |
## Install
@@ -50,7 +50,7 @@ Compatible with **flycanon service version `26.10.x`**.
com.firefly
flycanon-sdk
- 26.10.4
+ 26.10.5
```
@@ -418,7 +418,7 @@ use).
## Versioning
The SDK pins its version to the service's CalVer (`YY.MM.PP`), so
-`flycanon-sdk@26.10.4` is the matching client for service version
+`flycanon-sdk@26.10.5` is the matching client for service version
`26.10.x`. Upgrade the SDK in lockstep with the service. Both
clients (`CanonClient`, `ReactiveCanonClient`) ship from the same
artifact -- no separate dependency to manage.
diff --git a/sdks/java/pom.xml b/sdks/java/pom.xml
index 48e0cc8..f2f321c 100644
--- a/sdks/java/pom.xml
+++ b/sdks/java/pom.xml
@@ -19,7 +19,7 @@
com.firefly
flycanon-sdk
- 26.10.4
+ 26.10.5
jar
flycanon-sdk
diff --git a/sdks/java/src/main/java/com/firefly/flycanon/sdk/CanonClient.java b/sdks/java/src/main/java/com/firefly/flycanon/sdk/CanonClient.java
index ba33efd..2510a26 100644
--- a/sdks/java/src/main/java/com/firefly/flycanon/sdk/CanonClient.java
+++ b/sdks/java/src/main/java/com/firefly/flycanon/sdk/CanonClient.java
@@ -94,7 +94,7 @@ public final class CanonClient {
static final String HEADER_IDEMPOTENCY_KEY = "Idempotency-Key";
/** SDK version. Mirrors {@code pom.xml}. */
- public static final String SDK_VERSION = "26.10.4";
+ public static final String SDK_VERSION = "26.10.5";
private final RestClient restClient;
private final ObjectMapper mapper;
diff --git a/sdks/python/CHANGELOG.md b/sdks/python/CHANGELOG.md
index c906323..3892cad 100644
--- a/sdks/python/CHANGELOG.md
+++ b/sdks/python/CHANGELOG.md
@@ -2,6 +2,12 @@
All notable changes to **flycanon-sdk** (Python) are documented here.
+## [26.10.5] - 2026-10-17
+
+### Changed
+
+- Align package and User-Agent version metadata with the server's installed migration CLI fix. SDK behavior and HTTP contracts are unchanged; the `26.10.4` SDK remains compatible.
+
## [26.10.0] - 2026-10-12
### Changed
diff --git a/sdks/python/README.md b/sdks/python/README.md
index 1ca31b2..3029e8d 100644
--- a/sdks/python/README.md
+++ b/sdks/python/README.md
@@ -33,7 +33,7 @@ Compatible with **flycanon service version `26.10.x`**.
| SDK | Service |
|-----|---------|
-| `26.10.4` | `26.10.x` (same request/response schemas; workspace creation requires matching scope) |
+| `26.10.5` | `26.10.x` (same request/response schemas; workspace creation requires matching scope) |
| `26.8.0` | `26.8.x` (embedding sets, `flycanon reindex`, `ChunkStats.by_embedding_model`) |
| `26.7.1` | `26.7.x` (routes realigned: `?mode=async`, `/query/stream`, `/conversations/{id}/turn`, `/query/suggest`, `?after_id=`; `X-API-Key`; `delete_source`, `purge_workspace`) |
| `26.5.7` | `26.5.x` |
@@ -41,9 +41,9 @@ Compatible with **flycanon service version `26.10.x`**.
## Install
```bash
-uv add https://github.com/fireflyframework/flycanon/releases/download/v26.10.4/flycanon_sdk-26.10.4-py3-none-any.whl
+uv add https://github.com/fireflyframework/flycanon/releases/download/v26.10.5/flycanon_sdk-26.10.5-py3-none-any.whl
# or
-pip install https://github.com/fireflyframework/flycanon/releases/download/v26.10.4/flycanon_sdk-26.10.4-py3-none-any.whl
+pip install https://github.com/fireflyframework/flycanon/releases/download/v26.10.5/flycanon_sdk-26.10.5-py3-none-any.whl
```
## Quick start
diff --git a/sdks/python/pyproject.toml b/sdks/python/pyproject.toml
index 2d446c9..dfcf84e 100644
--- a/sdks/python/pyproject.toml
+++ b/sdks/python/pyproject.toml
@@ -2,7 +2,7 @@
# SPDX-License-Identifier: Apache-2.0
[project]
name = "flycanon-sdk"
-version = "26.10.4"
+version = "26.10.5"
description = "Async-first Python client for the flycanon Operational Knowledge Repository service."
readme = "README.md"
requires-python = ">=3.11"
diff --git a/sdks/python/src/flycanon_sdk/__init__.py b/sdks/python/src/flycanon_sdk/__init__.py
index 7cef567..9597f09 100644
--- a/sdks/python/src/flycanon_sdk/__init__.py
+++ b/sdks/python/src/flycanon_sdk/__init__.py
@@ -120,7 +120,7 @@
WorkspaceUpdated,
)
-__version__ = "26.10.4"
+__version__ = "26.10.5"
__all__ = [
"CANON_WORKSPACES_TOPIC",
diff --git a/sdks/python/tests/test_client.py b/sdks/python/tests/test_client.py
index ecede67..70930a1 100644
--- a/sdks/python/tests/test_client.py
+++ b/sdks/python/tests/test_client.py
@@ -592,7 +592,7 @@ async def test_answer_returns_typed_response() -> None:
def test_sdk_version_declares_current_release() -> None:
- assert __version__ == "26.10.4"
+ assert __version__ == "26.10.5"
def test_canon_workspaces_topic_constant() -> None:
@@ -674,7 +674,7 @@ async def test_user_agent_advertises_sdk_version() -> None:
}
)
await client.version()
- assert "flycanon-sdk-python/26.10.4" in route.calls.last.request.headers["User-Agent"]
+ assert "flycanon-sdk-python/26.10.5" in route.calls.last.request.headers["User-Agent"]
# ----------------------------------------------------------------------
diff --git a/sdks/python/uv.lock b/sdks/python/uv.lock
index 65a238c..4cfaa10 100644
--- a/sdks/python/uv.lock
+++ b/sdks/python/uv.lock
@@ -44,7 +44,7 @@ wheels = [
[[package]]
name = "flycanon-sdk"
-version = "26.10.4"
+version = "26.10.5"
source = { editable = "." }
dependencies = [
{ name = "httpx" },
diff --git a/src/flycanon/cli.py b/src/flycanon/cli.py
index 54fc6f8..44d52bc 100644
--- a/src/flycanon/cli.py
+++ b/src/flycanon/cli.py
@@ -23,7 +23,8 @@
module is on the path; the subcommand is
silently dropped from the parser otherwise so
a bare bootstrap install still parses.
-* ``flycanon migrate`` -- run ``alembic upgrade head`` against the DB.
+* ``flycanon migrate`` -- run ``alembic upgrade head`` against the DB,
+ using ``alembic.ini`` in the working directory.
* ``flycanon reindex`` -- re-embed a corpus into a new embedding set and
switch to it atomically, plus ``--list`` /
``--activate`` / ``--rollback`` / ``--drop-set``.
@@ -44,6 +45,7 @@
import logging
import os
import sys
+from pathlib import Path
from flycanon.config import get_settings
@@ -329,12 +331,20 @@ def _confirmed(yes: bool) -> bool:
def cmd_migrate(_: argparse.Namespace) -> int:
- """Apply Alembic migrations."""
+ """Apply Alembic migrations from the operator's working directory."""
from alembic import command
from alembic.config import Config as AlembicConfig
- here = os.path.dirname(os.path.abspath(__file__))
- cfg = AlembicConfig(os.path.join(here, "..", "..", "alembic.ini"))
+ config_path = Path.cwd() / "alembic.ini"
+ if not config_path.is_file():
+ print(
+ f"Migration configuration is missing or not a file: {config_path}. "
+ "Run from the working directory containing alembic.ini and migrations/ "
+ "from the matching release source archive.",
+ file=sys.stderr,
+ )
+ return 1
+ cfg = AlembicConfig(str(config_path))
settings = get_settings()
cfg.set_main_option("sqlalchemy.url", settings.database_url)
command.upgrade(cfg, "head")
@@ -369,7 +379,7 @@ def build_parser() -> argparse.ArgumentParser:
sub_worker = sub.add_parser("worker", help="Run the ingestion EDA worker")
sub_worker.set_defaults(func=cmd_worker)
- sub_migrate = sub.add_parser("migrate", help="Apply database migrations")
+ sub_migrate = sub.add_parser("migrate", help="Apply database migrations using ./alembic.ini")
sub_migrate.set_defaults(func=cmd_migrate)
sub_reindex = sub.add_parser(
diff --git a/tests/unit/test_cli_migrate.py b/tests/unit/test_cli_migrate.py
new file mode 100644
index 0000000..4afd211
--- /dev/null
+++ b/tests/unit/test_cli_migrate.py
@@ -0,0 +1,75 @@
+# Copyright 2026 Firefly Software Foundation
+# SPDX-License-Identifier: Apache-2.0
+"""Installed CLI migrations use the operator's reviewed working directory."""
+
+from __future__ import annotations
+
+import argparse
+
+import pytest
+from alembic import command
+
+from flycanon import cli
+from flycanon.config import get_settings
+
+
+@pytest.fixture(autouse=True)
+def _migration_settings(monkeypatch: pytest.MonkeyPatch):
+ monkeypatch.setenv("FLYCANON_DATABASE_URL", "postgresql+asyncpg://migration.invalid/flycanon")
+ get_settings.cache_clear()
+ yield
+ get_settings.cache_clear()
+
+
+def test_installed_migrate_uses_cwd_scaffold_and_configured_database(tmp_path, monkeypatch):
+ runtime = tmp_path / "operator-runtime"
+ runtime.mkdir()
+ (runtime / "alembic.ini").write_text("[alembic]\nscript_location = migrations\n")
+ monkeypatch.chdir(runtime)
+ monkeypatch.setattr(cli, "__file__", str(tmp_path / "wheel/site-packages/flycanon/cli.py"))
+ upgrades = []
+
+ def upgrade(config, revision):
+ upgrades.append(
+ (
+ config.config_file_name,
+ config.get_main_option("script_location"),
+ config.get_main_option("sqlalchemy.url"),
+ revision,
+ )
+ )
+
+ monkeypatch.setattr(command, "upgrade", upgrade)
+
+ assert cli.cmd_migrate(argparse.Namespace()) == 0
+ assert upgrades == [
+ (
+ str(runtime / "alembic.ini"),
+ "migrations",
+ "postgresql+asyncpg://migration.invalid/flycanon",
+ "head",
+ )
+ ]
+
+
+@pytest.mark.parametrize("config_is_directory", [False, True])
+def test_missing_cwd_config_refuses_before_upgrade_without_searching_parents(
+ tmp_path, monkeypatch, capsys, config_is_directory
+):
+ (tmp_path / "alembic.ini").write_text("[alembic]\nscript_location = migrations\n")
+ runtime = tmp_path / "operator-runtime"
+ runtime.mkdir()
+ if config_is_directory:
+ (runtime / "alembic.ini").mkdir()
+ monkeypatch.chdir(runtime)
+
+ def forbidden_upgrade(*args, **kwargs):
+ pytest.fail("Missing migration scaffold must be refused before database migration")
+
+ monkeypatch.setattr(command, "upgrade", forbidden_upgrade)
+
+ assert cli.cmd_migrate(argparse.Namespace()) == 1
+ error = capsys.readouterr().err
+ assert str(runtime / "alembic.ini") in error
+ assert "source archive" in error
+ assert "working directory" in error
diff --git a/tests/unit/test_concurrent_operations.py b/tests/unit/test_concurrent_operations.py
index 77fae4e..31eb8ba 100644
--- a/tests/unit/test_concurrent_operations.py
+++ b/tests/unit/test_concurrent_operations.py
@@ -42,7 +42,11 @@
from datetime import UTC, datetime, timedelta
import pytest
+import pytest_asyncio
+from sqlalchemy import text
+from sqlalchemy.ext.asyncio import create_async_engine
+from flycanon.models.entities import Base
from flycanon.models.entities.candidate import CandidateRow
from flycanon.models.entities.ingest_job import IngestJobRow
from flycanon.models.entities.knowledge_item import KnowledgeItemRow
@@ -54,6 +58,40 @@
_SCOPE: dict[str, str] = {"tenant_id": "default", "workspace_id": "default"}
+@pytest_asyncio.fixture
+async def engine(tmp_path):
+ # In-memory SQLite shares one transaction across concurrent AsyncSessions.
+ database = tmp_path / "concurrency.sqlite3"
+ instance = create_async_engine(f"sqlite+aiosqlite:///{database}", future=True)
+ async with instance.begin() as connection:
+ await connection.run_sync(Base.metadata.create_all)
+ try:
+ yield instance
+ finally:
+ await instance.dispose()
+
+
+@pytest.mark.asyncio
+async def test_concurrent_sessions_use_distinct_database_connections(engine):
+ async with engine.connect() as first, engine.connect() as second:
+ first_raw = await first.get_raw_connection()
+ second_raw = await second.get_raw_connection()
+ assert first_raw.driver_connection is not second_raw.driver_connection
+
+
+@pytest.mark.asyncio
+async def test_observer_rollback_cannot_discard_another_connection_write(engine):
+ async with engine.begin() as setup:
+ await setup.execute(text("CREATE TABLE transaction_isolation_probe (id INTEGER PRIMARY KEY)"))
+ async with engine.connect() as writer, engine.connect() as observer:
+ await writer.execute(text("INSERT INTO transaction_isolation_probe VALUES (1)"))
+ before = await observer.scalar(text("SELECT COUNT(*) FROM transaction_isolation_probe"))
+ await observer.rollback()
+ await writer.commit()
+ after = await observer.scalar(text("SELECT COUNT(*) FROM transaction_isolation_probe"))
+ assert {"before_commit": before, "after_commit": after} == {"before_commit": 0, "after_commit": 1}
+
+
@pytest.fixture
def jobs(repositories):
return repositories["ingest_job"]
diff --git a/uv.lock b/uv.lock
index 762f686..542837e 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1420,7 +1420,7 @@ wheels = [
[[package]]
name = "flycanon"
-version = "26.10.4"
+version = "26.10.5"
source = { editable = "." }
dependencies = [
{ name = "aiosqlite" },