From 603cdda8455dd817586ec29678e8052716024708 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 15:06:34 +0900 Subject: [PATCH 001/338] test(postgres): define driver replacement port contract --- tests/test_postgres_driver_port.py | 191 +++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 tests/test_postgres_driver_port.py diff --git a/tests/test_postgres_driver_port.py b/tests/test_postgres_driver_port.py new file mode 100644 index 00000000..122d7bd3 --- /dev/null +++ b/tests/test_postgres_driver_port.py @@ -0,0 +1,191 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any + +import pytest + +from pg_llm_batch.postgres_driver_port import ( + PostgresConnectionPort, + PostgresCursorPort, + PostgresDriverPort, +) + + +def test_cursor_port_covers_existing_database_interaction_surface() -> None: + assert PostgresCursorPort.__abstractmethods__ == { + "__enter__", + "__exit__", + "execute", + "executemany", + "fetchall", + "fetchmany", + "fetchone", + } + + +def test_connection_port_covers_transaction_and_cursor_lifecycle() -> None: + assert PostgresConnectionPort.__abstractmethods__ == { + "__enter__", + "__exit__", + "close", + "commit", + "cursor", + "execute", + "rollback", + } + + +def test_driver_port_covers_psycopg_replacement_capabilities_only() -> None: + assert PostgresDriverPort.__abstractmethods__ == { + "connect", + "is_undefined_function", + "jsonb", + "make_conninfo", + "parse_conninfo", + } + assert not hasattr(PostgresDriverPort, "discover_model") + assert not hasattr(PostgresDriverPort, "route_model") + assert not hasattr(PostgresDriverPort, "select_provider") + + +class _Cursor(PostgresCursorPort): + def __init__(self) -> None: + self.executions: list[tuple[str, object | None]] = [] + + def execute(self, query: str, params: object | None = None) -> _Cursor: + self.executions.append((query, params)) + return self + + def executemany(self, query: str, params_seq: object) -> _Cursor: + self.executions.append((query, params_seq)) + return self + + def fetchone(self) -> object | None: + return ("row",) + + def fetchmany(self, size: int) -> list[object]: + return [("row",)] * size + + def fetchall(self) -> list[object]: + return [("row",)] + + def __enter__(self) -> _Cursor: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + traceback: object | None, + ) -> None: + return None + + +class _Connection(PostgresConnectionPort): + def __init__(self) -> None: + self.cursor_instance = _Cursor() + self.committed = False + self.rolled_back = False + self.closed = False + + def cursor(self) -> _Cursor: + return self.cursor_instance + + def execute(self, query: str, params: object | None = None) -> _Cursor: + return self.cursor_instance.execute(query, params) + + def commit(self) -> None: + self.committed = True + + def rollback(self) -> None: + self.rolled_back = True + + def close(self) -> None: + self.closed = True + + def __enter__(self) -> _Connection: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + traceback: object | None, + ) -> None: + self.close() + + +class _UndefinedFunctionError(Exception): + pass + + +class _Driver(PostgresDriverPort): + def connect( + self, + dsn: str, + *, + connect_timeout_seconds: float | None = None, + ) -> _Connection: + assert dsn == "service=pg_llm_batch" + assert connect_timeout_seconds == 5.0 + return _Connection() + + def parse_conninfo(self, dsn: str) -> Mapping[str, str]: + key, value = dsn.split("=", 1) + return {key: value} + + def make_conninfo(self, params: Mapping[str, str]) -> str: + return " ".join(f"{key}={value}" for key, value in sorted(params.items())) + + def jsonb(self, value: object) -> object: + return ("jsonb", value) + + def is_undefined_function(self, error: BaseException) -> bool: + return isinstance(error, _UndefinedFunctionError) + + +def test_complete_port_can_run_without_psycopg_types() -> None: + driver = _Driver() + + connection = driver.connect( + "service=pg_llm_batch", + connect_timeout_seconds=5.0, + ) + with connection as active_connection: + with active_connection.cursor() as cursor: + cursor.execute("SELECT %s", ("tenant-a",)) + cursor.executemany("SELECT %s", [("tenant-a",), ("tenant-b",)]) + assert cursor.fetchone() == ("row",) + assert cursor.fetchmany(1) == [("row",)] + assert cursor.fetchall() == [("row",)] + active_connection.commit() + + assert connection.committed is True + assert connection.closed is True + assert driver.parse_conninfo("service=pg_llm_batch") == { + "service": "pg_llm_batch" + } + assert driver.make_conninfo({"service": "pg_llm_batch"}) == ( + "service=pg_llm_batch" + ) + assert driver.jsonb({"request_id": "opaque"}) == ( + "jsonb", + {"request_id": "opaque"}, + ) + assert driver.is_undefined_function(_UndefinedFunctionError()) is True + assert driver.is_undefined_function(RuntimeError()) is False + + +def test_incomplete_driver_cannot_be_instantiated() -> None: + class _IncompleteDriver(PostgresDriverPort): + def connect( + self, + dsn: str, + *, + connect_timeout_seconds: float | None = None, + ) -> PostgresConnectionPort: + raise AssertionError("not called") + + with pytest.raises(TypeError): + _IncompleteDriver() From 7ff3036e2634b454aa453f8456f06eb829041685 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 15:07:12 +0900 Subject: [PATCH 002/338] feat(postgres): add driver-neutral database port --- pg_llm_batch/postgres_driver_port.py | 222 +++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 pg_llm_batch/postgres_driver_port.py diff --git a/pg_llm_batch/postgres_driver_port.py b/pg_llm_batch/postgres_driver_port.py new file mode 100644 index 00000000..57096fd2 --- /dev/null +++ b/pg_llm_batch/postgres_driver_port.py @@ -0,0 +1,222 @@ +"""Provider-neutral PostgreSQL driver contracts for runtime decoupling. + +The package currently has direct Psycopg coupling at several infrastructure +boundaries. These abstract ports describe the database capabilities those +callers actually need without choosing a concrete PostgreSQL driver. Concrete +adapters remain infrastructure concerns and must preserve parameterized SQL, +transaction semantics, connection-string handling, JSONB adaptation, and +PostgreSQL error classification. +""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from collections.abc import Mapping + + +class PostgresCursorPort(ABC): + """Describe the synchronous cursor surface used by pg-llm-batch. + + Implementations must preserve parameter binding rather than interpolating + SQL text themselves. The fetch methods intentionally expose driver-neutral + Python objects because individual bounded contexts validate row shapes at + their own trust boundaries. + """ + + @abstractmethod + def execute( + self, + query: str, + params: object | None = None, + ) -> PostgresCursorPort: + """Execute one parameterized PostgreSQL operation and retain the cursor. + + ``query`` is package-authored SQL and ``params`` carries bound values. + Implementations must not downgrade this call into string formatting or + another transport that changes PostgreSQL parameter semantics. + """ + + @abstractmethod + def executemany(self, query: str, params_seq: object) -> PostgresCursorPort: + """Execute one package-authored operation for a parameter sequence. + + Concrete adapters are responsible for preserving the driver's normal + transactional behavior and must not silently commit between entries. + """ + + @abstractmethod + def fetchone(self) -> object | None: + """Return the next driver row, or ``None`` when no row remains. + + Domain code remains responsible for validating the returned row's exact + shape and primitive types before treating database evidence as trusted. + """ + + @abstractmethod + def fetchmany(self, size: int) -> list[object]: + """Return at most ``size`` rows through a finite materialization call. + + Bounded contexts use this operation when an explicit row budget is part + of the product contract; adapters must preserve that finite request. + """ + + @abstractmethod + def fetchall(self) -> list[object]: + """Return all rows for callers whose query already has a bounded result. + + This method exists for compatibility with current package code. New + untrusted-result paths should prefer a bounded query and ``fetchmany``. + """ + + @abstractmethod + def __enter__(self) -> PostgresCursorPort: + """Enter the cursor context without changing transaction ownership. + + Connection-level transaction semantics remain owned by the surrounding + connection port rather than being hidden in cursor entry. + """ + + @abstractmethod + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + traceback: object | None, + ) -> bool | None: + """Leave the cursor context and release adapter-owned cursor resources. + + Returning a truthy value may suppress an exception, so concrete + adapters should preserve their underlying driver's normal behavior. + """ + + +class PostgresConnectionPort(ABC): + """Describe the synchronous PostgreSQL connection capability the package uses. + + The port deliberately includes explicit commit and rollback operations so a + replacement driver cannot weaken the repository's transaction, RLS, replay, + or recovery contracts by hiding transaction ownership behind an adapter. + """ + + @abstractmethod + def cursor(self) -> PostgresCursorPort: + """Create a cursor bound to this connection's current transaction. + + The returned cursor must implement ``PostgresCursorPort`` semantics and + must not open a second implicit connection. + """ + + @abstractmethod + def execute( + self, + query: str, + params: object | None = None, + ) -> PostgresCursorPort: + """Execute one parameterized statement using this exact connection. + + This convenience operation must retain the same transaction and session + state, including transaction-local tenant ``set_config`` values. + """ + + @abstractmethod + def commit(self) -> None: + """Commit the current local PostgreSQL transaction. + + A successful return means only that the concrete driver reported local + transaction commit; it does not imply distributed exactly-once delivery. + """ + + @abstractmethod + def rollback(self) -> None: + """Roll back the current local PostgreSQL transaction. + + Adapters must preserve PostgreSQL rollback behavior and must not convert + rollback failures into a successful application outcome. + """ + + @abstractmethod + def close(self) -> None: + """Release the concrete database connection and its session authority. + + Implementations must not keep an implicit reusable connection alive + after callers intentionally close this capability. + """ + + @abstractmethod + def __enter__(self) -> PostgresConnectionPort: + """Enter the connection context using the concrete driver's semantics. + + The adapter must preserve whether normal context exit commits or rolls + back rather than inventing a different transaction policy. + """ + + @abstractmethod + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + traceback: object | None, + ) -> bool | None: + """Leave the connection context and preserve driver error propagation. + + Concrete adapters remain responsible for matching their documented + commit, rollback, and cleanup behavior on normal and exceptional exit. + """ + + +class PostgresDriverPort(ABC): + """Define the PostgreSQL-driver anti-corruption layer required by the package. + + This port owns no model discovery, provider routing, LLM credentials, or + batch-provider selection. It exists solely to let pg-llm-batch replace a + concrete PostgreSQL client while keeping its database and tenant contracts + stable and testable. + """ + + @abstractmethod + def connect( + self, + dsn: str, + *, + connect_timeout_seconds: float | None = None, + ) -> PostgresConnectionPort: + """Open one synchronous PostgreSQL connection for a validated DSN. + + The concrete adapter must honor the requested finite connection timeout + when supplied and return a connection whose transaction/session behavior + conforms to ``PostgresConnectionPort``. + """ + + @abstractmethod + def parse_conninfo(self, dsn: str) -> Mapping[str, str]: + """Parse a PostgreSQL connection selector without exposing credentials. + + The returned mapping is used only for deterministic policy decisions; + callers remain responsible for rejecting credential-bearing selectors + where their boundary requires a credential-free DSN. + """ + + @abstractmethod + def make_conninfo(self, params: Mapping[str, str]) -> str: + """Render validated PostgreSQL connection parameters safely. + + Concrete adapters must use their reviewed conninfo quoting rules rather + than ad-hoc concatenation when values may contain PostgreSQL syntax. + """ + + @abstractmethod + def jsonb(self, value: object) -> object: + """Adapt one validated Python value for a PostgreSQL JSONB parameter. + + The adapter may return a driver-specific wrapper, but that wrapper must + remain confined behind this infrastructure boundary and out of domain + models and public package contracts. + """ + + @abstractmethod + def is_undefined_function(self, error: BaseException) -> bool: + """Classify the PostgreSQL undefined-function error without leaking it. + + Token-counting fallback logic needs this narrow database-error category; + adapters must not broaden it to unrelated provider or application errors. + """ From 2139d04c54629eb05b8d7cd3616f05991b938d0d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 15:09:28 +0900 Subject: [PATCH 003/338] test(postgres): cover connection lifecycle capabilities --- tests/test_postgres_driver_port.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/test_postgres_driver_port.py b/tests/test_postgres_driver_port.py index 122d7bd3..2dc184fa 100644 --- a/tests/test_postgres_driver_port.py +++ b/tests/test_postgres_driver_port.py @@ -1,7 +1,6 @@ from __future__ import annotations from collections.abc import Mapping -from typing import Any import pytest @@ -32,7 +31,9 @@ def test_connection_port_covers_transaction_and_cursor_lifecycle() -> None: "commit", "cursor", "execute", + "is_closed", "rollback", + "set_autocommit", } @@ -88,6 +89,7 @@ def __init__(self) -> None: self.committed = False self.rolled_back = False self.closed = False + self.autocommit = False def cursor(self) -> _Cursor: return self.cursor_instance @@ -101,6 +103,12 @@ def commit(self) -> None: def rollback(self) -> None: self.rolled_back = True + def set_autocommit(self, enabled: bool) -> None: + self.autocommit = enabled + + def is_closed(self) -> bool: + return self.closed + def close(self) -> None: self.closed = True @@ -152,6 +160,10 @@ def test_complete_port_can_run_without_psycopg_types() -> None: "service=pg_llm_batch", connect_timeout_seconds=5.0, ) + assert connection.is_closed() is False + connection.set_autocommit(True) + assert connection.autocommit is True + with connection as active_connection: with active_connection.cursor() as cursor: cursor.execute("SELECT %s", ("tenant-a",)) @@ -162,7 +174,7 @@ def test_complete_port_can_run_without_psycopg_types() -> None: active_connection.commit() assert connection.committed is True - assert connection.closed is True + assert connection.is_closed() is True assert driver.parse_conninfo("service=pg_llm_batch") == { "service": "pg_llm_batch" } From 001cf7d48cd5af90f638c61ac50867f89b09aab5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 15:10:06 +0900 Subject: [PATCH 004/338] feat(postgres): preserve autocommit and closed-state contracts --- pg_llm_batch/postgres_driver_port.py | 34 +++++++++++++++++++++------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/pg_llm_batch/postgres_driver_port.py b/pg_llm_batch/postgres_driver_port.py index 57096fd2..3f6db501 100644 --- a/pg_llm_batch/postgres_driver_port.py +++ b/pg_llm_batch/postgres_driver_port.py @@ -1,8 +1,8 @@ """Provider-neutral PostgreSQL driver contracts for runtime decoupling. The package currently has direct Psycopg coupling at several infrastructure -boundaries. These abstract ports describe the database capabilities those -callers actually need without choosing a concrete PostgreSQL driver. Concrete +boundaries. These abstract ports describe the database capabilities those +callers actually need without choosing a concrete PostgreSQL driver. Concrete adapters remain infrastructure concerns and must preserve parameterized SQL, transaction semantics, connection-string handling, JSONB adaptation, and PostgreSQL error classification. @@ -18,7 +18,7 @@ class PostgresCursorPort(ABC): """Describe the synchronous cursor surface used by pg-llm-batch. Implementations must preserve parameter binding rather than interpolating - SQL text themselves. The fetch methods intentionally expose driver-neutral + SQL text themselves. The fetch methods intentionally expose driver-neutral Python objects because individual bounded contexts validate row shapes at their own trust boundaries. """ @@ -64,7 +64,7 @@ def fetchmany(self, size: int) -> list[object]: def fetchall(self) -> list[object]: """Return all rows for callers whose query already has a bounded result. - This method exists for compatibility with current package code. New + This method exists for compatibility with current package code. New untrusted-result paths should prefer a bounded query and ``fetchmany``. """ @@ -93,9 +93,10 @@ def __exit__( class PostgresConnectionPort(ABC): """Describe the synchronous PostgreSQL connection capability the package uses. - The port deliberately includes explicit commit and rollback operations so a - replacement driver cannot weaken the repository's transaction, RLS, replay, - or recovery contracts by hiding transaction ownership behind an adapter. + The port deliberately keeps transaction mode and closed-state inspection + explicit because current token-counting, configuration, and batch assembly + paths depend on those semantics. A replacement driver must not weaken RLS, + replay, or recovery behavior by hiding them behind an opaque wrapper. """ @abstractmethod @@ -134,6 +135,23 @@ def rollback(self) -> None: rollback failures into a successful application outcome. """ + @abstractmethod + def set_autocommit(self, enabled: bool) -> None: + """Select explicit connection autocommit behavior without attribute leakage. + + Existing runtime paths intentionally use both autocommit and explicit + transactions. Concrete adapters translate this operation into their + native API while preserving PostgreSQL transaction and session scope. + """ + + @abstractmethod + def is_closed(self) -> bool: + """Report whether this concrete connection can still execute work. + + Cached connection owners use this signal to reconnect deterministically + instead of issuing work through a connection the driver has closed. + """ + @abstractmethod def close(self) -> None: """Release the concrete database connection and its session authority. @@ -168,7 +186,7 @@ class PostgresDriverPort(ABC): """Define the PostgreSQL-driver anti-corruption layer required by the package. This port owns no model discovery, provider routing, LLM credentials, or - batch-provider selection. It exists solely to let pg-llm-batch replace a + batch-provider selection. It exists solely to let pg-llm-batch replace a concrete PostgreSQL client while keeping its database and tenant contracts stable and testable. """ From 3fe4442ee7eeef74189d46103b54ec5778203eee Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 15:14:25 +0900 Subject: [PATCH 005/338] test(postgres): require affected-row contract --- tests/test_postgres_driver_port.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_postgres_driver_port.py b/tests/test_postgres_driver_port.py index 2dc184fa..e93199db 100644 --- a/tests/test_postgres_driver_port.py +++ b/tests/test_postgres_driver_port.py @@ -20,6 +20,7 @@ def test_cursor_port_covers_existing_database_interaction_surface() -> None: "fetchall", "fetchmany", "fetchone", + "row_count", } @@ -53,13 +54,16 @@ def test_driver_port_covers_psycopg_replacement_capabilities_only() -> None: class _Cursor(PostgresCursorPort): def __init__(self) -> None: self.executions: list[tuple[str, object | None]] = [] + self.affected_rows = 0 def execute(self, query: str, params: object | None = None) -> _Cursor: self.executions.append((query, params)) + self.affected_rows = 1 return self def executemany(self, query: str, params_seq: object) -> _Cursor: self.executions.append((query, params_seq)) + self.affected_rows = 2 return self def fetchone(self) -> object | None: @@ -71,6 +75,9 @@ def fetchmany(self, size: int) -> list[object]: def fetchall(self) -> list[object]: return [("row",)] + def row_count(self) -> int: + return self.affected_rows + def __enter__(self) -> _Cursor: return self @@ -167,7 +174,9 @@ def test_complete_port_can_run_without_psycopg_types() -> None: with connection as active_connection: with active_connection.cursor() as cursor: cursor.execute("SELECT %s", ("tenant-a",)) + assert cursor.row_count() == 1 cursor.executemany("SELECT %s", [("tenant-a",), ("tenant-b",)]) + assert cursor.row_count() == 2 assert cursor.fetchone() == ("row",) assert cursor.fetchmany(1) == [("row",)] assert cursor.fetchall() == [("row",)] From 33607e3e84ed401972920e199d5169394ce23fc1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 15:16:57 +0900 Subject: [PATCH 006/338] feat(postgres): preserve affected-row semantics --- pg_llm_batch/postgres_driver_port.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pg_llm_batch/postgres_driver_port.py b/pg_llm_batch/postgres_driver_port.py index 3f6db501..16dcf325 100644 --- a/pg_llm_batch/postgres_driver_port.py +++ b/pg_llm_batch/postgres_driver_port.py @@ -68,6 +68,17 @@ def fetchall(self) -> list[object]: untrusted-result paths should prefer a bounded query and ``fetchmany``. """ + @abstractmethod + def row_count(self) -> int: + """Return the concrete driver's affected-row count for the last operation. + + Existing persistence paths use the count to detect missing updates and + partial batch membership writes. The adapter must preserve the driver's + integer semantics rather than guessing success when the count is unknown; + consumers that require an exact count remain responsible for failing + closed on a driver-specific unknown sentinel. + """ + @abstractmethod def __enter__(self) -> PostgresCursorPort: """Enter the cursor context without changing transaction ownership. From c9d5ee07c11076ea61bdaeab36728126caa36f23 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 15:33:15 +0900 Subject: [PATCH 007/338] test(postgres): define psycopg adapter parity contract --- tests/test_psycopg_driver_adapter.py | 248 +++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 tests/test_psycopg_driver_adapter.py diff --git a/tests/test_psycopg_driver_adapter.py b/tests/test_psycopg_driver_adapter.py new file mode 100644 index 00000000..b96a449a --- /dev/null +++ b/tests/test_psycopg_driver_adapter.py @@ -0,0 +1,248 @@ +from __future__ import annotations + +from typing import Any + +import pytest +from psycopg.errors import UndefinedFunction +from psycopg.types.json import Jsonb + +from pg_llm_batch.postgres_driver_port import PostgresDriverPort +from pg_llm_batch.psycopg_driver_adapter import ( + PsycopgConnectionAdapter, + PsycopgCursorAdapter, + PsycopgDriverAdapter, + PsycopgDriverAdapterError, +) + + +class _RawCursor: + def __init__(self) -> None: + self.executions: list[tuple[str, object | None]] = [] + self.many_executions: list[tuple[str, object]] = [] + self.rows: list[object] = [("one",), ("two",)] + self.rowcount: object = 2 + self.entered = False + self.exited = False + + def execute(self, query: str, params: object | None = None) -> _RawCursor: + self.executions.append((query, params)) + return self + + def executemany(self, query: str, params_seq: object) -> None: + self.many_executions.append((query, params_seq)) + + def fetchone(self) -> object | None: + return self.rows[0] if self.rows else None + + def fetchmany(self, size: int) -> list[object]: + return list(self.rows[:size]) + + def fetchall(self) -> list[object]: + return list(self.rows) + + def __enter__(self) -> _RawCursor: + self.entered = True + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + traceback: object | None, + ) -> bool | None: + self.exited = True + return None + + +class _RawConnection: + def __init__(self) -> None: + self.autocommit = False + self.closed = False + self.cursor_value = _RawCursor() + self.commits = 0 + self.rollbacks = 0 + self.close_calls = 0 + self.entered = False + self.exited = False + + def cursor(self) -> _RawCursor: + return self.cursor_value + + def execute(self, query: str, params: object | None = None) -> _RawCursor: + return self.cursor_value.execute(query, params) + + def commit(self) -> None: + self.commits += 1 + + def rollback(self) -> None: + self.rollbacks += 1 + + def close(self) -> None: + self.close_calls += 1 + self.closed = True + + def __enter__(self) -> _RawConnection: + self.entered = True + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + traceback: object | None, + ) -> bool | None: + self.exited = True + return None + + +def test_psycopg_driver_is_provider_neutral_port_implementation() -> None: + assert isinstance(PsycopgDriverAdapter(), PostgresDriverPort) + + +def test_cursor_adapter_preserves_parameter_and_result_authority() -> None: + raw = _RawCursor() + cursor = PsycopgCursorAdapter(raw) + + assert cursor.execute("SELECT %s", ("tenant-a",)) is cursor + assert raw.executions == [("SELECT %s", ("tenant-a",))] + + batch = [(1,), (2,)] + assert cursor.executemany("INSERT INTO example VALUES (%s)", batch) is cursor + assert raw.many_executions == [("INSERT INTO example VALUES (%s)", batch)] + + assert cursor.fetchone() == ("one",) + assert cursor.fetchmany(1) == [("one",)] + assert cursor.fetchall() == [("one",), ("two",)] + assert cursor.row_count() == 2 + + +def test_cursor_adapter_rejects_non_integer_row_count() -> None: + raw = _RawCursor() + raw.rowcount = True + + with pytest.raises(PsycopgDriverAdapterError, match="row count"): + PsycopgCursorAdapter(raw).row_count() + + +def test_cursor_adapter_preserves_context_manager_boundary() -> None: + raw = _RawCursor() + cursor = PsycopgCursorAdapter(raw) + + with cursor as entered: + assert entered is cursor + + assert raw.entered is True + assert raw.exited is True + + +def test_connection_adapter_preserves_transaction_and_session_semantics() -> None: + raw = _RawConnection() + connection = PsycopgConnectionAdapter(raw) + + cursor = connection.cursor() + assert isinstance(cursor, PsycopgCursorAdapter) + assert cursor.execute("SELECT %s", (1,)) is cursor + + direct = connection.execute("SELECT %s", (2,)) + assert isinstance(direct, PsycopgCursorAdapter) + assert raw.cursor_value.executions[-1] == ("SELECT %s", (2,)) + + connection.commit() + connection.rollback() + assert raw.commits == 1 + assert raw.rollbacks == 1 + + connection.set_autocommit(True) + assert raw.autocommit is True + assert connection.is_closed() is False + + connection.close() + assert raw.close_calls == 1 + assert connection.is_closed() is True + + +def test_connection_adapter_rejects_non_boolean_autocommit() -> None: + raw = _RawConnection() + + with pytest.raises(PsycopgDriverAdapterError, match="autocommit"): + PsycopgConnectionAdapter(raw).set_autocommit(1) # type: ignore[arg-type] + + assert raw.autocommit is False + + +def test_connection_adapter_preserves_context_manager_boundary() -> None: + raw = _RawConnection() + connection = PsycopgConnectionAdapter(raw) + + with connection as entered: + assert entered is connection + + assert raw.entered is True + assert raw.exited is True + + +def test_driver_uses_psycopg_conninfo_and_jsonb_contracts() -> None: + driver = PsycopgDriverAdapter() + + parsed = driver.parse_conninfo("host=localhost dbname='batch db' application_name=pg-llm-batch") + assert parsed == { + "host": "localhost", + "dbname": "batch db", + "application_name": "pg-llm-batch", + } + + rendered = driver.make_conninfo(parsed) + assert driver.parse_conninfo(rendered) == parsed + + adapted = driver.jsonb({"count": 1}) + assert isinstance(adapted, Jsonb) + assert adapted.obj == {"count": 1} + + +def test_driver_classifies_only_psycopg_undefined_function() -> None: + driver = PsycopgDriverAdapter() + + assert driver.is_undefined_function(UndefinedFunction("missing")) is True + assert driver.is_undefined_function(RuntimeError("missing")) is False + + +def test_driver_connect_preserves_exact_timeout_and_wraps_connection( + monkeypatch: pytest.MonkeyPatch, +) -> None: + raw = _RawConnection() + calls: list[tuple[str, dict[str, Any]]] = [] + + def fake_connect(dsn: str, **kwargs: Any) -> _RawConnection: + calls.append((dsn, kwargs)) + return raw + + monkeypatch.setattr("pg_llm_batch.psycopg_driver_adapter.psycopg.connect", fake_connect) + + connection = PsycopgDriverAdapter().connect( + "host=localhost dbname=batch", + connect_timeout_seconds=7, + ) + + assert isinstance(connection, PsycopgConnectionAdapter) + assert calls == [("host=localhost dbname=batch", {"connect_timeout": 7})] + + +def test_driver_connect_fails_closed_on_invalid_timeout_before_driver_call( + monkeypatch: pytest.MonkeyPatch, +) -> None: + called = False + + def fake_connect(*args: object, **kwargs: object) -> None: + nonlocal called + called = True + + monkeypatch.setattr("pg_llm_batch.psycopg_driver_adapter.psycopg.connect", fake_connect) + + for invalid in (True, 0, -1, 1.5): + with pytest.raises(PsycopgDriverAdapterError, match="timeout"): + PsycopgDriverAdapter().connect( + "host=localhost dbname=batch", + connect_timeout_seconds=invalid, # type: ignore[arg-type] + ) + + assert called is False From 77192591e1cc09b4316b52111a60c7086d0c3df7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 15:34:14 +0900 Subject: [PATCH 008/338] feat(postgres): adapt current psycopg behavior behind driver port --- pg_llm_batch/psycopg_driver_adapter.py | 202 +++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 pg_llm_batch/psycopg_driver_adapter.py diff --git a/pg_llm_batch/psycopg_driver_adapter.py b/pg_llm_batch/psycopg_driver_adapter.py new file mode 100644 index 00000000..944df604 --- /dev/null +++ b/pg_llm_batch/psycopg_driver_adapter.py @@ -0,0 +1,202 @@ +"""Psycopg adapter for the provider-neutral PostgreSQL driver port. + +This module is a migration baseline, not the commercial replacement itself. It +encapsulates the PostgreSQL-client behavior that existing pg-llm-batch code +currently receives from Psycopg so a future permissively licensed adapter can be +verified against the same transaction, parameter-binding, conninfo, JSONB, and +error-classification contract before the LGPL-family runtime dependency is +removed. +""" + +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any + +import psycopg +from psycopg.conninfo import conninfo_to_dict, make_conninfo +from psycopg.errors import UndefinedFunction +from psycopg.types.json import Jsonb + +from .postgres_driver_port import ( + PostgresConnectionPort, + PostgresCursorPort, + PostgresDriverPort, +) + + +class PsycopgDriverAdapterError(RuntimeError): + """Report a fixed adapter-contract failure without reflecting database data. + + The adapter uses this error only when a driver-facing primitive violates the + migration port itself, such as a non-boolean autocommit state or a row-count + value with the wrong Python type. PostgreSQL execution errors continue to + propagate through Psycopg so existing bounded contexts can classify them. + """ + + +class PsycopgCursorAdapter(PostgresCursorPort): + """Wrap one Psycopg-compatible cursor without changing its transaction owner. + + The wrapper deliberately performs no SQL rewriting. Package-authored query + text and bound parameters are handed to the retained cursor unchanged, while + row materialization remains subject to each caller's existing trust-boundary + validation. + """ + + def __init__(self, cursor: Any) -> None: + self._cursor = cursor + + def execute( + self, + query: str, + params: object | None = None, + ) -> PsycopgCursorAdapter: + """Execute one query with Psycopg parameter binding and retain this wrapper.""" + self._cursor.execute(query, params) + return self + + def executemany( + self, + query: str, + params_seq: object, + ) -> PsycopgCursorAdapter: + """Execute one query for a parameter sequence without implicit commits.""" + self._cursor.executemany(query, params_seq) + return self + + def fetchone(self) -> object | None: + """Return the next raw row for validation by the owning bounded context.""" + return self._cursor.fetchone() + + def fetchmany(self, size: int) -> list[object]: + """Return the finite row page requested by the owning bounded context.""" + return list(self._cursor.fetchmany(size)) + + def fetchall(self) -> list[object]: + """Return all rows only for callers whose query already bounds the result.""" + return list(self._cursor.fetchall()) + + def row_count(self) -> int: + """Return Psycopg's exact integer affected-row result, including -1 unknown.""" + value = self._cursor.rowcount + if type(value) is not int: + raise PsycopgDriverAdapterError("PostgreSQL driver row count is invalid") + return value + + def __enter__(self) -> PsycopgCursorAdapter: + """Enter the retained cursor context while preserving wrapper identity.""" + self._cursor.__enter__() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + traceback: object | None, + ) -> bool | None: + """Delegate cursor cleanup and exception propagation to Psycopg.""" + return self._cursor.__exit__(exc_type, exc, traceback) + + +class PsycopgConnectionAdapter(PostgresConnectionPort): + """Wrap one Psycopg connection while preserving its session and transaction. + + The same retained raw connection backs cursor creation, direct execution, + commit, rollback, autocommit selection, and closed-state inspection. The + adapter therefore cannot silently move tenant-local ``set_config`` state to + another connection. + """ + + def __init__(self, connection: Any) -> None: + self._connection = connection + + def cursor(self) -> PsycopgCursorAdapter: + """Create a wrapped cursor on this exact retained PostgreSQL connection.""" + return PsycopgCursorAdapter(self._connection.cursor()) + + def execute( + self, + query: str, + params: object | None = None, + ) -> PsycopgCursorAdapter: + """Execute through this connection without opening an implicit second one.""" + return PsycopgCursorAdapter(self._connection.execute(query, params)) + + def commit(self) -> None: + """Commit the current local PostgreSQL transaction through Psycopg.""" + self._connection.commit() + + def rollback(self) -> None: + """Roll back the current local PostgreSQL transaction through Psycopg.""" + self._connection.rollback() + + def set_autocommit(self, enabled: bool) -> None: + """Set Psycopg autocommit only from an exact boolean policy decision.""" + if type(enabled) is not bool: + raise PsycopgDriverAdapterError("PostgreSQL driver autocommit is invalid") + self._connection.autocommit = enabled + + def is_closed(self) -> bool: + """Return Psycopg's public closed-state signal without truthiness coercion.""" + value = self._connection.closed + if type(value) is not bool: + raise PsycopgDriverAdapterError("PostgreSQL driver closed state is invalid") + return value + + def close(self) -> None: + """Close the retained PostgreSQL connection and release its session state.""" + self._connection.close() + + def __enter__(self) -> PsycopgConnectionAdapter: + """Enter Psycopg's connection context while preserving wrapper identity.""" + self._connection.__enter__() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + traceback: object | None, + ) -> bool | None: + """Delegate transaction-context exit and error propagation to Psycopg.""" + return self._connection.__exit__(exc_type, exc, traceback) + + +class PsycopgDriverAdapter(PostgresDriverPort): + """Expose existing Psycopg behavior through the migration anti-corruption port. + + This class establishes a parity baseline only. It does not make Psycopg an + approved commercial dependency and it does not acquire model/provider routing + authority from contextual-orchestrator. + """ + + def connect( + self, + dsn: str, + *, + connect_timeout_seconds: int | None = None, + ) -> PsycopgConnectionAdapter: + """Connect with an optional exact positive libpq timeout in whole seconds.""" + kwargs: dict[str, int] = {} + if connect_timeout_seconds is not None: + if type(connect_timeout_seconds) is not int or connect_timeout_seconds <= 0: + raise PsycopgDriverAdapterError("PostgreSQL driver timeout is invalid") + kwargs["connect_timeout"] = connect_timeout_seconds + return PsycopgConnectionAdapter(psycopg.connect(dsn, **kwargs)) + + def parse_conninfo(self, dsn: str) -> Mapping[str, str]: + """Parse PostgreSQL conninfo using Psycopg/libpq-compatible quoting rules.""" + return conninfo_to_dict(dsn) + + def make_conninfo(self, params: Mapping[str, str]) -> str: + """Render PostgreSQL conninfo using Psycopg's reviewed quoting implementation.""" + return make_conninfo(**dict(params)) + + def jsonb(self, value: object) -> Jsonb: + """Wrap a validated Python value in Psycopg's JSONB parameter adapter.""" + return Jsonb(value) + + def is_undefined_function(self, error: BaseException) -> bool: + """Recognize only Psycopg's PostgreSQL undefined-function error category.""" + return isinstance(error, UndefinedFunction) From c71df81e4059fff5c2ae16f6623933779d7c1351 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 15:34:51 +0900 Subject: [PATCH 009/338] fix(postgres): align driver timeout contract with libpq seconds --- pg_llm_batch/postgres_driver_port.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pg_llm_batch/postgres_driver_port.py b/pg_llm_batch/postgres_driver_port.py index 16dcf325..85cd20e1 100644 --- a/pg_llm_batch/postgres_driver_port.py +++ b/pg_llm_batch/postgres_driver_port.py @@ -207,13 +207,13 @@ def connect( self, dsn: str, *, - connect_timeout_seconds: float | None = None, + connect_timeout_seconds: int | None = None, ) -> PostgresConnectionPort: """Open one synchronous PostgreSQL connection for a validated DSN. - The concrete adapter must honor the requested finite connection timeout - when supplied and return a connection whose transaction/session behavior - conforms to ``PostgresConnectionPort``. + The concrete adapter must honor the requested finite positive timeout in + whole seconds when supplied and return a connection whose transaction and + session behavior conforms to ``PostgresConnectionPort``. """ @abstractmethod From 43921215e01111c98d162f0493210e16c388a3b6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 15:37:59 +0900 Subject: [PATCH 010/338] test(postgres): keep driver port timeout contract exact --- tests/test_postgres_driver_port.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_postgres_driver_port.py b/tests/test_postgres_driver_port.py index e93199db..ddd21b24 100644 --- a/tests/test_postgres_driver_port.py +++ b/tests/test_postgres_driver_port.py @@ -140,10 +140,10 @@ def connect( self, dsn: str, *, - connect_timeout_seconds: float | None = None, + connect_timeout_seconds: int | None = None, ) -> _Connection: assert dsn == "service=pg_llm_batch" - assert connect_timeout_seconds == 5.0 + assert connect_timeout_seconds == 5 return _Connection() def parse_conninfo(self, dsn: str) -> Mapping[str, str]: @@ -165,7 +165,7 @@ def test_complete_port_can_run_without_psycopg_types() -> None: connection = driver.connect( "service=pg_llm_batch", - connect_timeout_seconds=5.0, + connect_timeout_seconds=5, ) assert connection.is_closed() is False connection.set_autocommit(True) @@ -204,7 +204,7 @@ def connect( self, dsn: str, *, - connect_timeout_seconds: float | None = None, + connect_timeout_seconds: int | None = None, ) -> PostgresConnectionPort: raise AssertionError("not called") From b97973d57f9d7ec58ca46a523db423bbacf1bfcd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 15:44:13 +0900 Subject: [PATCH 011/338] test(postgres): define commercial driver candidate acceptance --- tests/test_postgres_driver_candidate.py | 112 ++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 tests/test_postgres_driver_candidate.py diff --git a/tests/test_postgres_driver_candidate.py b/tests/test_postgres_driver_candidate.py new file mode 100644 index 00000000..36aba717 --- /dev/null +++ b/tests/test_postgres_driver_candidate.py @@ -0,0 +1,112 @@ +from __future__ import annotations + +import pytest + +from pg_llm_batch.postgres_driver_candidate import ( + REQUIRED_POSTGRES_DRIVER_CAPABILITIES, + PostgresDriverCandidateEvidence, + PostgresDriverCandidateEvidenceError, + evaluate_postgres_driver_candidate, +) + + +FULL_CAPABILITIES = frozenset(REQUIRED_POSTGRES_DRIVER_CAPABILITIES) +SOURCE_SHA = "a" * 40 +ARTIFACT_SHA256 = "b" * 64 + + +def _evidence(**overrides: object) -> PostgresDriverCandidateEvidence: + values: dict[str, object] = { + "package_name": "candidate-driver", + "package_version": "1.2.3", + "license_spdx": "BSD-3-Clause", + "python_versions": ("3.12", "3.13", "3.14"), + "source_commit_sha": SOURCE_SHA, + "artifact_sha256": ARTIFACT_SHA256, + "capabilities": FULL_CAPABILITIES, + } + values.update(overrides) + return PostgresDriverCandidateEvidence(**values) # type: ignore[arg-type] + + +def test_complete_permissive_candidate_is_eligible_only_for_parity_validation() -> None: + decision = evaluate_postgres_driver_candidate(_evidence()) + + assert decision.eligible_for_parity_validation is True + assert decision.production_approved is False + assert decision.reasons == () + + +@pytest.mark.parametrize( + ("license_spdx", "expected_reason"), + [ + ("LGPL-3.0-only", "license_not_approved"), + ("GPL-3.0-only", "license_not_approved"), + ("AGPL-3.0-only", "license_not_approved"), + ("LicenseRef-Proprietary", "license_not_approved"), + ], +) +def test_candidate_fails_closed_when_license_is_not_explicitly_permissive( + license_spdx: str, + expected_reason: str, +) -> None: + decision = evaluate_postgres_driver_candidate(_evidence(license_spdx=license_spdx)) + + assert decision.eligible_for_parity_validation is False + assert decision.production_approved is False + assert expected_reason in decision.reasons + + +def test_candidate_requires_explicit_python_314_support_evidence() -> None: + decision = evaluate_postgres_driver_candidate( + _evidence(python_versions=("3.12", "3.13")) + ) + + assert decision.eligible_for_parity_validation is False + assert decision.reasons == ("python_3_14_not_evidenced",) + + +def test_candidate_reports_every_missing_runtime_capability_deterministically() -> None: + decision = evaluate_postgres_driver_candidate( + _evidence(capabilities=frozenset({"parameterized_sql", "jsonb"})) + ) + + expected_missing = sorted( + FULL_CAPABILITIES - {"parameterized_sql", "jsonb"} + ) + assert decision.eligible_for_parity_validation is False + assert decision.reasons == tuple( + f"missing_capability:{capability}" for capability in expected_missing + ) + + +@pytest.mark.parametrize( + ("field_name", "value"), + [ + ("package_name", ""), + ("package_version", ""), + ("license_spdx", ""), + ("python_versions", ()), + ("source_commit_sha", "a" * 39), + ("source_commit_sha", "g" * 40), + ("artifact_sha256", "b" * 63), + ("artifact_sha256", "z" * 64), + ("capabilities", frozenset()), + ], +) +def test_candidate_rejects_incomplete_or_nonimmutable_evidence( + field_name: str, + value: object, +) -> None: + with pytest.raises(PostgresDriverCandidateEvidenceError): + _evidence(**{field_name: value}) + + +def test_candidate_rejects_ambiguous_python_version_tokens() -> None: + with pytest.raises(PostgresDriverCandidateEvidenceError, match="Python version"): + _evidence(python_versions=("3.14+",)) + + +def test_candidate_rejects_unknown_capability_names() -> None: + with pytest.raises(PostgresDriverCandidateEvidenceError, match="capability"): + _evidence(capabilities=FULL_CAPABILITIES | {"model_routing"}) From f22db09955b49ad60c4ae6e65a76b4fa5b3f7871 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 15:45:19 +0900 Subject: [PATCH 012/338] test(postgres): fail closed on mutable candidate evidence --- tests/test_postgres_driver_candidate.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_postgres_driver_candidate.py b/tests/test_postgres_driver_candidate.py index 36aba717..e5462230 100644 --- a/tests/test_postgres_driver_candidate.py +++ b/tests/test_postgres_driver_candidate.py @@ -84,14 +84,21 @@ def test_candidate_reports_every_missing_runtime_capability_deterministically() ("field_name", "value"), [ ("package_name", ""), + ("package_name", 7), ("package_version", ""), + ("package_version", False), ("license_spdx", ""), + ("license_spdx", object()), ("python_versions", ()), + ("python_versions", ["3.14"]), ("source_commit_sha", "a" * 39), ("source_commit_sha", "g" * 40), + ("source_commit_sha", 40), ("artifact_sha256", "b" * 63), ("artifact_sha256", "z" * 64), + ("artifact_sha256", 64), ("capabilities", frozenset()), + ("capabilities", {"parameterized_sql"}), ], ) def test_candidate_rejects_incomplete_or_nonimmutable_evidence( @@ -107,6 +114,11 @@ def test_candidate_rejects_ambiguous_python_version_tokens() -> None: _evidence(python_versions=("3.14+",)) +def test_candidate_rejects_non_string_python_version_tokens() -> None: + with pytest.raises(PostgresDriverCandidateEvidenceError, match="Python version"): + _evidence(python_versions=("3.14", 314)) + + def test_candidate_rejects_unknown_capability_names() -> None: with pytest.raises(PostgresDriverCandidateEvidenceError, match="capability"): _evidence(capabilities=FULL_CAPABILITIES | {"model_routing"}) From ec2e56c68fcd06f6a83334a6a7faf46bc9cd817a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 15:45:51 +0900 Subject: [PATCH 013/338] feat(postgres): gate commercial driver candidates fail closed --- pg_llm_batch/postgres_driver_candidate.py | 161 ++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 pg_llm_batch/postgres_driver_candidate.py diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py new file mode 100644 index 00000000..eceed160 --- /dev/null +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -0,0 +1,161 @@ +"""Fail-closed commercial acceptance for PostgreSQL driver candidates. + +The repository must replace its current LGPL-family Psycopg runtime dependency +without turning an unverified alternative into production authority. This module +keeps candidate package evidence immutable and decides only whether a candidate +has enough permissive-license, Python-version, artifact-identity, and capability +evidence to enter parity validation. Production approval remains a later gate +that requires a concrete adapter plus PostgreSQL/RLS/recovery/package evidence. +""" + +from __future__ import annotations + +from dataclasses import dataclass +import re + + +REQUIRED_POSTGRES_DRIVER_CAPABILITIES = frozenset( + { + "autocommit_state", + "connection_closed_state", + "connection_context", + "conninfo_parse_render", + "cursor_context", + "finite_connect_timeout", + "jsonb", + "parameterized_sql", + "row_count", + "transaction_commit_rollback", + "undefined_function_classification", + } +) +"""Capabilities a replacement driver must evidence before parity validation.""" + +_APPROVED_PERMISSIVE_LICENSES = frozenset( + { + "Apache-2.0", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "MIT", + "PostgreSQL", + } +) +_MINOR_PYTHON_VERSION = re.compile(r"^[1-9][0-9]*\.[0-9]+$") +_SOURCE_COMMIT_SHA = re.compile(r"^[0-9a-f]{40}$") +_ARTIFACT_SHA256 = re.compile(r"^[0-9a-f]{64}$") + + +class PostgresDriverCandidateEvidenceError(ValueError): + """Reject malformed or mutable evidence before commercial evaluation. + + Candidate metadata can influence a supply-chain migration decision, so the + evaluator accepts only immutable primitive evidence with exact digest and + version shapes. It never repairs or guesses malformed package metadata. + """ + + +@dataclass(frozen=True, slots=True) +class PostgresDriverCandidateEvidence: + """Describe one immutable PostgreSQL-driver package candidate. + + ``source_commit_sha`` identifies the reviewed source revision and + ``artifact_sha256`` identifies the exact distributable under evaluation. + ``python_versions`` and ``capabilities`` must contain explicit evidence rather + than inferred support from a nearby release or similar database driver. + """ + + package_name: str + package_version: str + license_spdx: str + python_versions: tuple[str, ...] + source_commit_sha: str + artifact_sha256: str + capabilities: frozenset[str] + + def __post_init__(self) -> None: + """Validate candidate evidence without normalizing ambiguous inputs.""" + for label, value in ( + ("package name", self.package_name), + ("package version", self.package_version), + ("license", self.license_spdx), + ): + if type(value) is not str or not value.strip(): + raise PostgresDriverCandidateEvidenceError( + f"PostgreSQL driver {label} evidence is invalid" + ) + if type(self.python_versions) is not tuple or not self.python_versions: + raise PostgresDriverCandidateEvidenceError( + "PostgreSQL driver Python version evidence is invalid" + ) + if any( + type(version) is not str or _MINOR_PYTHON_VERSION.fullmatch(version) is None + for version in self.python_versions + ): + raise PostgresDriverCandidateEvidenceError( + "PostgreSQL driver Python version evidence is invalid" + ) + if ( + type(self.source_commit_sha) is not str + or _SOURCE_COMMIT_SHA.fullmatch(self.source_commit_sha) is None + ): + raise PostgresDriverCandidateEvidenceError( + "PostgreSQL driver source commit evidence is invalid" + ) + if ( + type(self.artifact_sha256) is not str + or _ARTIFACT_SHA256.fullmatch(self.artifact_sha256) is None + ): + raise PostgresDriverCandidateEvidenceError( + "PostgreSQL driver artifact digest evidence is invalid" + ) + if type(self.capabilities) is not frozenset or not self.capabilities: + raise PostgresDriverCandidateEvidenceError( + "PostgreSQL driver capability evidence is invalid" + ) + unknown_capabilities = self.capabilities - REQUIRED_POSTGRES_DRIVER_CAPABILITIES + if unknown_capabilities: + raise PostgresDriverCandidateEvidenceError( + "PostgreSQL driver capability evidence contains an unknown capability" + ) + + +@dataclass(frozen=True, slots=True) +class PostgresDriverCandidateDecision: + """Record whether immutable evidence permits candidate parity validation. + + ``production_approved`` is deliberately always false in this stage. A package + that clears this evaluator still needs a concrete ``PostgresDriverPort`` + adapter and realistic PostgreSQL/RLS/concurrency/recovery/package gates. + """ + + eligible_for_parity_validation: bool + production_approved: bool + reasons: tuple[str, ...] + + +def evaluate_postgres_driver_candidate( + evidence: PostgresDriverCandidateEvidence, +) -> PostgresDriverCandidateDecision: + """Evaluate one candidate without promoting it to a production dependency. + + The decision fails closed when the SPDX identifier is not in the repository's + explicitly reviewed permissive set, Python 3.14 support is not evidenced, or + any runtime capability required by the migration port is absent. Reasons are + deterministic so CI and acquisition diligence can compare exact evidence. + """ + reasons: list[str] = [] + if evidence.license_spdx not in _APPROVED_PERMISSIVE_LICENSES: + reasons.append("license_not_approved") + if "3.14" not in evidence.python_versions: + reasons.append("python_3_14_not_evidenced") + missing_capabilities = REQUIRED_POSTGRES_DRIVER_CAPABILITIES - evidence.capabilities + reasons.extend( + f"missing_capability:{capability}" + for capability in sorted(missing_capabilities) + ) + return PostgresDriverCandidateDecision( + eligible_for_parity_validation=not reasons, + production_approved=False, + reasons=tuple(reasons), + ) From 2ca0f9aa10f5faefab506f6cde3f2dc5be149667 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 18:02:05 +0900 Subject: [PATCH 014/338] test(postgres): reject mutable candidate evidence --- tests/test_postgres_driver_candidate.py | 36 +++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/test_postgres_driver_candidate.py b/tests/test_postgres_driver_candidate.py index e5462230..d7b0e2fd 100644 --- a/tests/test_postgres_driver_candidate.py +++ b/tests/test_postgres_driver_candidate.py @@ -122,3 +122,39 @@ def test_candidate_rejects_non_string_python_version_tokens() -> None: def test_candidate_rejects_unknown_capability_names() -> None: with pytest.raises(PostgresDriverCandidateEvidenceError, match="capability"): _evidence(capabilities=FULL_CAPABILITIES | {"model_routing"}) + + +@pytest.mark.parametrize( + ("field_name", "mutated_value"), + [ + ("python_versions", ["3.14"]), + ("capabilities", set(FULL_CAPABILITIES)), + ], +) +def test_candidate_evaluation_revalidates_post_construction_container_mutation( + field_name: str, + mutated_value: object, +) -> None: + evidence = _evidence() + object.__setattr__(evidence, field_name, mutated_value) + + with pytest.raises(PostgresDriverCandidateEvidenceError): + evaluate_postgres_driver_candidate(evidence) + + +def test_candidate_evaluation_normalizes_deleted_authority_field() -> None: + evidence = _evidence() + object.__delattr__(evidence, "capabilities") + + with pytest.raises(PostgresDriverCandidateEvidenceError): + evaluate_postgres_driver_candidate(evidence) + + +def test_candidate_evaluation_rejects_shaped_object_before_member_access() -> None: + class CandidateShapedObject: + @property + def license_spdx(self) -> str: + raise AssertionError("candidate-shaped object member was evaluated") + + with pytest.raises(PostgresDriverCandidateEvidenceError): + evaluate_postgres_driver_candidate(CandidateShapedObject()) # type: ignore[arg-type] From e390ded5f743b8bd41160dfefdd273d100e909c2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 18:03:17 +0900 Subject: [PATCH 015/338] fix(postgres): revalidate driver candidate evidence --- pg_llm_batch/postgres_driver_candidate.py | 55 ++++++++++++++++++----- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index eceed160..db402e9e 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -2,7 +2,7 @@ The repository must replace its current LGPL-family Psycopg runtime dependency without turning an unverified alternative into production authority. This module -keeps candidate package evidence immutable and decides only whether a candidate +revalidates a bounded candidate snapshot and decides only whether a candidate has enough permissive-license, Python-version, artifact-identity, and capability evidence to enter parity validation. Production approval remains a later gate that requires a concrete adapter plus PostgreSQL/RLS/recovery/package evidence. @@ -57,12 +57,14 @@ class PostgresDriverCandidateEvidenceError(ValueError): @dataclass(frozen=True, slots=True) class PostgresDriverCandidateEvidence: - """Describe one immutable PostgreSQL-driver package candidate. + """Describe one validated PostgreSQL-driver package candidate. ``source_commit_sha`` identifies the reviewed source revision and ``artifact_sha256`` identifies the exact distributable under evaluation. ``python_versions`` and ``capabilities`` must contain explicit evidence rather than inferred support from a nearby release or similar database driver. + Evaluation revalidates a fresh snapshot because Python's frozen dataclasses do + not make ``object.__setattr__`` an authority boundary. """ package_name: str @@ -122,7 +124,7 @@ def __post_init__(self) -> None: @dataclass(frozen=True, slots=True) class PostgresDriverCandidateDecision: - """Record whether immutable evidence permits candidate parity validation. + """Record whether validated evidence permits candidate parity validation. ``production_approved`` is deliberately always false in this stage. A package that clears this evaluator still needs a concrete ``PostgresDriverPort`` @@ -134,22 +136,55 @@ class PostgresDriverCandidateDecision: reasons: tuple[str, ...] +def _validated_candidate_snapshot( + evidence: PostgresDriverCandidateEvidence, +) -> PostgresDriverCandidateEvidence: + """Capture and revalidate exact package evidence before policy evaluation. + + Candidate evidence crosses a supply-chain decision boundary. Requiring the + exact package type before member access prevents candidate-shaped objects from + executing caller-controlled accessors, while reconstruction reapplies every + primitive/container invariant after any post-construction mutation. Deleted + slots are normalized to the package's fixed evidence error. + """ + if type(evidence) is not PostgresDriverCandidateEvidence: + raise PostgresDriverCandidateEvidenceError( + "PostgreSQL driver candidate evidence is invalid" + ) + try: + return PostgresDriverCandidateEvidence( + package_name=evidence.package_name, + package_version=evidence.package_version, + license_spdx=evidence.license_spdx, + python_versions=evidence.python_versions, + source_commit_sha=evidence.source_commit_sha, + artifact_sha256=evidence.artifact_sha256, + capabilities=evidence.capabilities, + ) + except AttributeError: + raise PostgresDriverCandidateEvidenceError( + "PostgreSQL driver candidate evidence is invalid" + ) from None + + def evaluate_postgres_driver_candidate( evidence: PostgresDriverCandidateEvidence, ) -> PostgresDriverCandidateDecision: """Evaluate one candidate without promoting it to a production dependency. - The decision fails closed when the SPDX identifier is not in the repository's - explicitly reviewed permissive set, Python 3.14 support is not evidenced, or - any runtime capability required by the migration port is absent. Reasons are - deterministic so CI and acquisition diligence can compare exact evidence. + The decision first revalidates one exact package-owned snapshot, then fails + closed when the SPDX identifier is not in the repository's explicitly + reviewed permissive set, Python 3.14 support is not evidenced, or any runtime + capability required by the migration port is absent. Reasons are deterministic + so CI and acquisition diligence can compare exact evidence. """ + snapshot = _validated_candidate_snapshot(evidence) reasons: list[str] = [] - if evidence.license_spdx not in _APPROVED_PERMISSIVE_LICENSES: + if snapshot.license_spdx not in _APPROVED_PERMISSIVE_LICENSES: reasons.append("license_not_approved") - if "3.14" not in evidence.python_versions: + if "3.14" not in snapshot.python_versions: reasons.append("python_3_14_not_evidenced") - missing_capabilities = REQUIRED_POSTGRES_DRIVER_CAPABILITIES - evidence.capabilities + missing_capabilities = REQUIRED_POSTGRES_DRIVER_CAPABILITIES - snapshot.capabilities reasons.extend( f"missing_capability:{capability}" for capability in sorted(missing_capabilities) From ec26ce52b1dc6a634f8b5290117dcd8c448dea69 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 18:06:32 +0900 Subject: [PATCH 016/338] test(postgres): bound driver row fetches --- tests/test_psycopg_driver_adapter.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_psycopg_driver_adapter.py b/tests/test_psycopg_driver_adapter.py index b96a449a..09d137da 100644 --- a/tests/test_psycopg_driver_adapter.py +++ b/tests/test_psycopg_driver_adapter.py @@ -116,6 +116,16 @@ def test_cursor_adapter_preserves_parameter_and_result_authority() -> None: assert cursor.row_count() == 2 +@pytest.mark.parametrize("invalid_size", [True, 0, -1, 1.5]) +def test_cursor_adapter_rejects_non_positive_or_non_integer_fetch_budget( + invalid_size: object, +) -> None: + raw = _RawCursor() + + with pytest.raises(PsycopgDriverAdapterError, match="fetch size"): + PsycopgCursorAdapter(raw).fetchmany(invalid_size) # type: ignore[arg-type] + + def test_cursor_adapter_rejects_non_integer_row_count() -> None: raw = _RawCursor() raw.rowcount = True From 0bbcc4ae1829ca05f26d4879921e5d467fff76ee Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 18:07:24 +0900 Subject: [PATCH 017/338] fix(postgres): enforce finite fetch budgets --- pg_llm_batch/psycopg_driver_adapter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pg_llm_batch/psycopg_driver_adapter.py b/pg_llm_batch/psycopg_driver_adapter.py index 944df604..31b6d5b7 100644 --- a/pg_llm_batch/psycopg_driver_adapter.py +++ b/pg_llm_batch/psycopg_driver_adapter.py @@ -70,7 +70,9 @@ def fetchone(self) -> object | None: return self._cursor.fetchone() def fetchmany(self, size: int) -> list[object]: - """Return the finite row page requested by the owning bounded context.""" + """Return a strictly positive finite row page through the retained cursor.""" + if type(size) is not int or size <= 0: + raise PsycopgDriverAdapterError("PostgreSQL driver fetch size is invalid") return list(self._cursor.fetchmany(size)) def fetchall(self) -> list[object]: From b457207c496d3974ad3895706ea671199f1aed90 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 18:12:26 +0900 Subject: [PATCH 018/338] test(postgres): require conninfo error classification --- tests/test_postgres_driver_port.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_postgres_driver_port.py b/tests/test_postgres_driver_port.py index ddd21b24..0916c0d3 100644 --- a/tests/test_postgres_driver_port.py +++ b/tests/test_postgres_driver_port.py @@ -41,6 +41,7 @@ def test_connection_port_covers_transaction_and_cursor_lifecycle() -> None: def test_driver_port_covers_psycopg_replacement_capabilities_only() -> None: assert PostgresDriverPort.__abstractmethods__ == { "connect", + "is_invalid_conninfo", "is_undefined_function", "jsonb", "make_conninfo", @@ -135,6 +136,10 @@ class _UndefinedFunctionError(Exception): pass +class _InvalidConninfoError(Exception): + pass + + class _Driver(PostgresDriverPort): def connect( self, @@ -156,6 +161,9 @@ def make_conninfo(self, params: Mapping[str, str]) -> str: def jsonb(self, value: object) -> object: return ("jsonb", value) + def is_invalid_conninfo(self, error: BaseException) -> bool: + return isinstance(error, _InvalidConninfoError) + def is_undefined_function(self, error: BaseException) -> bool: return isinstance(error, _UndefinedFunctionError) @@ -194,6 +202,8 @@ def test_complete_port_can_run_without_psycopg_types() -> None: "jsonb", {"request_id": "opaque"}, ) + assert driver.is_invalid_conninfo(_InvalidConninfoError()) is True + assert driver.is_invalid_conninfo(RuntimeError()) is False assert driver.is_undefined_function(_UndefinedFunctionError()) is True assert driver.is_undefined_function(RuntimeError()) is False From a5db16cb0ec08f540d77965e42fd05cc853aa725 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 18:13:32 +0900 Subject: [PATCH 019/338] test(postgres): classify conninfo grammar errors --- tests/test_psycopg_driver_adapter.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_psycopg_driver_adapter.py b/tests/test_psycopg_driver_adapter.py index 09d137da..334474a5 100644 --- a/tests/test_psycopg_driver_adapter.py +++ b/tests/test_psycopg_driver_adapter.py @@ -3,6 +3,7 @@ from typing import Any import pytest +from psycopg import ProgrammingError from psycopg.errors import UndefinedFunction from psycopg.types.json import Jsonb @@ -209,6 +210,13 @@ def test_driver_uses_psycopg_conninfo_and_jsonb_contracts() -> None: assert adapted.obj == {"count": 1} +def test_driver_classifies_only_psycopg_conninfo_grammar_failures() -> None: + driver = PsycopgDriverAdapter() + + assert driver.is_invalid_conninfo(ProgrammingError("invalid conninfo")) is True + assert driver.is_invalid_conninfo(RuntimeError("invalid conninfo")) is False + + def test_driver_classifies_only_psycopg_undefined_function() -> None: driver = PsycopgDriverAdapter() From cbfd3354b3b1cc514c4139422afaf00bab340b37 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 18:14:44 +0900 Subject: [PATCH 020/338] feat(postgres): classify invalid conninfo through port --- pg_llm_batch/postgres_driver_port.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pg_llm_batch/postgres_driver_port.py b/pg_llm_batch/postgres_driver_port.py index 85cd20e1..72cd8025 100644 --- a/pg_llm_batch/postgres_driver_port.py +++ b/pg_llm_batch/postgres_driver_port.py @@ -242,6 +242,16 @@ def jsonb(self, value: object) -> object: models and public package contracts. """ + @abstractmethod + def is_invalid_conninfo(self, error: BaseException) -> bool: + """Classify only a PostgreSQL connection-selector grammar failure. + + CLI and bootstrap boundaries need to normalize malformed DSN syntax + without importing one concrete driver's exception type. Implementations + must not broaden this category to connection, authentication, or runtime + database failures. + """ + @abstractmethod def is_undefined_function(self, error: BaseException) -> bool: """Classify the PostgreSQL undefined-function error without leaking it. From bb77391c2d2a6e3a866bcf346f46f5639bd63879 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 18:15:44 +0900 Subject: [PATCH 021/338] feat(postgres): isolate conninfo error type --- pg_llm_batch/psycopg_driver_adapter.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pg_llm_batch/psycopg_driver_adapter.py b/pg_llm_batch/psycopg_driver_adapter.py index 31b6d5b7..4ba258f9 100644 --- a/pg_llm_batch/psycopg_driver_adapter.py +++ b/pg_llm_batch/psycopg_driver_adapter.py @@ -14,6 +14,7 @@ from typing import Any import psycopg +from psycopg import ProgrammingError from psycopg.conninfo import conninfo_to_dict, make_conninfo from psycopg.errors import UndefinedFunction from psycopg.types.json import Jsonb @@ -199,6 +200,10 @@ def jsonb(self, value: object) -> Jsonb: """Wrap a validated Python value in Psycopg's JSONB parameter adapter.""" return Jsonb(value) + def is_invalid_conninfo(self, error: BaseException) -> bool: + """Recognize only Psycopg's connection-selector grammar error category.""" + return isinstance(error, ProgrammingError) + def is_undefined_function(self, error: BaseException) -> bool: """Recognize only Psycopg's PostgreSQL undefined-function error category.""" return isinstance(error, UndefinedFunction) From 06746dc9f4699dfb088f6aa0b2529e174afe9943 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 18:16:43 +0900 Subject: [PATCH 022/338] fix(postgres): require conninfo error parity --- pg_llm_batch/postgres_driver_candidate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index db402e9e..6a9a8e22 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -22,6 +22,7 @@ "conninfo_parse_render", "cursor_context", "finite_connect_timeout", + "invalid_conninfo_classification", "jsonb", "parameterized_sql", "row_count", From e748dadfff48cece13a4f9efd24f9c5d9652428b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 18:22:28 +0900 Subject: [PATCH 023/338] test(postgres): require full type and parameter parity --- tests/test_postgres_driver_candidate.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_postgres_driver_candidate.py b/tests/test_postgres_driver_candidate.py index d7b0e2fd..7e80caba 100644 --- a/tests/test_postgres_driver_candidate.py +++ b/tests/test_postgres_driver_candidate.py @@ -37,6 +37,14 @@ def test_complete_permissive_candidate_is_eligible_only_for_parity_validation() assert decision.reasons == () +def test_candidate_contract_covers_issue_322_type_and_parameter_parity() -> None: + assert { + "result_row_semantics", + "sql_parameter_style_adaptation", + "uuid_timestamp_adaptation", + } <= REQUIRED_POSTGRES_DRIVER_CAPABILITIES + + @pytest.mark.parametrize( ("license_spdx", "expected_reason"), [ From adfd1e8267653c90417dd267c6f1f3ed57f9c013 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:00:18 +0900 Subject: [PATCH 024/338] fix(postgres): require full driver parity evidence --- pg_llm_batch/postgres_driver_candidate.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index 6a9a8e22..eee0110a 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -25,9 +25,12 @@ "invalid_conninfo_classification", "jsonb", "parameterized_sql", + "result_row_semantics", "row_count", + "sql_parameter_style_adaptation", "transaction_commit_rollback", "undefined_function_classification", + "uuid_timestamp_adaptation", } ) """Capabilities a replacement driver must evidence before parity validation.""" From 6524fdfb2c9a6592b2ceaee4621a1e407b0e6a47 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:04:46 +0900 Subject: [PATCH 025/338] test(postgres): reject broad ProgrammingError conninfo classification --- tests/test_psycopg_driver_adapter.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_psycopg_driver_adapter.py b/tests/test_psycopg_driver_adapter.py index 334474a5..ca7460a3 100644 --- a/tests/test_psycopg_driver_adapter.py +++ b/tests/test_psycopg_driver_adapter.py @@ -210,10 +210,14 @@ def test_driver_uses_psycopg_conninfo_and_jsonb_contracts() -> None: assert adapted.obj == {"count": 1} -def test_driver_classifies_only_psycopg_conninfo_grammar_failures() -> None: +def test_driver_classifies_only_adapter_owned_conninfo_grammar_failures() -> None: driver = PsycopgDriverAdapter() - assert driver.is_invalid_conninfo(ProgrammingError("invalid conninfo")) is True + with pytest.raises(PsycopgDriverAdapterError) as invalid_conninfo: + driver.parse_conninfo("host='unterminated") + + assert driver.is_invalid_conninfo(invalid_conninfo.value) is True + assert driver.is_invalid_conninfo(ProgrammingError("syntax error")) is False assert driver.is_invalid_conninfo(RuntimeError("invalid conninfo")) is False From 6c29ff11e8a752eaace7141da6ffcc2ee9affea8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:05:37 +0900 Subject: [PATCH 026/338] fix(postgres): narrow conninfo error classification --- pg_llm_batch/psycopg_driver_adapter.py | 32 +++++++++++++++++++++----- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/pg_llm_batch/psycopg_driver_adapter.py b/pg_llm_batch/psycopg_driver_adapter.py index 4ba258f9..72b53a3c 100644 --- a/pg_llm_batch/psycopg_driver_adapter.py +++ b/pg_llm_batch/psycopg_driver_adapter.py @@ -36,6 +36,16 @@ class PsycopgDriverAdapterError(RuntimeError): """ +class PsycopgInvalidConninfoError(PsycopgDriverAdapterError): + """Identify conninfo grammar failures created at the adapter parsing boundary. + + Psycopg's public ``ProgrammingError`` class also represents server-side SQL + errors such as undefined tables and malformed statements. Wrapping only + failures raised by conninfo parsing/rendering prevents those unrelated + database errors from being misclassified as an invalid DSN. + """ + + class PsycopgCursorAdapter(PostgresCursorPort): """Wrap one Psycopg-compatible cursor without changing its transaction owner. @@ -189,20 +199,30 @@ def connect( return PsycopgConnectionAdapter(psycopg.connect(dsn, **kwargs)) def parse_conninfo(self, dsn: str) -> Mapping[str, str]: - """Parse PostgreSQL conninfo using Psycopg/libpq-compatible quoting rules.""" - return conninfo_to_dict(dsn) + """Parse conninfo and narrow Psycopg's broad ProgrammingError category.""" + try: + return conninfo_to_dict(dsn) + except ProgrammingError: + raise PsycopgInvalidConninfoError( + "PostgreSQL connection selector is invalid" + ) from None def make_conninfo(self, params: Mapping[str, str]) -> str: - """Render PostgreSQL conninfo using Psycopg's reviewed quoting implementation.""" - return make_conninfo(**dict(params)) + """Render conninfo and narrow Psycopg's broad ProgrammingError category.""" + try: + return make_conninfo(**dict(params)) + except ProgrammingError: + raise PsycopgInvalidConninfoError( + "PostgreSQL connection selector is invalid" + ) from None def jsonb(self, value: object) -> Jsonb: """Wrap a validated Python value in Psycopg's JSONB parameter adapter.""" return Jsonb(value) def is_invalid_conninfo(self, error: BaseException) -> bool: - """Recognize only Psycopg's connection-selector grammar error category.""" - return isinstance(error, ProgrammingError) + """Recognize only errors wrapped at the conninfo grammar boundary.""" + return isinstance(error, PsycopgInvalidConninfoError) def is_undefined_function(self, error: BaseException) -> bool: """Recognize only Psycopg's PostgreSQL undefined-function error category.""" From 4e9486bfa2e24bc6426d2b55bc8ba5506ee4608d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:09:20 +0900 Subject: [PATCH 027/338] test(postgres): require canonical tuple result rows --- tests/test_psycopg_driver_adapter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_psycopg_driver_adapter.py b/tests/test_psycopg_driver_adapter.py index ca7460a3..43b1b092 100644 --- a/tests/test_psycopg_driver_adapter.py +++ b/tests/test_psycopg_driver_adapter.py @@ -20,7 +20,7 @@ class _RawCursor: def __init__(self) -> None: self.executions: list[tuple[str, object | None]] = [] self.many_executions: list[tuple[str, object]] = [] - self.rows: list[object] = [("one",), ("two",)] + self.rows: list[object] = [["one"], ["two"]] self.rowcount: object = 2 self.entered = False self.exited = False From 8dc90b2309e33b70b2e7614e2524e837401c6329 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:10:10 +0900 Subject: [PATCH 028/338] fix(postgres): normalize driver result rows to tuples --- pg_llm_batch/psycopg_driver_adapter.py | 57 ++++++++++++++++++-------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/pg_llm_batch/psycopg_driver_adapter.py b/pg_llm_batch/psycopg_driver_adapter.py index 72b53a3c..31fc404b 100644 --- a/pg_llm_batch/psycopg_driver_adapter.py +++ b/pg_llm_batch/psycopg_driver_adapter.py @@ -30,9 +30,10 @@ class PsycopgDriverAdapterError(RuntimeError): """Report a fixed adapter-contract failure without reflecting database data. The adapter uses this error only when a driver-facing primitive violates the - migration port itself, such as a non-boolean autocommit state or a row-count - value with the wrong Python type. PostgreSQL execution errors continue to - propagate through Psycopg so existing bounded contexts can classify them. + migration port itself, such as a non-boolean autocommit state, unsupported row + container, or a row-count value with the wrong Python type. PostgreSQL + execution errors continue to propagate through Psycopg so existing bounded + contexts can classify them. """ @@ -47,17 +48,29 @@ class PsycopgInvalidConninfoError(PsycopgDriverAdapterError): class PsycopgCursorAdapter(PostgresCursorPort): - """Wrap one Psycopg-compatible cursor without changing its transaction owner. + """Wrap one PostgreSQL cursor while preserving canonical tuple row semantics. - The wrapper deliberately performs no SQL rewriting. Package-authored query - text and bound parameters are handed to the retained cursor unchanged, while - row materialization remains subject to each caller's existing trust-boundary - validation. + Package-authored query text and bound parameters are handed to the retained + cursor unchanged. Result rows are normalized from exact tuple/list containers + to tuples because current pg-llm-batch bounded contexts use positional tuple + identity and equality. This keeps a future DB-API driver that returns list rows + from silently changing application behavior. """ def __init__(self, cursor: Any) -> None: self._cursor = cursor + @staticmethod + def _normalize_result_row(row: object | None) -> tuple[object, ...] | None: + """Normalize one DB-API row to the package's positional tuple contract.""" + if row is None: + return None + if type(row) is tuple: + return row + if type(row) is list: + return tuple(row) + raise PsycopgDriverAdapterError("PostgreSQL driver result row is invalid") + def execute( self, query: str, @@ -76,19 +89,27 @@ def executemany( self._cursor.executemany(query, params_seq) return self - def fetchone(self) -> object | None: - """Return the next raw row for validation by the owning bounded context.""" - return self._cursor.fetchone() + def fetchone(self) -> tuple[object, ...] | None: + """Return the next row in the package's canonical tuple representation.""" + return self._normalize_result_row(self._cursor.fetchone()) - def fetchmany(self, size: int) -> list[object]: - """Return a strictly positive finite row page through the retained cursor.""" + def fetchmany(self, size: int) -> list[tuple[object, ...]]: + """Return a finite page with every driver row normalized to a tuple.""" if type(size) is not int or size <= 0: raise PsycopgDriverAdapterError("PostgreSQL driver fetch size is invalid") - return list(self._cursor.fetchmany(size)) - - def fetchall(self) -> list[object]: - """Return all rows only for callers whose query already bounds the result.""" - return list(self._cursor.fetchall()) + return [ + normalized + for row in self._cursor.fetchmany(size) + if (normalized := self._normalize_result_row(row)) is not None + ] + + def fetchall(self) -> list[tuple[object, ...]]: + """Return bounded query results with every row normalized to a tuple.""" + return [ + normalized + for row in self._cursor.fetchall() + if (normalized := self._normalize_result_row(row)) is not None + ] def row_count(self) -> int: """Return Psycopg's exact integer affected-row result, including -1 unknown.""" From 2886f9028d3d260bb972d99d99a0b016dbaaa1a8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:11:09 +0900 Subject: [PATCH 029/338] test(postgres): reject null rows inside result pages --- tests/test_psycopg_driver_adapter.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_psycopg_driver_adapter.py b/tests/test_psycopg_driver_adapter.py index 43b1b092..76b2b5a1 100644 --- a/tests/test_psycopg_driver_adapter.py +++ b/tests/test_psycopg_driver_adapter.py @@ -117,6 +117,17 @@ def test_cursor_adapter_preserves_parameter_and_result_authority() -> None: assert cursor.row_count() == 2 +def test_cursor_adapter_rejects_none_inside_materialized_result_page() -> None: + raw = _RawCursor() + raw.rows = [["one"], None] + cursor = PsycopgCursorAdapter(raw) + + with pytest.raises(PsycopgDriverAdapterError, match="result row"): + cursor.fetchmany(2) + with pytest.raises(PsycopgDriverAdapterError, match="result row"): + cursor.fetchall() + + @pytest.mark.parametrize("invalid_size", [True, 0, -1, 1.5]) def test_cursor_adapter_rejects_non_positive_or_non_integer_fetch_budget( invalid_size: object, From 20e00e5fefd9fb544d192ea360227ae3a0bbbfb3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:11:59 +0900 Subject: [PATCH 030/338] fix(postgres): fail closed on malformed result pages --- pg_llm_batch/psycopg_driver_adapter.py | 29 ++++++++++---------------- 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/pg_llm_batch/psycopg_driver_adapter.py b/pg_llm_batch/psycopg_driver_adapter.py index 31fc404b..5f3df0d0 100644 --- a/pg_llm_batch/psycopg_driver_adapter.py +++ b/pg_llm_batch/psycopg_driver_adapter.py @@ -61,10 +61,8 @@ def __init__(self, cursor: Any) -> None: self._cursor = cursor @staticmethod - def _normalize_result_row(row: object | None) -> tuple[object, ...] | None: - """Normalize one DB-API row to the package's positional tuple contract.""" - if row is None: - return None + def _normalize_result_row(row: object) -> tuple[object, ...]: + """Normalize one materialized DB-API row to the positional tuple contract.""" if type(row) is tuple: return row if type(row) is list: @@ -90,26 +88,21 @@ def executemany( return self def fetchone(self) -> tuple[object, ...] | None: - """Return the next row in the package's canonical tuple representation.""" - return self._normalize_result_row(self._cursor.fetchone()) + """Return one canonical tuple row, or ``None`` only for end-of-results.""" + row = self._cursor.fetchone() + if row is None: + return None + return self._normalize_result_row(row) def fetchmany(self, size: int) -> list[tuple[object, ...]]: - """Return a finite page with every driver row normalized to a tuple.""" + """Return a finite page while rejecting malformed materialized rows.""" if type(size) is not int or size <= 0: raise PsycopgDriverAdapterError("PostgreSQL driver fetch size is invalid") - return [ - normalized - for row in self._cursor.fetchmany(size) - if (normalized := self._normalize_result_row(row)) is not None - ] + return [self._normalize_result_row(row) for row in self._cursor.fetchmany(size)] def fetchall(self) -> list[tuple[object, ...]]: - """Return bounded query results with every row normalized to a tuple.""" - return [ - normalized - for row in self._cursor.fetchall() - if (normalized := self._normalize_result_row(row)) is not None - ] + """Return bounded query results while rejecting malformed rows.""" + return [self._normalize_result_row(row) for row in self._cursor.fetchall()] def row_count(self) -> int: """Return Psycopg's exact integer affected-row result, including -1 unknown.""" From b51c97efb57b422b7b98af5414e57dca76ea2fca Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:13:20 +0900 Subject: [PATCH 031/338] test(postgres): bind port to tuple row semantics --- tests/test_postgres_driver_port.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/tests/test_postgres_driver_port.py b/tests/test_postgres_driver_port.py index 0916c0d3..2b2a7fe6 100644 --- a/tests/test_postgres_driver_port.py +++ b/tests/test_postgres_driver_port.py @@ -1,6 +1,7 @@ from __future__ import annotations from collections.abc import Mapping +from typing import get_type_hints import pytest @@ -24,6 +25,18 @@ def test_cursor_port_covers_existing_database_interaction_surface() -> None: } +def test_cursor_port_declares_canonical_tuple_result_rows() -> None: + assert get_type_hints(PostgresCursorPort.fetchone)["return"] == ( + tuple[object, ...] | None + ) + assert get_type_hints(PostgresCursorPort.fetchmany)["return"] == list[ + tuple[object, ...] + ] + assert get_type_hints(PostgresCursorPort.fetchall)["return"] == list[ + tuple[object, ...] + ] + + def test_connection_port_covers_transaction_and_cursor_lifecycle() -> None: assert PostgresConnectionPort.__abstractmethods__ == { "__enter__", @@ -67,13 +80,13 @@ def executemany(self, query: str, params_seq: object) -> _Cursor: self.affected_rows = 2 return self - def fetchone(self) -> object | None: + def fetchone(self) -> tuple[object, ...] | None: return ("row",) - def fetchmany(self, size: int) -> list[object]: + def fetchmany(self, size: int) -> list[tuple[object, ...]]: return [("row",)] * size - def fetchall(self) -> list[object]: + def fetchall(self) -> list[tuple[object, ...]]: return [("row",)] def row_count(self) -> int: From 9e1bfa7060348194cae3969b3524254f9a3f2906 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:14:03 +0900 Subject: [PATCH 032/338] fix(postgres): declare canonical tuple result contract --- pg_llm_batch/postgres_driver_port.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/pg_llm_batch/postgres_driver_port.py b/pg_llm_batch/postgres_driver_port.py index 72cd8025..e898f884 100644 --- a/pg_llm_batch/postgres_driver_port.py +++ b/pg_llm_batch/postgres_driver_port.py @@ -18,9 +18,10 @@ class PostgresCursorPort(ABC): """Describe the synchronous cursor surface used by pg-llm-batch. Implementations must preserve parameter binding rather than interpolating - SQL text themselves. The fetch methods intentionally expose driver-neutral - Python objects because individual bounded contexts validate row shapes at - their own trust boundaries. + SQL text themselves. Materialized result rows use positional tuples as the + canonical package representation because existing bounded contexts use tuple + indexing and equality. A concrete driver returning another row container must + normalize it inside its adapter before exposing it through this port. """ @abstractmethod @@ -45,24 +46,26 @@ def executemany(self, query: str, params_seq: object) -> PostgresCursorPort: """ @abstractmethod - def fetchone(self) -> object | None: - """Return the next driver row, or ``None`` when no row remains. + def fetchone(self) -> tuple[object, ...] | None: + """Return one canonical tuple row, or ``None`` when no row remains. - Domain code remains responsible for validating the returned row's exact - shape and primitive types before treating database evidence as trusted. + The adapter owns only the row-container normalization. The consuming + bounded context remains responsible for validating field count, primitive + types, and semantic meaning before database evidence becomes trusted. """ @abstractmethod - def fetchmany(self, size: int) -> list[object]: - """Return at most ``size`` rows through a finite materialization call. + def fetchmany(self, size: int) -> list[tuple[object, ...]]: + """Return at most ``size`` canonical tuple rows. Bounded contexts use this operation when an explicit row budget is part - of the product contract; adapters must preserve that finite request. + of the product contract; adapters must preserve that finite request and + fail closed rather than silently dropping malformed materialized rows. """ @abstractmethod - def fetchall(self) -> list[object]: - """Return all rows for callers whose query already has a bounded result. + def fetchall(self) -> list[tuple[object, ...]]: + """Return canonical tuple rows for an already bounded result set. This method exists for compatibility with current package code. New untrusted-result paths should prefer a bounded query and ``fetchmany``. From 5bb1a1c7f1806cb9e1bcb6f73e69e9b71ce92dbe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:26:50 +0900 Subject: [PATCH 033/338] test(postgres): require checkpoint store driver-port injection --- tests/test_checkpoint_store_driver_port.py | 134 +++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 tests/test_checkpoint_store_driver_port.py diff --git a/tests/test_checkpoint_store_driver_port.py b/tests/test_checkpoint_store_driver_port.py new file mode 100644 index 00000000..91e80908 --- /dev/null +++ b/tests/test_checkpoint_store_driver_port.py @@ -0,0 +1,134 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Regression tests for checkpoint persistence through the PostgreSQL driver port.""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import pytest + +import pg_llm_batch.checkpoint_store as checkpoint_store +from pg_llm_batch.checkpoint_store import ( + PostgresBatchResultCheckpointStore, + apply_result_checkpoint_schema, +) + + +class _PortCursor: + """Provide the minimal cursor behavior needed by the checkpoint boundary.""" + + def __init__(self, calls: list[tuple[str, object | None]]) -> None: + self.calls = calls + self.result: object | None = None + + def execute(self, query: str, params: object | None = None) -> "_PortCursor": + """Record parameterized SQL without interpolating caller values.""" + self.calls.append((query, params)) + self.result = (params[0],) if query.startswith("SELECT set_config") and params else None + return self + + def fetchone(self) -> object | None: + """Return the bounded result from the previous fake operation.""" + return self.result + + def __enter__(self) -> "_PortCursor": + """Retain cursor identity across the context-manager boundary.""" + return self + + def __exit__(self, *_exc: object) -> None: + """Release no external resources in the deterministic fake.""" + return None + + +class _PortConnection: + """Expose one retained fake connection for driver-port regression tests.""" + + def __init__(self, calls: list[tuple[str, object | None]]) -> None: + self.calls = calls + self.commit_count = 0 + + def cursor(self) -> _PortCursor: + """Create a cursor bound to this exact fake connection.""" + return _PortCursor(self.calls) + + def commit(self) -> None: + """Record explicit package-owned transaction commit authority.""" + self.commit_count += 1 + + def __enter__(self) -> "_PortConnection": + """Retain connection identity across the context-manager boundary.""" + return self + + def __exit__(self, *_exc: object) -> None: + """Release no external resources in the deterministic fake.""" + return None + + +class _DriverPortFake: + """Connect checkpoint operations without exposing a concrete database client.""" + + def __init__(self) -> None: + self.dsns: list[str] = [] + self.calls: list[tuple[str, object | None]] = [] + self.connections: list[_PortConnection] = [] + + def connect(self, dsn: str, **_kwargs: Any) -> _PortConnection: + """Return one connection and preserve the exact validated DSN.""" + self.dsns.append(dsn) + connection = _PortConnection(self.calls) + self.connections.append(connection) + return connection + + +def _deny_legacy_psycopg_path(monkeypatch: pytest.MonkeyPatch) -> None: + """Make accidental fallback to the retained Psycopg path fail immediately.""" + + def fail_require_psycopg() -> None: + raise AssertionError("legacy Psycopg availability check was reached") + + class _ForbiddenPsycopg: + def connect(self, *_args: object, **_kwargs: object) -> None: + raise AssertionError("legacy Psycopg connection path was reached") + + monkeypatch.setattr(checkpoint_store, "_require_psycopg", fail_require_psycopg) + monkeypatch.setattr(checkpoint_store, "psycopg", _ForbiddenPsycopg()) + + +def test_checkpoint_store_load_uses_injected_driver_port_without_psycopg( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A migrated store must reach tenant SQL through the injected database port.""" + _deny_legacy_psycopg_path(monkeypatch) + driver = _DriverPortFake() + store = PostgresBatchResultCheckpointStore( + "postgresql://unit", + tenant_scope="tenant-a", + postgres_driver=driver, # type: ignore[arg-type] + ) + + assert store.load("worker-a", "batch-1", "default") is None + assert driver.dsns == ["postgresql://unit"] + assert driver.calls[0][1] == ("tenant-a",) + assert driver.calls[1][1] == ("tenant-a", "worker-a", "default", "batch-1") + + +def test_checkpoint_schema_application_uses_injected_driver_port_without_psycopg( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + """Schema migration must be able to run through the same replacement seam.""" + _deny_legacy_psycopg_path(monkeypatch) + migration = tmp_path / "checkpoint.sql" + migration.write_text("CREATE TABLE checkpoint_probe (probe_id BIGINT);", encoding="utf-8") + driver = _DriverPortFake() + + apply_result_checkpoint_schema( + "postgresql://unit", + str(migration), + postgres_driver=driver, # type: ignore[arg-type] + ) + + assert driver.dsns == ["postgresql://unit"] + assert driver.calls == [("CREATE TABLE checkpoint_probe (probe_id BIGINT);", None)] + assert driver.connections[0].commit_count == 1 From 43713a14a03883d82e15d4a146d15073d0c77926 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:29:31 +0900 Subject: [PATCH 034/338] feat(postgres): route checkpoint persistence through driver port --- pg_llm_batch/checkpoint_store.py | 33 +++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/pg_llm_batch/checkpoint_store.py b/pg_llm_batch/checkpoint_store.py index 4eda5019..18ddddbc 100644 --- a/pg_llm_batch/checkpoint_store.py +++ b/pg_llm_batch/checkpoint_store.py @@ -18,6 +18,7 @@ validate_tenant_scope, ) from .exceptions import ConfigError, PgLlmBatchError, ValidationError +from .postgres_driver_port import PostgresDriverPort from .result_streaming import BatchResultCheckpoint MIGRATION_PATH = ( @@ -85,6 +86,23 @@ def _validated_postgres_dsn(value: Any) -> str: return value +def _connect_postgres( + postgres_dsn: str, + postgres_driver: PostgresDriverPort | None, +) -> Any: + """Connect through an injected driver while preserving the legacy default. + + The optional port lets one bounded persistence consumer migrate away from + Psycopg without changing its SQL, transaction, tenant, or checkpoint + semantics. Until the repository selects and validates a commercial + replacement, omitting the port retains the current Psycopg path explicitly. + """ + if postgres_driver is not None: + return postgres_driver.connect(postgres_dsn) + _require_psycopg() + return psycopg.connect(postgres_dsn) + + def _validated_checkpoint(value: Any, field: str) -> BatchResultCheckpoint: """Require one immutable checkpoint whose counters fit PostgreSQL storage.""" if not isinstance(value, BatchResultCheckpoint): @@ -171,13 +189,14 @@ def _checkpoint_values(checkpoint: BatchResultCheckpoint) -> tuple[Any, ...]: def apply_result_checkpoint_schema( postgres_dsn: str, migration_path: Optional[str] = None, + *, + postgres_driver: PostgresDriverPort | None = None, ) -> None: """Apply the idempotent durable result-checkpoint migration.""" dsn = _validated_postgres_dsn(postgres_dsn) - _require_psycopg() path = Path(migration_path) if migration_path else MIGRATION_PATH sql = path.read_text(encoding="utf-8") - with psycopg.connect(dsn) as conn: + with _connect_postgres(dsn, postgres_driver) as conn: with conn.cursor() as cur: cur.execute(sql) conn.commit() @@ -191,9 +210,11 @@ def __init__( postgres_dsn: str, *, tenant_scope: str = DEFAULT_TENANT_SCOPE, + postgres_driver: PostgresDriverPort | None = None, ) -> None: - """Bind one explicit database and trusted local tenant scope to the store.""" + """Bind one explicit database, tenant scope, and optional driver port.""" self.postgres_dsn = _validated_postgres_dsn(postgres_dsn) + self._postgres_driver = postgres_driver try: self.tenant_scope = validate_tenant_scope(tenant_scope) except ValidationError as exc: @@ -210,8 +231,7 @@ def load( endpoint_alias: str, ) -> Optional[BatchResultCheckpoint]: """Load the current checkpoint in one package-owned transaction.""" - _require_psycopg() - with psycopg.connect(self.postgres_dsn) as conn: + with _connect_postgres(self.postgres_dsn, self._postgres_driver) as conn: with conn.cursor() as cur: return self.load_in_transaction( cur, @@ -257,8 +277,7 @@ def save( expected_previous: Optional[BatchResultCheckpoint] = None, ) -> BatchResultCheckpoint: """Create or advance a checkpoint in one package-owned transaction.""" - _require_psycopg() - with psycopg.connect(self.postgres_dsn) as conn: + with _connect_postgres(self.postgres_dsn, self._postgres_driver) as conn: with conn.cursor() as cur: saved = self.save_in_transaction( cur, From 07962cbd241caa9208ee2bd9f5a489f2aa8b492d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:30:59 +0900 Subject: [PATCH 035/338] test(postgres): require readiness driver-port injection --- tests/test_health_driver_port.py | 119 +++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 tests/test_health_driver_port.py diff --git a/tests/test_health_driver_port.py b/tests/test_health_driver_port.py new file mode 100644 index 00000000..d1fcf19b --- /dev/null +++ b/tests/test_health_driver_port.py @@ -0,0 +1,119 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Regression tests for readiness checks through the PostgreSQL driver port.""" + +from __future__ import annotations + +from typing import Any + +import pytest + +from pg_llm_batch import health + + +class _HealthCursor: + """Expose deterministic readiness rows through the driver-neutral cursor shape.""" + + def __init__(self) -> None: + self.executions: list[tuple[str, object | None]] = [] + + def execute(self, query: str, params: object | None = None) -> "_HealthCursor": + """Record the package-authored health query without modifying it.""" + self.executions.append((query, params)) + return self + + def fetchall(self) -> list[tuple[object, ...]]: + """Return all required healthy component rows as canonical tuples.""" + return [ + ("database", True, "connected"), + ("pg_tiktoken", True, "installed"), + ("com_config", True, "ready"), + ] + + def __enter__(self) -> "_HealthCursor": + """Retain the cursor identity during the readiness transaction.""" + return self + + def __exit__(self, *_exc: object) -> None: + """Release no external resources in the deterministic fake.""" + return None + + +class _HealthConnection: + """Retain one cursor for the driver-neutral readiness connection.""" + + def __init__(self) -> None: + self.cursor_value = _HealthCursor() + + def cursor(self) -> _HealthCursor: + """Return the cursor bound to this exact connection.""" + return self.cursor_value + + def __enter__(self) -> "_HealthConnection": + """Retain connection identity across context-manager entry.""" + return self + + def __exit__(self, *_exc: object) -> None: + """Release no external resources in the deterministic fake.""" + return None + + +class _HealthDriver: + """Capture readiness connection parameters without a concrete client import.""" + + def __init__(self) -> None: + self.calls: list[tuple[str, int | None]] = [] + self.connection = _HealthConnection() + + def connect( + self, + dsn: str, + *, + connect_timeout_seconds: int | None = None, + ) -> _HealthConnection: + """Preserve the exact DSN and bounded five-second readiness timeout.""" + self.calls.append((dsn, connect_timeout_seconds)) + return self.connection + + +def test_check_health_uses_injected_driver_without_psycopg( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Readiness must work through the replacement seam when Psycopg is unavailable.""" + monkeypatch.setattr(health, "psycopg", None) + driver = _HealthDriver() + + report = health.check_health( + "postgresql://example", + postgres_driver=driver, # type: ignore[arg-type] + ) + + assert report["ready"] is True + assert driver.calls == [("postgresql://example", 5)] + assert driver.connection.cursor_value.executions == [ + ("SELECT component, is_ready, detail FROM pg_llm_batch_health_check()", None) + ] + + +def test_check_health_bounds_injected_driver_failures_without_psycopg( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Replacement-driver failures remain a database readiness result, not a crash.""" + monkeypatch.setattr(health, "psycopg", None) + + class _BrokenDriver: + def connect(self, _dsn: str, **_kwargs: Any) -> None: + raise OSError("replacement driver connection refused") + + report = health.check_health( + "postgresql://example", + postgres_driver=_BrokenDriver(), # type: ignore[arg-type] + ) + + assert report["ready"] is False + assert report["components"] == [ + { + "component": "database", + "is_ready": False, + "detail": "replacement driver connection refused", + } + ] From 1f1c6ae78c72ebc4fb38d3c38f66845b6e9ecad2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:31:27 +0900 Subject: [PATCH 036/338] feat(postgres): route readiness through driver port --- pg_llm_batch/health.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/pg_llm_batch/health.py b/pg_llm_batch/health.py index 6abed36a..24b9e460 100644 --- a/pg_llm_batch/health.py +++ b/pg_llm_batch/health.py @@ -13,6 +13,8 @@ import logging from typing import Any, Dict, List +from .postgres_driver_port import PostgresDriverPort + try: # pragma: no cover - optional dependency import psycopg # type: ignore except ImportError: # pragma: no cover @@ -24,9 +26,32 @@ REQUIRED_COMPONENTS = {"database", "pg_tiktoken", "com_config"} -def check_health(dsn: str) -> Dict[str, Any]: - """Return a readiness report ``{ready: bool, components: [...]}``.""" +def _connect_health_database( + dsn: str, + postgres_driver: PostgresDriverPort | None, +) -> Any: + """Open the bounded readiness connection through the selected database seam. + + An explicitly injected driver is authoritative for this call and receives the + same five-second connection budget as the retained Psycopg path. Omitting the + port preserves the current optional-dependency behavior until a replacement + driver has passed the repository's commercial parity gates. + """ + if postgres_driver is not None: + return postgres_driver.connect(dsn, connect_timeout_seconds=5) if psycopg is None: + return None + return psycopg.connect(dsn, connect_timeout=5) + + +def check_health( + dsn: str, + *, + postgres_driver: PostgresDriverPort | None = None, +) -> Dict[str, Any]: + """Return a readiness report using the injected or retained database driver.""" + connection = _connect_health_database(dsn, postgres_driver) + if connection is None: return { "ready": False, "components": [ @@ -35,7 +60,7 @@ def check_health(dsn: str) -> Dict[str, Any]: } components: List[Dict[str, Any]] = [] try: - with psycopg.connect(dsn, connect_timeout=5) as conn: + with connection as conn: with conn.cursor() as cur: cur.execute( "SELECT component, is_ready, detail FROM pg_llm_batch_health_check()" From 6f4620093c4d053023d44cf5660aebc585d018d3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:31:57 +0900 Subject: [PATCH 037/338] fix(postgres): preserve health connection failure boundary --- pg_llm_batch/health.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/pg_llm_batch/health.py b/pg_llm_batch/health.py index 24b9e460..6163ea94 100644 --- a/pg_llm_batch/health.py +++ b/pg_llm_batch/health.py @@ -50,16 +50,20 @@ def check_health( postgres_driver: PostgresDriverPort | None = None, ) -> Dict[str, Any]: """Return a readiness report using the injected or retained database driver.""" - connection = _connect_health_database(dsn, postgres_driver) - if connection is None: - return { - "ready": False, - "components": [ - {"component": "psycopg", "is_ready": False, "detail": "not installed"} - ], - } components: List[Dict[str, Any]] = [] try: + connection = _connect_health_database(dsn, postgres_driver) + if connection is None: + return { + "ready": False, + "components": [ + { + "component": "psycopg", + "is_ready": False, + "detail": "not installed", + } + ], + } with connection as conn: with conn.cursor() as cur: cur.execute( From d7fcd4fba691e79fc4dbdea4b236c1c89049e057 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:37:38 +0900 Subject: [PATCH 038/338] test(postgres): require config stores driver-port injection --- tests/test_config_driver_port.py | 93 ++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 tests/test_config_driver_port.py diff --git a/tests/test_config_driver_port.py b/tests/test_config_driver_port.py new file mode 100644 index 00000000..ac349582 --- /dev/null +++ b/tests/test_config_driver_port.py @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Regression tests for configuration persistence through the PostgreSQL port.""" + +from __future__ import annotations + +from typing import Any + +import pytest + +from pg_llm_batch import config +from pg_llm_batch.config import PostgresConfigStore, SecretStore +from tests.conftest import FakeCursor, FakeKVStore + + +class _ConfigConnection: + """Expose KV persistence through the driver-neutral connection surface.""" + + def __init__(self, store: FakeKVStore) -> None: + self.store = store + self.autocommit_values: list[bool] = [] + self.closed = False + + def cursor(self) -> FakeCursor: + """Return a cursor backed by the shared deterministic KV store.""" + return FakeCursor(self.store) + + def set_autocommit(self, enabled: bool) -> None: + """Record the store's explicit autocommit policy decision.""" + self.autocommit_values.append(enabled) + + def close(self) -> None: + """Record deterministic release of this connection capability.""" + self.closed = True + + +class _ConfigDriver: + """Return distinct connections backed by one deterministic database state.""" + + def __init__(self) -> None: + self.store = FakeKVStore() + self.dsns: list[str] = [] + self.connections: list[_ConfigConnection] = [] + + def connect(self, dsn: str, **_kwargs: Any) -> _ConfigConnection: + """Capture one validated DSN and create a connection on shared state.""" + self.dsns.append(dsn) + connection = _ConfigConnection(self.store) + self.connections.append(connection) + return connection + + +def test_config_store_uses_injected_driver_without_psycopg( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Configuration CRUD must not require Psycopg when a replacement port is supplied.""" + monkeypatch.setattr(config, "psycopg", None) + driver = _ConfigDriver() + + store = PostgresConfigStore( + "postgresql://example", + postgres_driver=driver, # type: ignore[arg-type] + ) + try: + store.set("batch_size", "default", 321) + assert store.get("batch_size", "default") == 321 + assert driver.dsns == ["postgresql://example"] + assert driver.connections[0].autocommit_values == [True] + finally: + store.close() + + assert driver.connections[0].closed is True + + +def test_secret_store_uses_injected_driver_without_psycopg( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Secret persistence must retain the same DB seam without a concrete driver import.""" + monkeypatch.setattr(config, "psycopg", None) + driver = _ConfigDriver() + + store = SecretStore( + "postgresql://example", + postgres_driver=driver, # type: ignore[arg-type] + ) + try: + store.set_secret("provider.key", "secret-value") + assert store.get_secret("provider.key") == "secret-value" + assert driver.dsns == ["postgresql://example"] + assert driver.connections[0].autocommit_values == [True] + finally: + store.close() + + assert driver.connections[0].closed is True From 7f31ef5fcf2af2f6ae2937158289923c48573909 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:40:47 +0900 Subject: [PATCH 039/338] feat(postgres): route config stores through driver port --- pg_llm_batch/config.py | 55 +++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/pg_llm_batch/config.py b/pg_llm_batch/config.py index 1a33a4d0..e8f9b20a 100644 --- a/pg_llm_batch/config.py +++ b/pg_llm_batch/config.py @@ -21,6 +21,7 @@ from typing import Any, Dict, Iterable, Optional, Tuple, Type from .exceptions import ConfigError +from .postgres_driver_port import PostgresDriverPort try: # pragma: no cover - optional dependency import psycopg # type: ignore @@ -155,23 +156,55 @@ def _split_full_key(full_key: str) -> Tuple[str, str]: return "global", full_key +def _connect_store_database( + dsn: str, + postgres_driver: PostgresDriverPort | None, + *, + missing_dependency_message: str, +) -> Any: + """Open one config-store connection through the selected driver boundary. + + Explicit driver injection lets these durable stores migrate independently of + the retained Psycopg runtime while preserving the same connection identity + for table setup, reads, and writes. The legacy default remains available + until a replacement adapter has passed the repository's parity gates. + """ + if postgres_driver is not None: + connection = postgres_driver.connect(dsn) + connection.set_autocommit(True) + return connection + if psycopg is None: + raise ConfigError(missing_dependency_message) + connection = psycopg.connect(dsn) + connection.autocommit = True + return connection + + class PostgresConfigStore: """PostgreSQL-backed KV configuration store (``com_config`` table).""" TABLE_NAME = "com_config" - def __init__(self, dsn: str) -> None: - """Connect to PostgreSQL and initialize the configuration cache.""" - if psycopg is None: + def __init__( + self, + dsn: str, + *, + postgres_driver: PostgresDriverPort | None = None, + ) -> None: + """Connect through the selected driver and initialize the config cache.""" + if postgres_driver is None and psycopg is None: raise ConfigError("psycopg is required for PostgresConfigStore") if not dsn: raise ConfigError( "A Postgres DSN must be provided explicitly (no os.getenv for config)" ) self.dsn = dsn - self._conn = psycopg.connect(self.dsn) + self._conn = _connect_store_database( + self.dsn, + postgres_driver, + missing_dependency_message="psycopg is required for PostgresConfigStore", + ) try: - self._conn.autocommit = True self.cache: Dict[str, Dict[str, Any]] = {} self._ensure_table() self._ensure_defaults() @@ -301,9 +334,10 @@ def __init__( fernet_key: Optional[str] = None, *, require_encryption: bool = False, + postgres_driver: PostgresDriverPort | None = None, ) -> None: - """Connect using optional Fernet encryption or fail when it is required.""" - if psycopg is None: + """Connect through the selected driver with the requested secret policy.""" + if postgres_driver is None and psycopg is None: raise ConfigError("psycopg is required for SecretStore") if not dsn: raise ConfigError("A Postgres DSN must be provided explicitly") @@ -316,9 +350,12 @@ def __init__( "Fernet encryption requires the optional cryptography dependency" ) self.dsn = dsn - self._conn = psycopg.connect(self.dsn) + self._conn = _connect_store_database( + self.dsn, + postgres_driver, + missing_dependency_message="psycopg is required for SecretStore", + ) try: - self._conn.autocommit = True self._fernet = None if fernet_key and Fernet is not None: self._fernet = Fernet(fernet_key.encode("utf-8")) From 9b30ce617ca53b7a14b1473e63d475ffaea3f315 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:41:18 +0900 Subject: [PATCH 040/338] test(postgres): require config setup failure cleanup --- tests/test_config_driver_port.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/test_config_driver_port.py b/tests/test_config_driver_port.py index ac349582..d1bbefaf 100644 --- a/tests/test_config_driver_port.py +++ b/tests/test_config_driver_port.py @@ -91,3 +91,33 @@ def test_secret_store_uses_injected_driver_without_psycopg( store.close() assert driver.connections[0].closed is True + + +def test_config_store_closes_connection_when_autocommit_setup_fails( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A replacement-driver setup failure must not leak the opened DB connection.""" + monkeypatch.setattr(config, "psycopg", None) + + class _BrokenConnection(_ConfigConnection): + def set_autocommit(self, enabled: bool) -> None: + """Fail after connection creation to exercise constructor cleanup.""" + super().set_autocommit(enabled) + raise RuntimeError("autocommit setup failed") + + class _BrokenDriver(_ConfigDriver): + def connect(self, dsn: str, **_kwargs: Any) -> _BrokenConnection: + """Return the observable broken connection for cleanup verification.""" + self.dsns.append(dsn) + connection = _BrokenConnection(self.store) + self.connections.append(connection) + return connection + + driver = _BrokenDriver() + with pytest.raises(RuntimeError, match="autocommit setup failed"): + PostgresConfigStore( + "postgresql://example", + postgres_driver=driver, # type: ignore[arg-type] + ) + + assert driver.connections[0].closed is True From 41efac77d6a67a2acc89ecae402d91ce0c75c3dc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:42:15 +0900 Subject: [PATCH 041/338] fix(postgres): preserve config setup cleanup --- pg_llm_batch/config.py | 43 ++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/pg_llm_batch/config.py b/pg_llm_batch/config.py index e8f9b20a..9506ac9d 100644 --- a/pg_llm_batch/config.py +++ b/pg_llm_batch/config.py @@ -35,8 +35,6 @@ logger = logging.getLogger(__name__) -# Default configuration tree. Mirrors the upstream batch tunables so behaviour -# is preserved after extraction. Secrets are NOT stored here. DEFAULT_CONFIG_TREE: Dict[str, Dict[str, Any]] = { "batch_size": { "min": 100, @@ -45,7 +43,7 @@ "description": "Batch request size limit", }, "token_limits": { - "per_batch": 5_000_000_000, # 5B tokens + "per_batch": 5_000_000_000, "per_request": 128_000, "buffer_percentage": 5, "description": "Token count limits", @@ -166,18 +164,25 @@ def _connect_store_database( Explicit driver injection lets these durable stores migrate independently of the retained Psycopg runtime while preserving the same connection identity - for table setup, reads, and writes. The legacy default remains available - until a replacement adapter has passed the repository's parity gates. + for table setup, reads, and writes. Autocommit setup stays outside this helper + so a constructor can close the already-opened connection if setup fails. """ if postgres_driver is not None: - connection = postgres_driver.connect(dsn) - connection.set_autocommit(True) - return connection + return postgres_driver.connect(dsn) if psycopg is None: raise ConfigError(missing_dependency_message) - connection = psycopg.connect(dsn) + return psycopg.connect(dsn) + + +def _set_store_autocommit( + connection: Any, + postgres_driver: PostgresDriverPort | None, +) -> None: + """Enable explicit store autocommit through the selected connection contract.""" + if postgres_driver is not None: + connection.set_autocommit(True) + return connection.autocommit = True - return connection class PostgresConfigStore: @@ -205,6 +210,7 @@ def __init__( missing_dependency_message="psycopg is required for PostgresConfigStore", ) try: + _set_store_autocommit(self._conn, postgres_driver) self.cache: Dict[str, Dict[str, Any]] = {} self._ensure_table() self._ensure_defaults() @@ -216,7 +222,7 @@ def __init__( def _ensure_table(self) -> None: """Create the ``com_config`` table if it does not already exist.""" with self._conn.cursor() as cur: - cur.execute( # nosemgrep -- formatted-sql-query / sqlalchemy-execute-raw-query FP: only the fixed class constant TABLE_NAME ("com_config") is interpolated; every value is bound via %s placeholders. + cur.execute( f""" CREATE TABLE IF NOT EXISTS {self.TABLE_NAME} ( config_key TEXT PRIMARY KEY, @@ -231,7 +237,7 @@ def _ensure_defaults(self) -> None: """Insert any missing default config rows without overwriting existing ones.""" with self._conn.cursor() as cur: for item in DEFAULT_CONFIG_INDEX.values(): - cur.execute( # nosemgrep -- sqlalchemy-execute-raw-query FP: only the fixed TABLE_NAME constant is interpolated; all values are bound via %s placeholders. + cur.execute( f""" INSERT INTO {self.TABLE_NAME} (config_key, config_value, config_description) @@ -249,7 +255,7 @@ def _load_cache(self) -> None: """Reload the in-memory cache from every row in the config table.""" self.cache.clear() with self._conn.cursor() as cur: - cur.execute(f"SELECT config_key, config_value FROM {self.TABLE_NAME}") # nosemgrep -- formatted-sql-query / sqlalchemy-execute-raw-query FP: only the fixed TABLE_NAME constant is interpolated; no user input reaches the query. + cur.execute(f"SELECT config_key, config_value FROM {self.TABLE_NAME}") for config_key, config_value in cur.fetchall(): category, key = _split_full_key(config_key) value = _deserialize_value(config_key, config_value) @@ -261,7 +267,7 @@ def get(self, category: str, key: str, default: Any = None) -> Any: return _isolated_cached_value(self.cache[category][key]) full_key = f"{category}.{key}" with self._conn.cursor() as cur: - cur.execute( # nosemgrep -- sqlalchemy-execute-raw-query FP: only the fixed TABLE_NAME constant is interpolated; the lookup value is bound via a %s placeholder. + cur.execute( f"SELECT config_value FROM {self.TABLE_NAME} WHERE config_key = %s", (full_key,), ) @@ -280,7 +286,7 @@ def set(self, category: str, key: str, value: Any) -> None: item = DEFAULT_CONFIG_INDEX.get(full_key) description = item["description"] if item else full_key with self._conn.cursor() as cur: - cur.execute( # nosemgrep -- sqlalchemy-execute-raw-query FP: only the fixed TABLE_NAME constant is interpolated; all values are bound via %s placeholders. + cur.execute( f""" INSERT INTO {self.TABLE_NAME} (config_key, config_value, config_description) @@ -356,6 +362,7 @@ def __init__( missing_dependency_message="psycopg is required for SecretStore", ) try: + _set_store_autocommit(self._conn, postgres_driver) self._fernet = None if fernet_key and Fernet is not None: self._fernet = Fernet(fernet_key.encode("utf-8")) @@ -367,7 +374,7 @@ def __init__( def _ensure_table(self) -> None: """Create the ``com_secrets`` table if it does not already exist.""" with self._conn.cursor() as cur: - cur.execute( # nosemgrep -- formatted-sql-query / sqlalchemy-execute-raw-query FP: only the fixed class constant TABLE_NAME ("com_secrets") is interpolated; every value is bound via %s placeholders. + cur.execute( f""" CREATE TABLE IF NOT EXISTS {self.TABLE_NAME} ( secret_key TEXT PRIMARY KEY, @@ -382,7 +389,7 @@ def _encode(self, raw: str) -> Tuple[str, bool]: """Encode a secret for storage, returning the text and whether it is encrypted.""" if self._fernet is not None: return self._fernet.encrypt(raw.encode("utf-8")).decode("utf-8"), True - logger.warning( # nosemgrep -- python-logger-credential-disclosure FP: the message text contains the word "secret", but the only logged argument is the literal mask "***"; no secret value is ever logged. + logger.warning( "No Fernet key configured; secret '%s' stored base64-obfuscated only.", "***", ) @@ -402,7 +409,7 @@ def set_secret(self, key: str, value: str) -> None: """Encrypt or obfuscate and persist a secret value.""" encoded, is_encrypted = self._encode(value) with self._conn.cursor() as cur: - cur.execute( # nosemgrep -- sqlalchemy-execute-raw-query FP: only the fixed TABLE_NAME constant is interpolated; all values are bound via %s placeholders. + cur.execute( f""" INSERT INTO {self.TABLE_NAME} (secret_key, secret_value, is_encrypted) VALUES (%s, %s, %s) From 804d9656c95fa7ca57bc4bedd8032f6b973bcda6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:43:21 +0900 Subject: [PATCH 042/338] chore(postgres): preserve scanner evidence comments --- pg_llm_batch/config.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/pg_llm_batch/config.py b/pg_llm_batch/config.py index 9506ac9d..d16c4bd2 100644 --- a/pg_llm_batch/config.py +++ b/pg_llm_batch/config.py @@ -35,6 +35,8 @@ logger = logging.getLogger(__name__) +# Default configuration tree. Mirrors the upstream batch tunables so behaviour +# is preserved after extraction. Secrets are NOT stored here. DEFAULT_CONFIG_TREE: Dict[str, Dict[str, Any]] = { "batch_size": { "min": 100, @@ -43,7 +45,7 @@ "description": "Batch request size limit", }, "token_limits": { - "per_batch": 5_000_000_000, + "per_batch": 5_000_000_000, # 5B tokens "per_request": 128_000, "buffer_percentage": 5, "description": "Token count limits", @@ -222,7 +224,7 @@ def __init__( def _ensure_table(self) -> None: """Create the ``com_config`` table if it does not already exist.""" with self._conn.cursor() as cur: - cur.execute( + cur.execute( # nosemgrep -- formatted-sql-query / sqlalchemy-execute-raw-query FP: only the fixed class constant TABLE_NAME ("com_config") is interpolated; every value is bound via %s placeholders. f""" CREATE TABLE IF NOT EXISTS {self.TABLE_NAME} ( config_key TEXT PRIMARY KEY, @@ -237,7 +239,7 @@ def _ensure_defaults(self) -> None: """Insert any missing default config rows without overwriting existing ones.""" with self._conn.cursor() as cur: for item in DEFAULT_CONFIG_INDEX.values(): - cur.execute( + cur.execute( # nosemgrep -- sqlalchemy-execute-raw-query FP: only the fixed TABLE_NAME constant is interpolated; all values are bound via %s placeholders. f""" INSERT INTO {self.TABLE_NAME} (config_key, config_value, config_description) @@ -255,7 +257,7 @@ def _load_cache(self) -> None: """Reload the in-memory cache from every row in the config table.""" self.cache.clear() with self._conn.cursor() as cur: - cur.execute(f"SELECT config_key, config_value FROM {self.TABLE_NAME}") + cur.execute(f"SELECT config_key, config_value FROM {self.TABLE_NAME}") # nosemgrep -- formatted-sql-query / sqlalchemy-execute-raw-query FP: only the fixed TABLE_NAME constant is interpolated; no user input reaches the query. for config_key, config_value in cur.fetchall(): category, key = _split_full_key(config_key) value = _deserialize_value(config_key, config_value) @@ -267,7 +269,7 @@ def get(self, category: str, key: str, default: Any = None) -> Any: return _isolated_cached_value(self.cache[category][key]) full_key = f"{category}.{key}" with self._conn.cursor() as cur: - cur.execute( + cur.execute( # nosemgrep -- sqlalchemy-execute-raw-query FP: only the fixed TABLE_NAME constant is interpolated; the lookup value is bound via a %s placeholder. f"SELECT config_value FROM {self.TABLE_NAME} WHERE config_key = %s", (full_key,), ) @@ -286,7 +288,7 @@ def set(self, category: str, key: str, value: Any) -> None: item = DEFAULT_CONFIG_INDEX.get(full_key) description = item["description"] if item else full_key with self._conn.cursor() as cur: - cur.execute( + cur.execute( # nosemgrep -- sqlalchemy-execute-raw-query FP: only the fixed TABLE_NAME constant is interpolated; all values are bound via %s placeholders. f""" INSERT INTO {self.TABLE_NAME} (config_key, config_value, config_description) @@ -374,7 +376,7 @@ def __init__( def _ensure_table(self) -> None: """Create the ``com_secrets`` table if it does not already exist.""" with self._conn.cursor() as cur: - cur.execute( + cur.execute( # nosemgrep -- formatted-sql-query / sqlalchemy-execute-raw-query FP: only the fixed class constant TABLE_NAME ("com_secrets") is interpolated; every value is bound via %s placeholders. f""" CREATE TABLE IF NOT EXISTS {self.TABLE_NAME} ( secret_key TEXT PRIMARY KEY, @@ -389,7 +391,7 @@ def _encode(self, raw: str) -> Tuple[str, bool]: """Encode a secret for storage, returning the text and whether it is encrypted.""" if self._fernet is not None: return self._fernet.encrypt(raw.encode("utf-8")).decode("utf-8"), True - logger.warning( + logger.warning( # nosemgrep -- python-logger-credential-disclosure FP: the message text contains the word "secret", but the only logged argument is the literal mask "***"; no secret value is ever logged. "No Fernet key configured; secret '%s' stored base64-obfuscated only.", "***", ) @@ -409,7 +411,7 @@ def set_secret(self, key: str, value: str) -> None: """Encrypt or obfuscate and persist a secret value.""" encoded, is_encrypted = self._encode(value) with self._conn.cursor() as cur: - cur.execute( + cur.execute( # nosemgrep -- sqlalchemy-execute-raw-query FP: only the fixed TABLE_NAME constant is interpolated; all values are bound via %s placeholders. f""" INSERT INTO {self.TABLE_NAME} (secret_key, secret_value, is_encrypted) VALUES (%s, %s, %s) From 71451e5dff4b55ef927e174725eeb811af959d83 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 20:07:24 +0900 Subject: [PATCH 043/338] test(health): redact driver failure details --- tests/test_health_driver_port.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/tests/test_health_driver_port.py b/tests/test_health_driver_port.py index d1fcf19b..4cda7131 100644 --- a/tests/test_health_driver_port.py +++ b/tests/test_health_driver_port.py @@ -97,23 +97,27 @@ def test_check_health_uses_injected_driver_without_psycopg( def test_check_health_bounds_injected_driver_failures_without_psycopg( monkeypatch: pytest.MonkeyPatch, ) -> None: - """Replacement-driver failures remain a database readiness result, not a crash.""" + """Replacement-driver failures remain bounded without reflecting connection data.""" monkeypatch.setattr(health, "psycopg", None) + secret_sentinel = "postgresql://user:private-password@db.example/batch" class _BrokenDriver: def connect(self, _dsn: str, **_kwargs: Any) -> None: - raise OSError("replacement driver connection refused") + raise OSError(f"connection refused for {secret_sentinel}") report = health.check_health( "postgresql://example", postgres_driver=_BrokenDriver(), # type: ignore[arg-type] ) - assert report["ready"] is False - assert report["components"] == [ - { - "component": "database", - "is_ready": False, - "detail": "replacement driver connection refused", - } - ] + assert report == { + "ready": False, + "components": [ + { + "component": "database", + "is_ready": False, + "detail": "database readiness check failed", + } + ], + } + assert secret_sentinel not in repr(report) From cf69773586bbdb575c30a8d5b64c687acf10290e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 20:08:06 +0900 Subject: [PATCH 044/338] fix(health): bound database failure details --- pg_llm_batch/health.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pg_llm_batch/health.py b/pg_llm_batch/health.py index 6163ea94..7b5f9227 100644 --- a/pg_llm_batch/health.py +++ b/pg_llm_batch/health.py @@ -77,11 +77,16 @@ def check_health( "detail": detail, } ) - except Exception as exc: + except Exception: + logger.debug("Database readiness check failed") return { "ready": False, "components": [ - {"component": "database", "is_ready": False, "detail": str(exc)} + { + "component": "database", + "is_ready": False, + "detail": "database readiness check failed", + } ], } From 88cf659748823327069875772846e5bd559b371b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 20:09:54 +0900 Subject: [PATCH 045/338] test(health): require server driver forwarding --- tests/test_health.py | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/tests/test_health.py b/tests/test_health.py index e9362580..d077f051 100644 --- a/tests/test_health.py +++ b/tests/test_health.py @@ -76,8 +76,8 @@ def test_missing_required_component_is_reported_not_ready(monkeypatch): } -def test_health_dependency_and_database_failures_include_reason(monkeypatch): - """Dependency and connection failures are explicit rather than hidden.""" +def test_health_dependency_and_database_failures_are_bounded(monkeypatch): + """Dependency absence is explicit while runtime failures stay content-free.""" monkeypatch.setattr(health, "psycopg", None) report = health.check_health("postgresql://example") assert report == { @@ -90,12 +90,21 @@ def test_health_dependency_and_database_failures_include_reason(monkeypatch): class BrokenPsycopg: @staticmethod def connect(_dsn, *, connect_timeout): - raise OSError(f"connection refused after {connect_timeout}s") + raise OSError(f"private-dsn-sentinel after {connect_timeout}s") monkeypatch.setattr(health, "psycopg", BrokenPsycopg()) report = health.check_health("postgresql://example") - assert report["ready"] is False - assert "connection refused after 5s" in report["components"][0]["detail"] + assert report == { + "ready": False, + "components": [ + { + "component": "database", + "is_ready": False, + "detail": "database readiness check failed", + } + ], + } + assert "private-dsn-sentinel" not in repr(report) def test_health_requires_every_required_component(monkeypatch): @@ -115,8 +124,10 @@ def test_health_requires_every_required_component(monkeypatch): def test_serve_healthz_reports_status_body_and_not_found(monkeypatch): - """The HTTP wrapper emits JSON readiness and a strict 404 elsewhere.""" + """The HTTP wrapper emits JSON readiness and forwards the selected DB driver.""" events = [] + observed = {} + driver = object() class FakeHTTPServer: def __init__(self, address, handler_class): @@ -142,13 +153,19 @@ def serve_forever(self): handler = self.handler_class.__new__(self.handler_class) assert handler.log_message("ignored") is None + def fake_check_health(_dsn, *, postgres_driver=None): + observed["driver"] = postgres_driver + return {"ready": False, "components": []} + monkeypatch.setattr("http.server.HTTPServer", FakeHTTPServer) - monkeypatch.setattr( - health, - "check_health", - lambda _dsn: {"ready": False, "components": []}, + monkeypatch.setattr(health, "check_health", fake_check_health) + health.serve_healthz( + "postgresql://example", + host="127.0.0.1", + port=8090, + postgres_driver=driver, # type: ignore[arg-type] ) - health.serve_healthz("postgresql://example", host="127.0.0.1", port=8090) + assert observed["driver"] is driver assert ("address", ("127.0.0.1", 8090)) in events assert ("/other", "status", 404) in events assert ("/healthz/", "status", 503) in events From 88b44ceacb636081eb6741835d286d2d5ffd28fc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 20:11:59 +0900 Subject: [PATCH 046/338] fix(health): forward selected postgres driver --- pg_llm_batch/health.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pg_llm_batch/health.py b/pg_llm_batch/health.py index 7b5f9227..3ba79a17 100644 --- a/pg_llm_batch/health.py +++ b/pg_llm_batch/health.py @@ -154,8 +154,14 @@ def public_health_report(report: Dict[str, Any]) -> Dict[str, Any]: } -def serve_healthz(dsn: str, host: str = "0.0.0.0", port: int = 8080) -> None: - """Serve a minimal ``/healthz`` endpoint (blocking).""" +def serve_healthz( + dsn: str, + host: str = "0.0.0.0", + port: int = 8080, + *, + postgres_driver: PostgresDriverPort | None = None, +) -> None: + """Serve ``/healthz`` using the selected PostgreSQL driver boundary.""" from http.server import BaseHTTPRequestHandler, HTTPServer class _Handler(BaseHTTPRequestHandler): @@ -167,7 +173,9 @@ def do_GET(self) -> None: # noqa: N802 (stdlib naming) self.send_response(404) self.end_headers() return - report = public_health_report(check_health(dsn)) + report = public_health_report( + check_health(dsn, postgres_driver=postgres_driver) + ) body = json.dumps(report).encode("utf-8") self.send_response(200 if report["ready"] else 503) self.send_header("Content-Type", "application/json") From a63fedc3b4c3f4df30c6c2252ca44cd6716227c9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 20:21:20 +0900 Subject: [PATCH 047/338] test(compose): require postgres driver bootstrap seam --- tests/test_compose_bootstrap_driver_port.py | 101 ++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 tests/test_compose_bootstrap_driver_port.py diff --git a/tests/test_compose_bootstrap_driver_port.py b/tests/test_compose_bootstrap_driver_port.py new file mode 100644 index 00000000..eed150e2 --- /dev/null +++ b/tests/test_compose_bootstrap_driver_port.py @@ -0,0 +1,101 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Regression tests for Compose bootstrap through the PostgreSQL driver port.""" + +from __future__ import annotations + +from collections.abc import Mapping +from pathlib import Path + +from pg_llm_batch import compose_bootstrap + + +class _BootstrapDriver: + """Capture conninfo and health-service use without importing a concrete driver.""" + + def __init__(self) -> None: + self.parsed: list[str] = [] + self.rendered: list[dict[str, str]] = [] + + def parse_conninfo(self, dsn: str) -> Mapping[str, str]: + """Return the credential-free target fields expected by the bootstrap.""" + self.parsed.append(dsn) + return { + "user": "pgllm", + "host": "postgres", + "port": "5432", + "dbname": "pgllm", + } + + def make_conninfo(self, params: Mapping[str, str]) -> str: + """Record the exact private parameter map and return an opaque DSN.""" + snapshot = dict(params) + self.rendered.append(snapshot) + return "driver-private-dsn" + + +def test_build_private_dsn_uses_injected_driver_without_legacy_renderer() -> None: + """Mounted-secret assembly must be usable after the Psycopg renderer is removed.""" + driver = _BootstrapDriver() + + private_dsn = compose_bootstrap._build_private_dsn( + "postgresql://pgllm@postgres:5432/pgllm", + "private-password", + postgres_driver=driver, # type: ignore[arg-type] + ) + + assert private_dsn == "driver-private-dsn" + assert driver.parsed == ["postgresql://pgllm@postgres:5432/pgllm"] + assert driver.rendered == [ + { + "user": "pgllm", + "host": "postgres", + "port": "5432", + "dbname": "pgllm", + "password": "private-password", + } + ] + + +def test_run_compose_health_forwards_one_driver_to_dsn_and_health_boundaries( + tmp_path: Path, + monkeypatch, +) -> None: + """One selected driver must own both secret-safe DSN assembly and readiness I/O.""" + password_file = tmp_path / "database-password" + password_file.write_text("private-password", encoding="utf-8") + driver = _BootstrapDriver() + observed: dict[str, object] = {} + + monkeypatch.setattr( + compose_bootstrap, + "resolve_dsn", + lambda explicit=None: "postgresql://pgllm@postgres:5432/pgllm", + ) + + def capture_health( + dsn: str, + host: str, + port: int, + *, + postgres_driver=None, + ) -> None: + observed.update( + dsn=dsn, + host=host, + port=port, + postgres_driver=postgres_driver, + ) + + monkeypatch.setattr(compose_bootstrap, "serve_healthz", capture_health) + + compose_bootstrap.run_compose_health( + password_file, + postgres_driver=driver, # type: ignore[arg-type] + ) + + assert observed == { + "dsn": "driver-private-dsn", + "host": "0.0.0.0", + "port": 8080, + "postgres_driver": driver, + } From 825efc9bdba883ef2a2a514fd0bf1e8bc881bc1e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 20:21:58 +0900 Subject: [PATCH 048/338] fix(compose): route bootstrap through postgres driver port --- pg_llm_batch/compose_bootstrap.py | 62 +++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/pg_llm_batch/compose_bootstrap.py b/pg_llm_batch/compose_bootstrap.py index b3987f40..7b58fa6d 100644 --- a/pg_llm_batch/compose_bootstrap.py +++ b/pg_llm_batch/compose_bootstrap.py @@ -5,8 +5,8 @@ The Compose profile keeps the database password out of committed configuration, process arguments, and the credential-free bootstrap DSN. This module reads the single explicitly mounted secret, combines it with the bootstrap target only in -process memory using psycopg's conninfo quoting, and hands the result directly to -the existing health server. +process memory through the selected PostgreSQL driver boundary, and hands the +result directly to the existing health server. """ from __future__ import annotations @@ -15,11 +15,15 @@ from pathlib import Path from typing import Sequence -from psycopg.conninfo import make_conninfo - from .bootstrap import resolve_dsn from .exceptions import ConfigError from .health import serve_healthz +from .postgres_driver_port import PostgresDriverPort + +try: # pragma: no cover - retained optional dependency during migration + from psycopg.conninfo import make_conninfo as _psycopg_make_conninfo +except ImportError: # pragma: no cover + _psycopg_make_conninfo = None _DEFAULT_PASSWORD_FILE = Path("/run/secrets/postgres_password") _MAX_PASSWORD_BYTES = 65_536 @@ -50,20 +54,56 @@ def _load_database_password(password_file: Path) -> str: return password -def _build_private_dsn(base_dsn: str, password: str) -> str: - """Add the password to a validated DSN using psycopg's conninfo quoting.""" +def _build_private_dsn( + base_dsn: str, + password: str, + *, + postgres_driver: PostgresDriverPort | None = None, +) -> str: + """Add the mounted password through the selected reviewed conninfo renderer. + + An injected replacement driver parses the credential-free selector and then + renders a fresh parameter snapshot containing the mounted password. The + retained Psycopg renderer remains the default only while the commercial + migration is incomplete. Parser or renderer diagnostics are normalized so + secret material never escapes this bootstrap boundary. + """ try: - return make_conninfo(base_dsn, password=password) + if postgres_driver is not None: + parameters = dict(postgres_driver.parse_conninfo(base_dsn)) + parameters["password"] = password + return postgres_driver.make_conninfo(parameters) + if _psycopg_make_conninfo is None: + raise ConfigError("The PostgreSQL bootstrap driver is unavailable.") + return _psycopg_make_conninfo(base_dsn, password=password) + except ConfigError: + raise except Exception: raise ConfigError("The PostgreSQL bootstrap target is invalid.") from None -def run_compose_health(password_file: Path = _DEFAULT_PASSWORD_FILE) -> None: - """Serve health checks using the credential-free DSN plus mounted secret.""" +def run_compose_health( + password_file: Path = _DEFAULT_PASSWORD_FILE, + *, + postgres_driver: PostgresDriverPort | None = None, +) -> None: + """Serve readiness with one driver owning private DSN assembly and database I/O.""" base_dsn = resolve_dsn(None) password = _load_database_password(password_file) - private_dsn = _build_private_dsn(base_dsn, password) - serve_healthz(private_dsn, host="0.0.0.0", port=8080) + private_dsn = _build_private_dsn( + base_dsn, + password, + postgres_driver=postgres_driver, + ) + if postgres_driver is None: + serve_healthz(private_dsn, host="0.0.0.0", port=8080) + return + serve_healthz( + private_dsn, + host="0.0.0.0", + port=8080, + postgres_driver=postgres_driver, + ) def _password_file_from_args(argv: Sequence[str] | None) -> Path: From 9e4ab4b77eb48bfe5b8fb45ba8d593a69e491b71 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 22:10:00 +0900 Subject: [PATCH 049/338] test(postgres): bound driver candidate identity evidence --- tests/test_postgres_driver_candidate.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_postgres_driver_candidate.py b/tests/test_postgres_driver_candidate.py index 7e80caba..5ceefae2 100644 --- a/tests/test_postgres_driver_candidate.py +++ b/tests/test_postgres_driver_candidate.py @@ -166,3 +166,22 @@ def license_spdx(self) -> str: with pytest.raises(PostgresDriverCandidateEvidenceError): evaluate_postgres_driver_candidate(CandidateShapedObject()) # type: ignore[arg-type] + + +@pytest.mark.parametrize("field_name", ["package_name", "package_version", "license_spdx"]) +def test_candidate_rejects_surrounding_whitespace_in_identity_evidence( + field_name: str, +) -> None: + with pytest.raises(PostgresDriverCandidateEvidenceError): + _evidence(**{field_name: " candidate-driver "}) + + +@pytest.mark.parametrize("field_name", ["package_name", "package_version", "license_spdx"]) +def test_candidate_rejects_unbounded_identity_evidence(field_name: str) -> None: + with pytest.raises(PostgresDriverCandidateEvidenceError): + _evidence(**{field_name: "x" * 257}) + + +def test_candidate_rejects_duplicate_python_version_evidence() -> None: + with pytest.raises(PostgresDriverCandidateEvidenceError, match="Python version"): + _evidence(python_versions=("3.14", "3.14")) From 7647dacb05701b53bafb6ae9568f5a54cfac51dc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 22:11:07 +0900 Subject: [PATCH 050/338] fix(postgres): bound driver candidate identity evidence --- pg_llm_batch/postgres_driver_candidate.py | 30 ++++++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index eee0110a..93f45227 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -45,6 +45,7 @@ "PostgreSQL", } ) +_MAX_IDENTITY_EVIDENCE_BYTES = 256 _MINOR_PYTHON_VERSION = re.compile(r"^[1-9][0-9]*\.[0-9]+$") _SOURCE_COMMIT_SHA = re.compile(r"^[0-9a-f]{40}$") _ARTIFACT_SHA256 = re.compile(r"^[0-9a-f]{64}$") @@ -59,6 +60,26 @@ class PostgresDriverCandidateEvidenceError(ValueError): """ +def _validate_identity_text(label: str, value: object) -> None: + """Require one finite, exact package-identity token without normalization. + + Package name, version, and SPDX evidence participate in an acquisition + decision and can arrive from untrusted package metadata. Rejecting surrounding + whitespace prevents two textual identities from being treated as equivalent, + while the UTF-8 byte ceiling keeps malformed metadata from expanding an + otherwise tiny decision record without imposing a package-manager grammar. + """ + if ( + type(value) is not str + or not value + or value != value.strip() + or len(value.encode("utf-8")) > _MAX_IDENTITY_EVIDENCE_BYTES + ): + raise PostgresDriverCandidateEvidenceError( + f"PostgreSQL driver {label} evidence is invalid" + ) + + @dataclass(frozen=True, slots=True) class PostgresDriverCandidateEvidence: """Describe one validated PostgreSQL-driver package candidate. @@ -86,10 +107,7 @@ def __post_init__(self) -> None: ("package version", self.package_version), ("license", self.license_spdx), ): - if type(value) is not str or not value.strip(): - raise PostgresDriverCandidateEvidenceError( - f"PostgreSQL driver {label} evidence is invalid" - ) + _validate_identity_text(label, value) if type(self.python_versions) is not tuple or not self.python_versions: raise PostgresDriverCandidateEvidenceError( "PostgreSQL driver Python version evidence is invalid" @@ -101,6 +119,10 @@ def __post_init__(self) -> None: raise PostgresDriverCandidateEvidenceError( "PostgreSQL driver Python version evidence is invalid" ) + if len(set(self.python_versions)) != len(self.python_versions): + raise PostgresDriverCandidateEvidenceError( + "PostgreSQL driver Python version evidence is invalid" + ) if ( type(self.source_commit_sha) is not str or _SOURCE_COMMIT_SHA.fullmatch(self.source_commit_sha) is None From f05a84f6149c393dd7890bf5c483429c03d2847d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 22:15:58 +0900 Subject: [PATCH 051/338] test(postgres): reject control-bearing driver identity --- tests/test_postgres_driver_candidate.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_postgres_driver_candidate.py b/tests/test_postgres_driver_candidate.py index 5ceefae2..e5585825 100644 --- a/tests/test_postgres_driver_candidate.py +++ b/tests/test_postgres_driver_candidate.py @@ -176,6 +176,22 @@ def test_candidate_rejects_surrounding_whitespace_in_identity_evidence( _evidence(**{field_name: " candidate-driver "}) +@pytest.mark.parametrize( + "identity_value", + [ + "candidate\ndriver", + "candidate\tdriver", + "candidate\u00a0driver", + "candidate\x7fdriver", + ], +) +def test_candidate_rejects_embedded_whitespace_or_control_identity_evidence( + identity_value: str, +) -> None: + with pytest.raises(PostgresDriverCandidateEvidenceError): + _evidence(package_name=identity_value) + + @pytest.mark.parametrize("field_name", ["package_name", "package_version", "license_spdx"]) def test_candidate_rejects_unbounded_identity_evidence(field_name: str) -> None: with pytest.raises(PostgresDriverCandidateEvidenceError): From 49ed804bed65bed3defcf9fc879d068a40d75e45 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 22:16:55 +0900 Subject: [PATCH 052/338] fix(postgres): reject control-bearing driver identity --- pg_llm_batch/postgres_driver_candidate.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index 93f45227..88542e70 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -64,16 +64,18 @@ def _validate_identity_text(label: str, value: object) -> None: """Require one finite, exact package-identity token without normalization. Package name, version, and SPDX evidence participate in an acquisition - decision and can arrive from untrusted package metadata. Rejecting surrounding - whitespace prevents two textual identities from being treated as equivalent, - while the UTF-8 byte ceiling keeps malformed metadata from expanding an - otherwise tiny decision record without imposing a package-manager grammar. + decision and can arrive from untrusted package metadata. Rejecting whitespace + and control characters keeps one evidence value from becoming multiple visual + or line-oriented identities, while the UTF-8 byte ceiling bounds malformed + metadata without inventing a package-manager-specific grammar. """ + if type(value) is not str or not value: + raise PostgresDriverCandidateEvidenceError( + f"PostgreSQL driver {label} evidence is invalid" + ) if ( - type(value) is not str - or not value - or value != value.strip() - or len(value.encode("utf-8")) > _MAX_IDENTITY_EVIDENCE_BYTES + len(value.encode("utf-8")) > _MAX_IDENTITY_EVIDENCE_BYTES + or any(character.isspace() or ord(character) < 32 or ord(character) == 127 for character in value) ): raise PostgresDriverCandidateEvidenceError( f"PostgreSQL driver {label} evidence is invalid" From 1df9614857ac8f0af30e970690a46c3decbba5b5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 22:18:16 +0900 Subject: [PATCH 053/338] style(postgres): keep candidate identity guard lint-safe --- pg_llm_batch/postgres_driver_candidate.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index 88542e70..818a5f49 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -75,7 +75,12 @@ def _validate_identity_text(label: str, value: object) -> None: ) if ( len(value.encode("utf-8")) > _MAX_IDENTITY_EVIDENCE_BYTES - or any(character.isspace() or ord(character) < 32 or ord(character) == 127 for character in value) + or any( + character.isspace() + or ord(character) < 32 + or ord(character) == 127 + for character in value + ) ): raise PostgresDriverCandidateEvidenceError( f"PostgreSQL driver {label} evidence is invalid" From 7a12c0c4415531e4505fc65d8a7a47a7c3709dde Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 22:25:57 +0900 Subject: [PATCH 054/338] test(postgres): require full supported Python parity --- tests/test_postgres_driver_candidate.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/tests/test_postgres_driver_candidate.py b/tests/test_postgres_driver_candidate.py index e5585825..730bfead 100644 --- a/tests/test_postgres_driver_candidate.py +++ b/tests/test_postgres_driver_candidate.py @@ -4,6 +4,7 @@ from pg_llm_batch.postgres_driver_candidate import ( REQUIRED_POSTGRES_DRIVER_CAPABILITIES, + REQUIRED_POSTGRES_DRIVER_PYTHON_VERSIONS, PostgresDriverCandidateEvidence, PostgresDriverCandidateEvidenceError, evaluate_postgres_driver_candidate, @@ -11,6 +12,7 @@ FULL_CAPABILITIES = frozenset(REQUIRED_POSTGRES_DRIVER_CAPABILITIES) +FULL_PYTHON_VERSIONS = tuple(sorted(REQUIRED_POSTGRES_DRIVER_PYTHON_VERSIONS)) SOURCE_SHA = "a" * 40 ARTIFACT_SHA256 = "b" * 64 @@ -20,7 +22,7 @@ def _evidence(**overrides: object) -> PostgresDriverCandidateEvidence: "package_name": "candidate-driver", "package_version": "1.2.3", "license_spdx": "BSD-3-Clause", - "python_versions": ("3.12", "3.13", "3.14"), + "python_versions": FULL_PYTHON_VERSIONS, "source_commit_sha": SOURCE_SHA, "artifact_sha256": ARTIFACT_SHA256, "capabilities": FULL_CAPABILITIES, @@ -45,6 +47,12 @@ def test_candidate_contract_covers_issue_322_type_and_parameter_parity() -> None } <= REQUIRED_POSTGRES_DRIVER_CAPABILITIES +def test_candidate_contract_requires_every_repository_ci_python_version() -> None: + assert REQUIRED_POSTGRES_DRIVER_PYTHON_VERSIONS == frozenset( + {"3.10", "3.12", "3.14"} + ) + + @pytest.mark.parametrize( ("license_spdx", "expected_reason"), [ @@ -65,13 +73,19 @@ def test_candidate_fails_closed_when_license_is_not_explicitly_permissive( assert expected_reason in decision.reasons -def test_candidate_requires_explicit_python_314_support_evidence() -> None: +@pytest.mark.parametrize("missing_version", ["3.10", "3.12", "3.14"]) +def test_candidate_requires_every_repository_ci_python_version( + missing_version: str, +) -> None: + candidate_versions = tuple( + version for version in FULL_PYTHON_VERSIONS if version != missing_version + ) decision = evaluate_postgres_driver_candidate( - _evidence(python_versions=("3.12", "3.13")) + _evidence(python_versions=candidate_versions) ) assert decision.eligible_for_parity_validation is False - assert decision.reasons == ("python_3_14_not_evidenced",) + assert decision.reasons == (f"missing_python_version:{missing_version}",) def test_candidate_reports_every_missing_runtime_capability_deterministically() -> None: From 8b4772de01d4eaabcc2706621320da18683c27d1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 22:33:11 +0900 Subject: [PATCH 055/338] fix(postgres): require full supported Python parity --- pg_llm_batch/postgres_driver_candidate.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index 818a5f49..9118d118 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -35,6 +35,9 @@ ) """Capabilities a replacement driver must evidence before parity validation.""" +REQUIRED_POSTGRES_DRIVER_PYTHON_VERSIONS = frozenset({"3.10", "3.12", "3.14"}) +"""Repository CI Python minors a replacement driver must evidence explicitly.""" + _APPROVED_PERMISSIVE_LICENSES = frozenset( { "Apache-2.0", @@ -207,16 +210,22 @@ def evaluate_postgres_driver_candidate( The decision first revalidates one exact package-owned snapshot, then fails closed when the SPDX identifier is not in the repository's explicitly - reviewed permissive set, Python 3.14 support is not evidenced, or any runtime - capability required by the migration port is absent. Reasons are deterministic - so CI and acquisition diligence can compare exact evidence. + reviewed permissive set, any repository-required Python runtime is not + evidenced, or any runtime capability required by the migration port is absent. + Reasons are deterministic so CI and acquisition diligence can compare exact + evidence. """ snapshot = _validated_candidate_snapshot(evidence) reasons: list[str] = [] if snapshot.license_spdx not in _APPROVED_PERMISSIVE_LICENSES: reasons.append("license_not_approved") - if "3.14" not in snapshot.python_versions: - reasons.append("python_3_14_not_evidenced") + missing_python_versions = REQUIRED_POSTGRES_DRIVER_PYTHON_VERSIONS - set( + snapshot.python_versions + ) + reasons.extend( + f"missing_python_version:{version}" + for version in sorted(missing_python_versions) + ) missing_capabilities = REQUIRED_POSTGRES_DRIVER_CAPABILITIES - snapshot.capabilities reasons.extend( f"missing_capability:{capability}" From b545718b884e167a1a6de0513cbcde434d7d1c1e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 22:36:58 +0900 Subject: [PATCH 056/338] test(postgres): decouple schema and payload driver boundary --- tests/test_db.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/test_db.py b/tests/test_db.py index 57130e3a..87e51b0a 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -70,6 +70,21 @@ def test_apply_schema_executes_packaged_file(monkeypatch, tmp_path): assert driver.commits == 1 +def test_apply_schema_uses_injected_driver_without_psycopg(monkeypatch, tmp_path): + """Schema bootstrap must migrate through the driver port before manifest swap.""" + driver = _Psycopg() + monkeypatch.setattr(db, "psycopg", None) + schema = tmp_path / "schema.sql" + schema.write_text("CREATE TABLE snake_case_name (id int);", encoding="utf-8") + monkeypatch.setattr(db, "SCHEMA_PATH", schema) + + db.apply_schema("postgresql://x", postgres_driver=driver) + + assert driver.connections == ["postgresql://x"] + assert driver.executions == [("CREATE TABLE snake_case_name (id int);", None)] + assert driver.commits == 1 + + def test_apply_schema_refuses_caller_selected_sql(monkeypatch, tmp_path): """Caller-controlled local files must not acquire arbitrary SQL authority.""" driver = _Psycopg() @@ -99,6 +114,24 @@ def test_load_virtual_payload_preserves_canonical_jsonl(monkeypatch, stored, exp assert driver.executions[0][1] == ("file-1",) +def test_load_virtual_payload_uses_injected_driver_without_psycopg(monkeypatch): + """Virtual payload reads must not require Psycopg once a driver port is injected.""" + stored = {"text": '{"id":1}\n', "line_count": 1} + driver = _Psycopg((stored,)) + monkeypatch.setattr(db, "psycopg", None) + + assert ( + db.load_virtual_payload( + "postgresql://x", + "file-1", + postgres_driver=driver, + ) + == '{"id":1}\n' + ) + assert driver.connections == ["postgresql://x"] + assert driver.executions[0][1] == ("file-1",) + + def test_load_virtual_payload_returns_none_when_missing(monkeypatch): monkeypatch.setattr(db, "psycopg", _Psycopg(None)) assert db.load_virtual_payload("postgresql://x", "missing") is None From 6607b7a05a1e2a55505a03f332a6c4a8ae0f0572 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 22:42:30 +0900 Subject: [PATCH 057/338] feat(postgres): decouple schema and payload driver boundary --- pg_llm_batch/db.py | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/pg_llm_batch/db.py b/pg_llm_batch/db.py index 56467760..b476192f 100644 --- a/pg_llm_batch/db.py +++ b/pg_llm_batch/db.py @@ -19,6 +19,7 @@ from typing import Any, Dict, Optional from .exceptions import ValidationError +from .postgres_driver_port import PostgresDriverPort try: # pragma: no cover - optional dependency import psycopg # type: ignore @@ -96,20 +97,44 @@ def _require_psycopg() -> None: raise RuntimeError("psycopg is required for database access") -def apply_schema(dsn: str) -> None: - """Apply the package-owned idempotent schema to one PostgreSQL database.""" +def _connect_database( + dsn: str, + postgres_driver: PostgresDriverPort | None, +) -> Any: + """Open one connection through an injected migration driver when supplied. + + The default remains Psycopg until a permissively licensed adapter has passed + the repository's parity and release gates. Injected candidates can therefore + exercise package SQL without making the current runtime dependency an + unavoidable prerequisite for every persistence consumer. + """ + if postgres_driver is not None: + return postgres_driver.connect(dsn) _require_psycopg() + return psycopg.connect(dsn) + + +def apply_schema( + dsn: str, + *, + postgres_driver: PostgresDriverPort | None = None, +) -> None: + """Apply the package-owned schema through the selected PostgreSQL driver.""" sql = SCHEMA_PATH.read_text(encoding="utf-8") - with psycopg.connect(dsn) as conn: + with _connect_database(dsn, postgres_driver) as conn: with conn.cursor() as cur: cur.execute(sql) conn.commit() -def load_virtual_payload(dsn: str, file_id: str) -> Optional[str]: - """Load one canonical package-owned JSONL payload or fail closed.""" - _require_psycopg() - with psycopg.connect(dsn) as conn: +def load_virtual_payload( + dsn: str, + file_id: str, + *, + postgres_driver: PostgresDriverPort | None = None, +) -> Optional[str]: + """Load canonical package JSONL through the selected PostgreSQL driver.""" + with _connect_database(dsn, postgres_driver) as conn: with conn.cursor() as cur: cur.execute( "SELECT content FROM llm_batch_file_payloads WHERE file_id = %s", @@ -850,4 +875,4 @@ def get_model_metadata(dsn: Optional[str], model_id: str) -> Optional[Dict[str, } except Exception as exc: # pragma: no cover - defensive logger.debug("model metadata lookup failed for %s: %s", model_id, exc) - return None + return None \ No newline at end of file From b07c110cd237fca845afd2ebef61ba97637bff69 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 22:44:24 +0900 Subject: [PATCH 058/338] test(postgres): decouple model metadata driver boundary --- tests/test_db.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_db.py b/tests/test_db.py index 87e51b0a..02f7419d 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -156,6 +156,22 @@ def test_model_metadata_normalizes_mode_and_handles_absence(monkeypatch): assert db.get_model_metadata("postgresql://x", "") is None +def test_model_metadata_uses_injected_driver_without_psycopg(monkeypatch): + """Tokenizer metadata lookup must migrate through the same driver boundary.""" + driver = _Psycopg((" CHAT ", "o200k_base")) + monkeypatch.setattr(db, "psycopg", None) + + assert db.get_model_metadata( + "postgresql://x", + "gpt-4o", + postgres_driver=driver, + ) == { + "mode": "chat", + "tokenizer_model": "o200k_base", + } + assert driver.connections == ["postgresql://x"] + + def test_model_metadata_driver_failure_is_nonfatal(monkeypatch, caplog): monkeypatch.setattr(db, "psycopg", _Psycopg(error=OSError("database down"))) with caplog.at_level("DEBUG"): From 81fff937054df5dd9fdaf024a6c63fe1ec0f3866 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 22:46:33 +0900 Subject: [PATCH 059/338] feat(postgres): decouple model metadata driver boundary --- pg_llm_batch/db.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/pg_llm_batch/db.py b/pg_llm_batch/db.py index b476192f..3fe9827d 100644 --- a/pg_llm_batch/db.py +++ b/pg_llm_batch/db.py @@ -839,21 +839,31 @@ def get_remote_batch_state( ) -def get_model_metadata(dsn: Optional[str], model_id: str) -> Optional[Dict[str, Any]]: - """Fetch model mode and tokenizer metadata for a model identifier. +def get_model_metadata( + dsn: Optional[str], + model_id: str, + *, + postgres_driver: PostgresDriverPort | None = None, +) -> Optional[Dict[str, Any]]: + """Fetch model metadata through the selected PostgreSQL driver boundary. Args: dsn: Optional PostgreSQL connection string. model_id: Provider model identifier to resolve. + postgres_driver: Optional migration driver retained only for database I/O. Returns: A dictionary containing normalized ``mode`` and ``tokenizer_model`` when found, otherwise ``None``. """ - if not dsn or psycopg is None or not model_id: + if ( + not dsn + or not model_id + or (postgres_driver is None and psycopg is None) + ): return None try: - with psycopg.connect(dsn) as conn: + with _connect_database(dsn, postgres_driver) as conn: with conn.cursor() as cur: cur.execute( """ From fb7280c9de0621ead47c039f71f12bc1f97584d9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 22:48:43 +0900 Subject: [PATCH 060/338] test(postgres): decouple durable lifecycle driver boundary --- .../test_postgres_driver_remote_lifecycle.py | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 tests/test_postgres_driver_remote_lifecycle.py diff --git a/tests/test_postgres_driver_remote_lifecycle.py b/tests/test_postgres_driver_remote_lifecycle.py new file mode 100644 index 00000000..bb8317bc --- /dev/null +++ b/tests/test_postgres_driver_remote_lifecycle.py @@ -0,0 +1,177 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Driver-port regressions for durable remote batch lifecycle persistence.""" + +from __future__ import annotations + +from datetime import datetime, timezone +from typing import Any + +import pytest + +from pg_llm_batch import db + + +class _Cursor: + """Expose the cursor behavior required by the lifecycle migration seam.""" + + def __init__(self, driver: _Driver) -> None: + self.driver = driver + + def __enter__(self) -> _Cursor: + return self + + def __exit__(self, *_exc: Any) -> None: + return None + + def execute(self, query: str, params: object | None = None) -> _Cursor: + self.driver.executions.append((query, params)) + return self + + def fetchone(self) -> tuple[object, ...] | None: + if not self.driver.rows: + return None + return self.driver.rows.pop(0) + + def row_count(self) -> int: + return self.driver.affected_rows + + +class _Connection: + """Retain one fake transaction and expose commit evidence.""" + + def __init__(self, driver: _Driver) -> None: + self.driver = driver + + def __enter__(self) -> _Connection: + return self + + def __exit__(self, *_exc: Any) -> None: + return None + + def cursor(self) -> _Cursor: + return _Cursor(self.driver) + + def commit(self) -> None: + self.driver.commits += 1 + + +class _Driver: + """Minimal driver-shaped test double that contains no Psycopg type.""" + + def __init__( + self, + *, + rows: list[tuple[object, ...]] | None = None, + affected_rows: int = 1, + ) -> None: + self.rows = list(rows or []) + self.affected_rows = affected_rows + self.executions: list[tuple[str, object | None]] = [] + self.connections: list[str] = [] + self.commits = 0 + + def connect(self, dsn: str) -> _Connection: + self.connections.append(dsn) + return _Connection(self) + + +def _persisted_remote_batch_row( + observed: datetime, +) -> tuple[object, ...]: + """Return one exact persisted lifecycle row used by stale-write regressions.""" + return ( + "standalone", + "primary", + "batch-1", + 1, + None, + "/v1/responses", + "in_progress", + None, + None, + 2, + 1, + 0, + {}, + observed, + observed, + None, + observed, + ) + + +def test_observation_order_reservation_uses_injected_driver_without_psycopg( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Global lifecycle ordering must remain usable after the Psycopg graph is removed.""" + driver = _Driver(rows=[(41,)]) + monkeypatch.setattr(db, "psycopg", None) + + order = db.reserve_remote_batch_observation_order( + "postgresql://x", + postgres_driver=driver, + ) + + assert order == 41 + assert driver.connections == ["postgresql://x"] + assert driver.executions == [ + ("SELECT nextval('llm_remote_batch_observation_sequence')", None) + ] + + +def test_stale_lifecycle_write_reads_persisted_state_through_injected_driver( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Port row-count semantics must preserve the stale-write recovery contract.""" + observed = datetime(2026, 9, 2, 12, 0, tzinfo=timezone.utc) + driver = _Driver( + rows=[_persisted_remote_batch_row(observed)], + affected_rows=0, + ) + monkeypatch.setattr(db, "psycopg", None) + + snapshot = db.persist_remote_batch_state( + "postgresql://x", + "primary", + { + "id": "batch-1", + "endpoint": "/v1/responses", + "status": "in_progress", + "request_counts": {"total": 2, "completed": 2, "failed": 0}, + }, + observation_order=2, + observed_at=observed, + postgres_driver=driver, + ) + + assert snapshot["observation_order"] == 1 + assert snapshot["completed_requests"] == 1 + assert any( + "SELECT tenant_scope" in query + for query, _params in driver.executions + ) + assert driver.commits == 1 + + +def test_remote_lifecycle_read_uses_injected_driver_without_psycopg( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Tenant-scoped reads must not reacquire the legacy driver implicitly.""" + observed = datetime(2026, 9, 2, 12, 0, tzinfo=timezone.utc) + driver = _Driver(rows=[_persisted_remote_batch_row(observed)]) + monkeypatch.setattr(db, "psycopg", None) + + snapshot = db.get_remote_batch_state( + "postgresql://x", + "primary", + "batch-1", + postgres_driver=driver, + ) + + assert snapshot is not None + assert snapshot["tenant_scope"] == "standalone" + assert snapshot["observation_order"] == 1 + assert driver.executions[0] == ( + "SELECT set_config('pg_llm_batch.tenant_scope', %s, true)", + ("standalone",), + ) From e2cae6ce4f067a5e218499743274d3723066553d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 22:53:55 +0900 Subject: [PATCH 061/338] feat(postgres): decouple durable lifecycle driver boundary --- pg_llm_batch/db.py | 49 +++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/pg_llm_batch/db.py b/pg_llm_batch/db.py index 3fe9827d..455f0b44 100644 --- a/pg_llm_batch/db.py +++ b/pg_llm_batch/db.py @@ -420,10 +420,13 @@ def normalize_provider_metadata(value: Any) -> Dict[str, Any]: return _provider_metadata(value)[0] -def reserve_remote_batch_observation_order(dsn: str) -> int: - """Reserve and return one positive database-owned lifecycle order.""" - _require_psycopg() - with psycopg.connect(dsn) as conn: +def reserve_remote_batch_observation_order( + dsn: str, + *, + postgres_driver: PostgresDriverPort | None = None, +) -> int: + """Reserve one positive database-owned lifecycle order through the driver port.""" + with _connect_database(dsn, postgres_driver) as conn: with conn.cursor() as cur: cur.execute("SELECT nextval('llm_remote_batch_observation_sequence')") row = cur.fetchone() @@ -447,6 +450,16 @@ def _set_transaction_tenant_scope(cursor: Any, tenant_scope: str) -> None: ) +def _cursor_row_count( + cursor: Any, + postgres_driver: PostgresDriverPort | None, +) -> int | None: + """Read affected-row evidence without leaking a candidate driver's raw cursor API.""" + if postgres_driver is not None: + return cursor.row_count() + return getattr(cursor, "rowcount", None) + + def _normalize_remote_batch_snapshot( tenant_scope: str, endpoint_alias: str, @@ -550,8 +563,9 @@ def _persist_remote_batch_state( observation_order: int, *, observed_at: Optional[datetime] = None, + postgres_driver: PostgresDriverPort | None = None, ) -> Dict[str, Any]: - """Persist one validated tenant-qualified lifecycle projection.""" + """Persist one validated tenant lifecycle projection through the driver port.""" snapshot, metadata_json = _normalize_remote_batch_snapshot( tenant_scope, endpoint_alias, @@ -676,12 +690,11 @@ def _persist_remote_batch_state( terminal_at, observed, ) - _require_psycopg() - with psycopg.connect(dsn) as conn: + with _connect_database(dsn, postgres_driver) as conn: with conn.cursor() as cur: _set_transaction_tenant_scope(cur, snapshot["tenant_scope"]) cur.execute(sql, params) - if getattr(cur, "rowcount", None) == 0: + if _cursor_row_count(cur, postgres_driver) == 0: cur.execute( """ SELECT tenant_scope, @@ -732,8 +745,9 @@ def persist_remote_batch_state( observation_order: int, *, observed_at: Optional[datetime] = None, + postgres_driver: PostgresDriverPort | None = None, ) -> Dict[str, Any]: - """Persist one standalone projection without changing its return shape.""" + """Persist one standalone projection through the selected PostgreSQL driver.""" snapshot = _persist_remote_batch_state( dsn, DEFAULT_TENANT_SCOPE, @@ -741,6 +755,7 @@ def persist_remote_batch_state( provider_batch, observation_order, observed_at=observed_at, + postgres_driver=postgres_driver, ) snapshot.pop("tenant_scope", None) snapshot.pop("total_requests_known", None) @@ -755,8 +770,9 @@ def persist_tenant_remote_batch_state( observation_order: int, *, observed_at: Optional[datetime] = None, + postgres_driver: PostgresDriverPort | None = None, ) -> Dict[str, Any]: - """Persist one lifecycle projection for an explicit trusted tenant scope.""" + """Persist one trusted-tenant lifecycle projection through the driver port.""" snapshot = _persist_remote_batch_state( dsn, tenant_scope, @@ -764,6 +780,7 @@ def persist_tenant_remote_batch_state( provider_batch, observation_order, observed_at=observed_at, + postgres_driver=postgres_driver, ) snapshot.pop("total_requests_known", None) return snapshot @@ -774,8 +791,10 @@ def get_tenant_remote_batch_state( tenant_scope: str, endpoint_alias: str, remote_batch_id: str, + *, + postgres_driver: PostgresDriverPort | None = None, ) -> Optional[Dict[str, Any]]: - """Return one lifecycle projection visible to a validated tenant scope.""" + """Return one tenant-visible lifecycle projection through the driver port.""" normalized_tenant_scope = validate_tenant_scope(tenant_scope) normalized_alias = validate_endpoint_alias(endpoint_alias) normalized_remote_batch_id = validate_remote_resource_id( @@ -805,8 +824,7 @@ def get_tenant_remote_batch_state( AND endpoint_alias = %s AND remote_batch_id = %s """ - _require_psycopg() - with psycopg.connect(dsn) as conn: + with _connect_database(dsn, postgres_driver) as conn: with conn.cursor() as cur: _set_transaction_tenant_scope(cur, normalized_tenant_scope) cur.execute( @@ -829,13 +847,16 @@ def get_remote_batch_state( dsn: str, endpoint_alias: str, remote_batch_id: str, + *, + postgres_driver: PostgresDriverPort | None = None, ) -> Optional[Dict[str, Any]]: - """Return one lifecycle projection from the standalone tenant scope.""" + """Return one standalone lifecycle projection through the driver port.""" return get_tenant_remote_batch_state( dsn, DEFAULT_TENANT_SCOPE, endpoint_alias, remote_batch_id, + postgres_driver=postgres_driver, ) From 7bebc7b0e19a83dcb3d3dfad299b5f27bf9c3cae Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 23:04:42 +0900 Subject: [PATCH 062/338] test(postgres): reject Unicode format controls in candidate identity --- tests/test_postgres_driver_candidate.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_postgres_driver_candidate.py b/tests/test_postgres_driver_candidate.py index 730bfead..faa4fe7d 100644 --- a/tests/test_postgres_driver_candidate.py +++ b/tests/test_postgres_driver_candidate.py @@ -206,6 +206,21 @@ def test_candidate_rejects_embedded_whitespace_or_control_identity_evidence( _evidence(package_name=identity_value) +@pytest.mark.parametrize( + "identity_value", + [ + "candidate\u200bdriver", + "candidate\u202edriver", + "candidate\u2066driver", + ], +) +def test_candidate_rejects_unicode_format_controls_in_identity_evidence( + identity_value: str, +) -> None: + with pytest.raises(PostgresDriverCandidateEvidenceError): + _evidence(package_name=identity_value) + + @pytest.mark.parametrize("field_name", ["package_name", "package_version", "license_spdx"]) def test_candidate_rejects_unbounded_identity_evidence(field_name: str) -> None: with pytest.raises(PostgresDriverCandidateEvidenceError): From b0ae7c47b767ea6d213cf2d946fddd3831fb932e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 23:05:31 +0900 Subject: [PATCH 063/338] fix(postgres): reject format-control candidate identities --- pg_llm_batch/postgres_driver_candidate.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index 9118d118..0799e095 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -12,6 +12,7 @@ from dataclasses import dataclass import re +import unicodedata REQUIRED_POSTGRES_DRIVER_CAPABILITIES = frozenset( @@ -67,10 +68,10 @@ def _validate_identity_text(label: str, value: object) -> None: """Require one finite, exact package-identity token without normalization. Package name, version, and SPDX evidence participate in an acquisition - decision and can arrive from untrusted package metadata. Rejecting whitespace - and control characters keeps one evidence value from becoming multiple visual - or line-oriented identities, while the UTF-8 byte ceiling bounds malformed - metadata without inventing a package-manager-specific grammar. + decision and can arrive from untrusted package metadata. Rejecting whitespace, + controls, and Unicode format characters keeps one evidence value from becoming + multiple visual or line-oriented identities, while the UTF-8 byte ceiling + bounds malformed metadata without inventing a package-manager-specific grammar. """ if type(value) is not str or not value: raise PostgresDriverCandidateEvidenceError( @@ -82,6 +83,7 @@ def _validate_identity_text(label: str, value: object) -> None: character.isspace() or ord(character) < 32 or ord(character) == 127 + or unicodedata.category(character) == "Cf" for character in value ) ): From 53bffc425859a2848b05ca4b450c1906c5e94907 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 23:08:58 +0900 Subject: [PATCH 064/338] test(postgres): define token counter driver-port seam --- tests/test_token_counter_driver_port.py | 131 ++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 tests/test_token_counter_driver_port.py diff --git a/tests/test_token_counter_driver_port.py b/tests/test_token_counter_driver_port.py new file mode 100644 index 00000000..cf7bc191 --- /dev/null +++ b/tests/test_token_counter_driver_port.py @@ -0,0 +1,131 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Driver-port regressions for PostgreSQL token-counting migration.""" + +from __future__ import annotations + +from typing import Any + +import pytest + +import pg_llm_batch.token_counter as token_counter_module +from pg_llm_batch.token_counter import TokenCounter + + +class _UndefinedFunctionError(RuntimeError): + """Represent one driver-classified undefined PostgreSQL function.""" + + +class _Cursor: + """Return deterministic pg_tiktoken probe and count rows.""" + + def __init__(self, driver: _Driver) -> None: + self.driver = driver + + def __enter__(self) -> _Cursor: + return self + + def __exit__(self, *_exc: Any) -> None: + return None + + def execute(self, query: str, params: object | None = None) -> _Cursor: + self.driver.executions.append((query, params)) + if "tiktoken_count" in query and "to_regprocedure" not in query: + if self.driver.fail_primary_count: + self.driver.fail_primary_count = False + raise _UndefinedFunctionError("undefined function") + self.driver.rows.append((7,)) + elif "tiktoken_encode" in query and "to_regprocedure" not in query: + self.driver.rows.append((9,)) + return self + + def fetchone(self) -> tuple[object, ...] | None: + if not self.driver.rows: + return None + return self.driver.rows.pop(0) + + +class _Connection: + """Expose the connection lifecycle required by TokenCounter.""" + + def __init__(self, driver: _Driver) -> None: + self.driver = driver + self.closed = False + self.autocommit_values: list[bool] = [] + + def cursor(self) -> _Cursor: + return _Cursor(self.driver) + + def set_autocommit(self, enabled: bool) -> None: + self.autocommit_values.append(enabled) + + def is_closed(self) -> bool: + return self.closed + + def close(self) -> None: + self.closed = True + + +class _Driver: + """Minimal Psycopg-free driver implementing the token-counting port surface.""" + + def __init__(self, *, fail_primary_count: bool = False) -> None: + self.fail_primary_count = fail_primary_count + self.executions: list[tuple[str, object | None]] = [] + self.rows: list[tuple[object, ...]] = [(True, True, True)] + self.connections: list[_Connection] = [] + self.dsn_values: list[str] = [] + + def connect( + self, + dsn: str, + *, + connect_timeout_seconds: int | None = None, + ) -> _Connection: + assert connect_timeout_seconds is None + self.dsn_values.append(dsn) + connection = _Connection(self) + self.connections.append(connection) + return connection + + def is_undefined_function(self, error: BaseException) -> bool: + return isinstance(error, _UndefinedFunctionError) + + +def test_token_counter_uses_injected_driver_without_psycopg( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A replacement candidate must exercise pg_tiktoken without Psycopg authority.""" + driver = _Driver() + monkeypatch.setattr(token_counter_module, "psycopg", None) + metadata_calls: list[tuple[str, str, object]] = [] + + def _metadata(dsn: str, model: str, *, postgres_driver: object = None) -> dict[str, str]: + metadata_calls.append((dsn, model, postgres_driver)) + return {"tokenizer_model": "o200k_base"} + + monkeypatch.setattr(token_counter_module, "get_model_metadata", _metadata) + + counter = TokenCounter("postgresql://x", postgres_driver=driver) + + assert counter.count_tokens("hello", "model-a") == 7 + assert driver.dsn_values == ["postgresql://x"] + assert driver.connections[0].autocommit_values == [True] + assert metadata_calls == [("postgresql://x", "model-a", driver)] + + +def test_token_counter_uses_driver_error_classification_for_encode_fallback( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Undefined-function fallback must not depend on a Psycopg exception class.""" + driver = _Driver(fail_primary_count=True) + monkeypatch.setattr(token_counter_module, "psycopg", None) + monkeypatch.setattr( + token_counter_module, + "get_model_metadata", + lambda _dsn, _model, *, postgres_driver=None: {"tokenizer_model": "o200k_base"}, + ) + + counter = TokenCounter("postgresql://x", postgres_driver=driver) + + assert counter.count_tokens("hello", "model-a") == 9 + assert any("tiktoken_encode" in query for query, _params in driver.executions) From 48badbead9e97df154b9c13893035036afe2b73b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 23:10:15 +0900 Subject: [PATCH 065/338] feat(postgres): route token counting through driver port --- pg_llm_batch/token_counter.py | 52 +++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/pg_llm_batch/token_counter.py b/pg_llm_batch/token_counter.py index e31b3d65..c7b32d27 100644 --- a/pg_llm_batch/token_counter.py +++ b/pg_llm_batch/token_counter.py @@ -21,6 +21,7 @@ from .db import get_model_metadata from .exceptions import TokenLimitExceededError, ValidationError from .models import BatchRequest +from .postgres_driver_port import PostgresDriverPort logger = logging.getLogger(__name__) @@ -57,8 +58,9 @@ def __init__( *, config: Optional[Any] = None, buffer_percentage: Optional[int] = None, + postgres_driver: PostgresDriverPort | None = None, ) -> None: - """Initialize PostgreSQL token counting and configured batch limits.""" + """Initialize token counting with an optional PostgreSQL migration driver.""" if not postgres_dsn: raise ValidationError( field="postgres_dsn", @@ -67,7 +69,8 @@ def __init__( ) self.postgres_dsn = postgres_dsn self.config = config - self._pg_conn: Optional["psycopg.Connection"] = None + self._postgres_driver = postgres_driver + self._pg_conn: Optional[Any] = None self._pg_available: bool = False self._encoder_cache: Dict[str, _EncoderInfo] = {} @@ -119,7 +122,7 @@ def __init__( ), ) - if psycopg is not None: + if self._postgres_driver is not None or psycopg is not None: self._pg_available = self._ensure_pg_tiktoken() @staticmethod @@ -284,7 +287,7 @@ def _resolve_config_value(self, category: str, key: str, default: Any) -> Any: def _ensure_pg_tiktoken(self) -> bool: """Verify the pre-provisioned pg_tiktoken extension and functions read-only.""" - if psycopg is None: + if self._postgres_driver is None and psycopg is None: return False try: conn = self._get_pg_conn() @@ -307,17 +310,26 @@ def _ensure_pg_tiktoken(self) -> bool: self.close() return False - def _get_pg_conn(self) -> "psycopg.Connection": - """Return a cached autocommit PostgreSQL connection, reconnecting if closed.""" + def _get_pg_conn(self) -> Any: + """Return a cached autocommit connection through the selected driver boundary.""" + if self._pg_conn is not None: + if self._postgres_driver is not None: + if not self._pg_conn.is_closed(): + return self._pg_conn + elif not self._pg_conn.closed: + return self._pg_conn + if self._postgres_driver is not None: + self._pg_conn = self._postgres_driver.connect(self.postgres_dsn) + self._pg_conn.set_autocommit(True) + return self._pg_conn assert psycopg is not None - if self._pg_conn is None or self._pg_conn.closed: - self._pg_conn = psycopg.connect(self.postgres_dsn) - self._pg_conn.autocommit = True + self._pg_conn = psycopg.connect(self.postgres_dsn) + self._pg_conn.autocommit = True return self._pg_conn def _count_tokens_postgres(self, text: str, model: str) -> int: - """Count tokens for text via pg_tiktoken, falling back to tiktoken_encode.""" - if psycopg is None: + """Count tokens via pg_tiktoken while preserving driver error classification.""" + if self._postgres_driver is None and psycopg is None: raise RuntimeError("PostgreSQL integration is unavailable") conn = self._get_pg_conn() tiktoken_name = self.get_encoder(model).tokenizer_name @@ -327,7 +339,12 @@ def _count_tokens_postgres(self, text: str, model: str) -> int: row = cur.fetchone() if row and row[0] is not None: return int(row[0]) - except UndefinedFunction: + except Exception as error: + if self._postgres_driver is not None: + if not self._postgres_driver.is_undefined_function(error): + raise + elif not isinstance(error, UndefinedFunction): + raise cur.execute( "SELECT COUNT(*) FROM tiktoken_encode(%s, %s)", (tiktoken_name, text), @@ -340,7 +357,14 @@ def _count_tokens_postgres(self, text: str, model: str) -> int: def _get_tokenizer_from_db(self, model: str) -> Optional[str]: """Return the tokenizer model recorded in model metadata, or None if unset.""" - metadata = get_model_metadata(self.postgres_dsn, model) + if self._postgres_driver is None: + metadata = get_model_metadata(self.postgres_dsn, model) + else: + metadata = get_model_metadata( + self.postgres_dsn, + model, + postgres_driver=self._postgres_driver, + ) if metadata and metadata.get("tokenizer_model"): return str(metadata["tokenizer_model"]) return None @@ -458,4 +482,4 @@ def to_jsonl(self) -> str: for _, line, _ in self.entries: buffer.write(line) buffer.write("\n") - return buffer.getvalue() \ No newline at end of file + return buffer.getvalue() From 942b7eba4ae2f9883fe3bed8cd2ba431e3f4d1c7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 23:11:13 +0900 Subject: [PATCH 066/338] test(postgres): distinguish candidate token-count errors --- tests/test_token_counter_driver_port.py | 37 +++++++++++++++++++++---- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/tests/test_token_counter_driver_port.py b/tests/test_token_counter_driver_port.py index cf7bc191..04e9aabb 100644 --- a/tests/test_token_counter_driver_port.py +++ b/tests/test_token_counter_driver_port.py @@ -15,6 +15,10 @@ class _UndefinedFunctionError(RuntimeError): """Represent one driver-classified undefined PostgreSQL function.""" +class _OtherDriverError(RuntimeError): + """Represent a database failure that must not disable pg_tiktoken availability.""" + + class _Cursor: """Return deterministic pg_tiktoken probe and count rows.""" @@ -30,9 +34,10 @@ def __exit__(self, *_exc: Any) -> None: def execute(self, query: str, params: object | None = None) -> _Cursor: self.driver.executions.append((query, params)) if "tiktoken_count" in query and "to_regprocedure" not in query: - if self.driver.fail_primary_count: - self.driver.fail_primary_count = False - raise _UndefinedFunctionError("undefined function") + if self.driver.primary_error is not None: + error = self.driver.primary_error + self.driver.primary_error = None + raise error self.driver.rows.append((7,)) elif "tiktoken_encode" in query and "to_regprocedure" not in query: self.driver.rows.append((9,)) @@ -68,8 +73,8 @@ def close(self) -> None: class _Driver: """Minimal Psycopg-free driver implementing the token-counting port surface.""" - def __init__(self, *, fail_primary_count: bool = False) -> None: - self.fail_primary_count = fail_primary_count + def __init__(self, *, primary_error: BaseException | None = None) -> None: + self.primary_error = primary_error self.executions: list[tuple[str, object | None]] = [] self.rows: list[tuple[object, ...]] = [(True, True, True)] self.connections: list[_Connection] = [] @@ -117,7 +122,7 @@ def test_token_counter_uses_driver_error_classification_for_encode_fallback( monkeypatch: pytest.MonkeyPatch, ) -> None: """Undefined-function fallback must not depend on a Psycopg exception class.""" - driver = _Driver(fail_primary_count=True) + driver = _Driver(primary_error=_UndefinedFunctionError("undefined function")) monkeypatch.setattr(token_counter_module, "psycopg", None) monkeypatch.setattr( token_counter_module, @@ -129,3 +134,23 @@ def test_token_counter_uses_driver_error_classification_for_encode_fallback( assert counter.count_tokens("hello", "model-a") == 9 assert any("tiktoken_encode" in query for query, _params in driver.executions) + + +def test_non_undefined_driver_error_does_not_disable_token_counting( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A transient candidate-driver failure must not masquerade as missing pg_tiktoken.""" + driver = _Driver(primary_error=_OtherDriverError("temporary database failure")) + monkeypatch.setattr(token_counter_module, "psycopg", None) + monkeypatch.setattr( + token_counter_module, + "get_model_metadata", + lambda _dsn, _model, *, postgres_driver=None: {"tokenizer_model": "o200k_base"}, + ) + + counter = TokenCounter("postgresql://x", postgres_driver=driver) + + with pytest.raises(RuntimeError, match="Token counting requires pg_tiktoken"): + counter.count_tokens("first", "model-a") + assert counter.count_tokens("second", "model-a") == 7 + assert not any("tiktoken_encode" in query for query, _params in driver.executions) From 8b8c7fc2204ae4f0fde5406a75b50ef3603731e0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 23:13:54 +0900 Subject: [PATCH 067/338] fix(postgres): preserve token counter on transient driver errors --- pg_llm_batch/token_counter.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/pg_llm_batch/token_counter.py b/pg_llm_batch/token_counter.py index c7b32d27..82bab05e 100644 --- a/pg_llm_batch/token_counter.py +++ b/pg_llm_batch/token_counter.py @@ -168,11 +168,12 @@ def count_tokens(self, text: str, model: str) -> int: if self._pg_available: try: return self._count_tokens_postgres(text, model) - except UndefinedFunction: - self._pg_available = False - logger.warning("pg_tiktoken extension/functions unavailable") - except Exception: # pragma: no cover - runtime DB variance - logger.debug("PostgreSQL token counting failed") + except Exception as error: # pragma: no cover - runtime DB variance + if self._is_undefined_function(error): + self._pg_available = False + logger.warning("pg_tiktoken extension/functions unavailable") + else: + logger.debug("PostgreSQL token counting failed") raise RuntimeError( "Token counting requires pg_tiktoken. Enable the extension and pass a " "valid DSN." @@ -327,6 +328,12 @@ def _get_pg_conn(self) -> Any: self._pg_conn.autocommit = True return self._pg_conn + def _is_undefined_function(self, error: BaseException) -> bool: + """Classify undefined-function failures through the selected driver boundary.""" + if self._postgres_driver is not None: + return self._postgres_driver.is_undefined_function(error) + return isinstance(error, UndefinedFunction) + def _count_tokens_postgres(self, text: str, model: str) -> int: """Count tokens via pg_tiktoken while preserving driver error classification.""" if self._postgres_driver is None and psycopg is None: @@ -340,10 +347,7 @@ def _count_tokens_postgres(self, text: str, model: str) -> int: if row and row[0] is not None: return int(row[0]) except Exception as error: - if self._postgres_driver is not None: - if not self._postgres_driver.is_undefined_function(error): - raise - elif not isinstance(error, UndefinedFunction): + if not self._is_undefined_function(error): raise cur.execute( "SELECT COUNT(*) FROM tiktoken_encode(%s, %s)", From 27240e446f5164e9569d21d71c4bee9af2703f8c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 23:16:32 +0900 Subject: [PATCH 068/338] test(postgres): define orchestrator driver-port seam --- tests/test_orchestrator_driver_port.py | 217 +++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 tests/test_orchestrator_driver_port.py diff --git a/tests/test_orchestrator_driver_port.py b/tests/test_orchestrator_driver_port.py new file mode 100644 index 00000000..3eb4295b --- /dev/null +++ b/tests/test_orchestrator_driver_port.py @@ -0,0 +1,217 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Driver-port regressions for PostgreSQL batch assembly and persistence.""" + +from __future__ import annotations + +from typing import Any + +import pytest + +from pg_llm_batch import db +from pg_llm_batch import orchestrator as orchestrator_module +from pg_llm_batch.orchestrator import PostgresBatchOrchestrator + + +class _Cursor: + """Expose deterministic rows for orchestrator driver-port acceptance.""" + + def __init__(self, driver: _Driver) -> None: + self.driver = driver + self.next_one: tuple[object, ...] | None = None + self.next_all: list[tuple[object, ...]] = [] + self._row_count = 0 + + def __enter__(self) -> _Cursor: + return self + + def __exit__(self, *_exc: Any) -> None: + return None + + def execute(self, query: str, params: object | None = None) -> _Cursor: + self.driver.executions.append((query, params)) + self._row_count = 0 + if "input_file_path" in query: + self.next_one = ("22222222-2222-2222-2222-222222222222",) + elif "FROM llm_requests" in query and "ORDER BY created_at" in query: + self.next_all = list(self.driver.request_rows) + elif "SELECT queue_uuid" in query: + self.next_one = ("33333333-3333-3333-3333-333333333333",) + elif "SELECT file_path" in query: + self.next_all = [] + elif "RETURNING file_uuid" in query: + self.next_one = ("44444444-4444-4444-4444-444444444444",) + elif "UPDATE llm_requests" in query: + assert isinstance(params, tuple) + self._row_count = len(params[2]) + return self + + def executemany(self, query: str, params_seq: object) -> _Cursor: + self.driver.many.append((query, list(params_seq))) # type: ignore[arg-type] + return self + + def fetchone(self) -> tuple[object, ...] | None: + return self.next_one + + def fetchall(self) -> list[tuple[object, ...]]: + return list(self.next_all) + + def row_count(self) -> int: + return self._row_count + + +class _Connection: + """Retain one fake transaction and explicit autocommit state.""" + + def __init__(self, driver: _Driver) -> None: + self.driver = driver + self.autocommit_values: list[bool] = [] + self.commits = 0 + self.closed = False + + def __enter__(self) -> _Connection: + return self + + def __exit__(self, *_exc: Any) -> None: + return None + + def cursor(self) -> _Cursor: + return _Cursor(self.driver) + + def set_autocommit(self, enabled: bool) -> None: + self.autocommit_values.append(enabled) + + def commit(self) -> None: + self.commits += 1 + + def rollback(self) -> None: + return None + + def is_closed(self) -> bool: + return self.closed + + def close(self) -> None: + self.closed = True + + +class _Driver: + """Minimal Psycopg-free driver for the orchestrator's database boundary.""" + + def __init__(self) -> None: + self.executions: list[tuple[str, object | None]] = [] + self.many: list[tuple[str, list[object]]] = [] + self.connections: list[_Connection] = [] + self.request_rows: list[tuple[object, ...]] = [] + self.jsonb_values: list[object] = [] + + def connect( + self, + dsn: str, + *, + connect_timeout_seconds: int | None = None, + ) -> _Connection: + assert dsn == "postgresql://x" + assert connect_timeout_seconds is None + connection = _Connection(self) + self.connections.append(connection) + return connection + + def jsonb(self, value: object) -> object: + self.jsonb_values.append(value) + return ("jsonb", value) + + +def test_orchestrator_accepts_injected_driver_without_psycopg( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Batch lookup must remain usable after the Psycopg runtime is removed.""" + driver = _Driver() + monkeypatch.setattr(orchestrator_module, "psycopg", None) + + orchestrator = PostgresBatchOrchestrator( + "postgresql://x", + postgres_driver=driver, + ) + + assert orchestrator._resolve_batch_uuid("input.jsonl") == ( + "22222222-2222-2222-2222-222222222222" + ) + assert len(driver.connections) == 1 + + +def test_assemble_payloads_passes_driver_to_model_metadata( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Model metadata reads must not silently reacquire the legacy driver.""" + driver = _Driver() + monkeypatch.setattr(orchestrator_module, "psycopg", None) + metadata_calls: list[tuple[str, str, object]] = [] + + def _metadata( + dsn: str, + model: str, + *, + postgres_driver: object = None, + ) -> dict[str, str]: + metadata_calls.append((dsn, model, postgres_driver)) + return {"mode": "embedding"} + + monkeypatch.setattr(db, "get_model_metadata", _metadata) + + class _Counter: + effective_limit = 100 + azure_max_records_per_file = 10 + azure_max_bytes_per_file = 10_000 + + def count_tokens(self, text: str, model: str) -> int: + return 1 if text else 0 + + orchestrator = PostgresBatchOrchestrator( + "postgresql://x", + postgres_driver=driver, + ) + payloads = orchestrator._assemble_payloads( + _Counter(), # type: ignore[arg-type] + [("req-1", "ignored", "content", "embed-model")], + ) + + assert metadata_calls == [("postgresql://x", "embed-model", driver)] + assert payloads[0]["record_count"] == 1 + + +def test_persist_payloads_uses_driver_jsonb_transaction_and_row_count( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Persistence must not leak Psycopg JSONB or rowcount semantics past the port.""" + driver = _Driver() + monkeypatch.setattr(orchestrator_module, "psycopg", None) + monkeypatch.setattr(orchestrator_module, "Jsonb", None) + orchestrator = PostgresBatchOrchestrator( + "postgresql://x", + postgres_driver=driver, + ) + + class _Counter: + azure_max_files_per_job = 1 + + result = orchestrator._persist_payloads( + [ + { + "part_index": 0, + "record_count": 1, + "total_tokens": 2, + "request_ids": ["aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"], + "lines": ['{"custom_id":"req-1"}'], + } + ], + "11111111-1111-1111-1111-111111111111", + _Counter(), # type: ignore[arg-type] + ) + + assert len(result["ready"]) == 1 + assert result["overflow"] == [] + assert driver.jsonb_values == [ + {"text": '{"custom_id":"req-1"}\n', "line_count": 1} + ] + assert driver.connections[0].autocommit_values == [False] + assert driver.connections[0].commits == 1 + assert len(driver.many) == 1 From 44e4dd2fd0be202cb2de4d63ada56bd9f7909cb7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 23:17:54 +0900 Subject: [PATCH 069/338] feat(postgres): route batch orchestration through driver port --- pg_llm_batch/orchestrator.py | 83 +++++++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 16 deletions(-) diff --git a/pg_llm_batch/orchestrator.py b/pg_llm_batch/orchestrator.py index 11f2aabd..bd89af4d 100644 --- a/pg_llm_batch/orchestrator.py +++ b/pg_llm_batch/orchestrator.py @@ -20,6 +20,7 @@ from . import db from .config import PostgresConfigStore from .exceptions import ValidationError +from .postgres_driver_port import PostgresDriverPort from .token_counter import BatchAccumulator, TokenCounter try: # pragma: no cover - optional dependency @@ -60,11 +61,45 @@ class BatchPayload: class PostgresBatchOrchestrator: """Assemble and persist JSONL batch payloads from queued requests.""" - def __init__(self, dsn: str) -> None: - """Initialize the orchestrator with an explicit PostgreSQL DSN.""" - if not dsn or psycopg is None: + def __init__( + self, + dsn: str, + *, + postgres_driver: PostgresDriverPort | None = None, + ) -> None: + """Initialize with an explicit DSN and optional migration driver.""" + if not dsn or (postgres_driver is None and psycopg is None): raise RuntimeError("A Postgres DSN and psycopg are required") self.dsn = dsn + self._postgres_driver = postgres_driver + + def _connect_database(self) -> Any: + """Open one orchestrator connection through the selected driver boundary.""" + if self._postgres_driver is not None: + return self._postgres_driver.connect(self.dsn) + assert psycopg is not None + return psycopg.connect(self.dsn) + + def _set_autocommit(self, connection: Any, enabled: bool) -> None: + """Set transaction mode without exposing a candidate driver's raw API.""" + if self._postgres_driver is not None: + connection.set_autocommit(enabled) + return + connection.autocommit = enabled + + def _adapt_jsonb(self, value: object) -> object: + """Adapt JSONB through the selected driver while preserving exact payload data.""" + if self._postgres_driver is not None: + return self._postgres_driver.jsonb(value) + if Jsonb is not None: + return Jsonb(value) + return json.dumps(value) + + def _cursor_row_count(self, cursor: Any) -> int | None: + """Read affected-row evidence through the selected cursor contract.""" + if self._postgres_driver is not None: + return cursor.row_count() + return getattr(cursor, "rowcount", None) def _resolve_batch_uuid(self, batch_key: str) -> Optional[str]: """Resolve an exact string batch UUID or input-file-path selector.""" @@ -79,7 +114,7 @@ def _resolve_batch_uuid(self, batch_key: str) -> Optional[str]: return batch_key except ValueError: pass - with psycopg.connect(self.dsn) as conn: + with self._connect_database() as conn: with conn.cursor() as cur: cur.execute( "SELECT batch_uuid FROM llm_batches " @@ -115,7 +150,7 @@ def prepare_batches( ), ) - with psycopg.connect(self.dsn) as conn: + with self._connect_database() as conn: with conn.cursor() as cur: cur.execute( """ @@ -130,9 +165,22 @@ def prepare_batches( ) rows: List[Tuple] = cur.fetchall() - config = PostgresConfigStore(self.dsn) + if self._postgres_driver is None: + config = PostgresConfigStore(self.dsn) + else: + config = PostgresConfigStore( + self.dsn, + postgres_driver=self._postgres_driver, + ) try: - counter = TokenCounter(self.dsn, config=config) + if self._postgres_driver is None: + counter = TokenCounter(self.dsn, config=config) + else: + counter = TokenCounter( + self.dsn, + config=config, + postgres_driver=self._postgres_driver, + ) try: if validated_token_limit is not None: counter.effective_limit = min( @@ -156,7 +204,14 @@ def _assemble_payloads( payloads: List[Dict[str, Any]] = [] for (request_uuid, system_prompt, user_prompt, model_name) in rows: - metadata = db.get_model_metadata(self.dsn, model_name) + if self._postgres_driver is None: + metadata = db.get_model_metadata(self.dsn, model_name) + else: + metadata = db.get_model_metadata( + self.dsn, + model_name, + postgres_driver=self._postgres_driver, + ) mode = str((metadata or {}).get("mode") or "").lower() system_for_tokens = system_prompt if mode != "embedding" else "" @@ -278,8 +333,8 @@ def _persist_payloads( immediate_limit = counter.azure_max_files_per_job lock_key = self._batch_lock_key(batch_uuid) - with psycopg.connect(self.dsn) as conn: - conn.autocommit = False + with self._connect_database() as conn: + self._set_autocommit(conn, False) with conn.cursor() as cur: cur.execute("SELECT pg_advisory_xact_lock(%s)", (lock_key,)) cur.execute( @@ -331,11 +386,7 @@ def _persist_payloads( request_ids = [str(item) for item in meta.get("request_ids", [])] content = "\n".join(lines) + ("\n" if lines else "") payload_doc = {"text": content, "line_count": len(lines)} - adapted = ( - Jsonb(payload_doc) - if Jsonb is not None - else json.dumps(payload_doc) - ) + adapted = self._adapt_jsonb(payload_doc) cur.execute( """ INSERT INTO llm_batch_file_payloads (file_id, content) @@ -399,7 +450,7 @@ def _persist_payloads( """, (file_uuid, batch_uuid, request_ids), ) - if cur.rowcount != len(request_ids): + if self._cursor_row_count(cur) != len(request_ids): raise ValidationError( field="request_ids", value=request_ids, From ba2a74d258f483a8c7c9c45bec33b01e57051dc2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 23:19:30 +0900 Subject: [PATCH 070/338] test(postgres): cover orchestrator driver propagation --- tests/test_orchestrator_driver_port.py | 73 ++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/tests/test_orchestrator_driver_port.py b/tests/test_orchestrator_driver_port.py index 3eb4295b..477bdfd0 100644 --- a/tests/test_orchestrator_driver_port.py +++ b/tests/test_orchestrator_driver_port.py @@ -178,6 +178,79 @@ def count_tokens(self, text: str, model: str) -> int: assert payloads[0]["record_count"] == 1 +def test_prepare_batches_propagates_driver_to_store_and_token_counter( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Preparation must keep one explicit driver boundary across its DB helpers.""" + driver = _Driver() + monkeypatch.setattr(orchestrator_module, "psycopg", None) + calls: list[tuple[str, object]] = [] + + class _Config: + def close(self) -> None: + calls.append(("config_close", self)) + + config = _Config() + + def _config_factory( + dsn: str, + *, + postgres_driver: object = None, + ) -> _Config: + assert dsn == "postgresql://x" + calls.append(("config_driver", postgres_driver)) + return config + + class _Counter: + effective_limit = 100 + azure_max_files_per_job = 1 + + def __init__( + self, + dsn: str, + *, + config: object, + postgres_driver: object = None, + ) -> None: + assert dsn == "postgresql://x" + assert config is globals_config + calls.append(("counter_driver", postgres_driver)) + + def close(self) -> None: + calls.append(("counter_close", self)) + + globals_config = config + monkeypatch.setattr(orchestrator_module, "PostgresConfigStore", _config_factory) + monkeypatch.setattr(orchestrator_module, "TokenCounter", _Counter) + + orchestrator = PostgresBatchOrchestrator( + "postgresql://x", + postgres_driver=driver, + ) + monkeypatch.setattr( + orchestrator, + "_resolve_batch_uuid", + lambda _batch_key: "11111111-1111-1111-1111-111111111111", + ) + monkeypatch.setattr(orchestrator, "_assemble_payloads", lambda _counter, _rows: []) + monkeypatch.setattr( + orchestrator, + "_persist_payloads", + lambda _payloads, batch_key, _counter: { + "ready": [batch_key], + "overflow": [], + }, + ) + + result = orchestrator.prepare_batches(batch_uuid="source.jsonl") + + assert result["ready"] == ["11111111-1111-1111-1111-111111111111"] + assert ("config_driver", driver) in calls + assert ("counter_driver", driver) in calls + assert any("FROM llm_requests" in query for query, _params in driver.executions) + assert sum(name.endswith("_close") for name, _value in calls) == 2 + + def test_persist_payloads_uses_driver_jsonb_transaction_and_row_count( monkeypatch: pytest.MonkeyPatch, ) -> None: From 788cec7a31dd6eadfb71feac34413a4d6a0bb1f3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 01:04:28 +0900 Subject: [PATCH 071/338] test(cli): require driver-neutral DSN parsing --- tests/test_cli_postgres_driver_port.py | 90 ++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 tests/test_cli_postgres_driver_port.py diff --git a/tests/test_cli_postgres_driver_port.py b/tests/test_cli_postgres_driver_port.py new file mode 100644 index 00000000..45d55d07 --- /dev/null +++ b/tests/test_cli_postgres_driver_port.py @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: Apache-2.0 +"""CLI regressions for the PostgreSQL driver-migration boundary.""" + +from __future__ import annotations + +import ast +from pathlib import Path + +import pytest + +from pg_llm_batch import cli + + +class _CandidateConninfoError(Exception): + """Candidate-driver parse failure used to exercise CLI normalization.""" + + +class _CandidateDriver: + """Minimal candidate parser double for the CLI migration seam.""" + + def __init__(self) -> None: + self.parsed_values: list[str] = [] + + def parse_conninfo(self, value: str) -> dict[str, str]: + """Parse bounded fixtures without delegating to Psycopg.""" + self.parsed_values.append(value) + if value == "invalid": + raise _CandidateConninfoError + if value == "credential-bearing": + return {"host": "db.internal", "password": "redacted-fixture"} + return {"host": "db.internal", "dbname": "batch"} + + def is_invalid_conninfo(self, error: BaseException) -> bool: + """Classify only this double's explicit conninfo failure.""" + return type(error) is _CandidateConninfoError + + +def test_cli_parser_uses_injected_postgres_driver_for_dsn() -> None: + """Candidate validation must not route DSN parsing back through Psycopg.""" + driver = _CandidateDriver() + + parser = cli.build_parser(postgres_driver=driver) + args = parser.parse_args(["health", "--dsn", "candidate-selector"]) + + assert args.dsn == "candidate-selector" + assert driver.parsed_values == ["candidate-selector"] + + +def test_cli_parser_normalizes_candidate_conninfo_failure() -> None: + """Driver-specific parse errors remain bounded argparse diagnostics.""" + driver = _CandidateDriver() + parser = cli.build_parser(postgres_driver=driver) + + with pytest.raises(SystemExit) as exc_info: + parser.parse_args(["health", "--dsn", "invalid"]) + + assert exc_info.value.code == 2 + + +def test_cli_parser_rejects_candidate_reported_credential_fields() -> None: + """Driver migration must preserve the credential-free argv contract.""" + driver = _CandidateDriver() + parser = cli.build_parser(postgres_driver=driver) + + with pytest.raises(SystemExit) as exc_info: + parser.parse_args(["health", "--dsn", "credential-bearing"]) + + assert exc_info.value.code == 2 + + +def test_cli_module_has_no_eager_psycopg_import() -> None: + """Importing the CLI must not itself require the retained legacy driver.""" + source = Path(cli.__file__).read_text(encoding="utf-8") + tree = ast.parse(source) + + direct_psycopg_imports = [ + node + for node in ast.walk(tree) + if ( + isinstance(node, ast.Import) + and any(alias.name == "psycopg" or alias.name.startswith("psycopg.") for alias in node.names) + ) + or ( + isinstance(node, ast.ImportFrom) + and node.module is not None + and (node.module == "psycopg" or node.module.startswith("psycopg.")) + ) + ] + + assert direct_psycopg_imports == [] From c6580c0d6a1b98b69f0b4dfacd32d0c122f5eda0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 01:07:11 +0900 Subject: [PATCH 072/338] fix(cli): route DSN parsing through driver port --- pg_llm_batch/cli.py | 74 ++++++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/pg_llm_batch/cli.py b/pg_llm_batch/cli.py index 8d09cded..bea8847a 100644 --- a/pg_llm_batch/cli.py +++ b/pg_llm_batch/cli.py @@ -33,17 +33,16 @@ import sys import warnings from contextlib import ExitStack +from functools import partial from typing import List, Optional -from psycopg import ProgrammingError -from psycopg.conninfo import conninfo_to_dict - from . import db from .batch_api_client import BatchAPIClient, config_credentials_provider from .bootstrap import resolve_dsn, resolve_secret_key from .config import PostgresConfigStore, SecretStore from .exceptions import ConfigError, PgLlmBatchError from .health import check_health, serve_healthz +from .postgres_driver_port import PostgresDriverPort from .token_counter import TokenCounter MAX_SECRET_INPUT_CHARACTERS = 65_536 @@ -73,28 +72,46 @@ def error(self, message: str) -> None: super().error(redacted_message) -def _validate_cli_dsn(value: str) -> str: - """Accept valid libpq selectors while refusing credential-bearing argv data.""" +def _default_postgres_driver() -> PostgresDriverPort: + """Load the retained Psycopg adapter only when a CLI parse needs the default.""" + from .psycopg_driver_adapter import PsycopgDriverAdapter + + return PsycopgDriverAdapter() + + +def _validate_cli_dsn( + value: str, + *, + postgres_driver: PostgresDriverPort | None = None, +) -> str: + """Accept valid PostgreSQL selectors without concrete-driver coupling.""" + driver = postgres_driver or _default_postgres_driver() try: - parameters = conninfo_to_dict(value) - except ProgrammingError: - raise argparse.ArgumentTypeError( - "Postgres DSN must be valid libpq connection information" - ) from None + parameters = driver.parse_conninfo(value) + except Exception as exc: + if driver.is_invalid_conninfo(exc): + raise argparse.ArgumentTypeError( + "Postgres DSN must be valid connection information" + ) from None + raise if CLI_DSN_SENSITIVE_PARAMETERS.intersection(parameters): raise argparse.ArgumentTypeError( "Credential-bearing Postgres DSNs are not accepted in --dsn; " - "use libpq secret mechanisms outside process argv" + "use PostgreSQL secret mechanisms outside process argv" ) return value -def _add_common(parser: argparse.ArgumentParser) -> None: +def _add_common( + parser: argparse.ArgumentParser, + *, + postgres_driver: PostgresDriverPort | None = None, +) -> None: """Add the shared credential-free ``--dsn`` selector to a subcommand parser.""" parser.add_argument( "--dsn", default=None, - type=_validate_cli_dsn, + type=partial(_validate_cli_dsn, postgres_driver=postgres_driver), help=( "Credential-free Postgres selector " "(else PG_LLM_BATCH_DSN bootstrap env var)" @@ -186,8 +203,11 @@ def _read_token_input() -> str: raise ConfigError("Token input must be valid UTF-8") from None -def build_parser() -> argparse.ArgumentParser: - """Build the command-line parser and all supported subcommands.""" +def build_parser( + *, + postgres_driver: PostgresDriverPort | None = None, +) -> argparse.ArgumentParser: + """Build the command-line parser with an injectable PostgreSQL DSN parser.""" parser = _RedactingArgumentParser( prog="pg_llm_batch", description="Standalone Postgres LLM batch engine", @@ -195,31 +215,31 @@ def build_parser() -> argparse.ArgumentParser: sub = parser.add_subparsers(dest="command", required=True) p_init = sub.add_parser("init-db", help="Apply batch schema (idempotent)") - _add_common(p_init) + _add_common(p_init, postgres_driver=postgres_driver) p_cfg = sub.add_parser("config", help="Manage KV config and secrets") cfg_sub = p_cfg.add_subparsers(dest="config_command", required=True) p_set = cfg_sub.add_parser("set", help="Set a config value") - _add_common(p_set) + _add_common(p_set, postgres_driver=postgres_driver) p_set.add_argument("category") p_set.add_argument("key") p_set.add_argument("value") p_get = cfg_sub.add_parser("get", help="Get a config value") - _add_common(p_get) + _add_common(p_get, postgres_driver=postgres_driver) p_get.add_argument("category") p_get.add_argument("key") p_secret = cfg_sub.add_parser( "set-secret", help="Store a secret from a no-echo prompt or standard input", ) - _add_common(p_secret) + _add_common(p_secret, postgres_driver=postgres_driver) p_secret.add_argument("secret_key") p_count = sub.add_parser( "count-tokens", help="Count bounded UTF-8 stdin content without exposing it in argv", ) - _add_common(p_count) + _add_common(p_count, postgres_driver=postgres_driver) p_count.add_argument("--model", required=True) p_count.add_argument( "--stdin", @@ -229,38 +249,38 @@ def build_parser() -> argparse.ArgumentParser: ) p_submit = sub.add_parser("submit", help="Upload payload + create batch job") - _add_common(p_submit) + _add_common(p_submit, postgres_driver=postgres_driver) p_submit.add_argument("--endpoint", required=True, help="Endpoint alias") p_submit.add_argument("--file-path", required=True, help="memory://") p_submit.add_argument("--batch-endpoint", default="/v1/chat/completions") p_poll = sub.add_parser("poll", help="Poll a batch job status once") - _add_common(p_poll) + _add_common(p_poll, postgres_driver=postgres_driver) p_poll.add_argument("--endpoint", required=True) p_poll.add_argument("--batch-id", required=True) p_wait = sub.add_parser("wait", help="Wait for a terminal batch status") - _add_common(p_wait) + _add_common(p_wait, postgres_driver=postgres_driver) p_wait.add_argument("--endpoint", required=True) p_wait.add_argument("--batch-id", required=True) p_wait.add_argument("--poll-interval", type=float, default=5.0) p_wait.add_argument("--timeout", type=float, default=3600.0) p_retrieve = sub.add_parser("retrieve", help="Download batch results") - _add_common(p_retrieve) + _add_common(p_retrieve, postgres_driver=postgres_driver) p_retrieve.add_argument("--endpoint", required=True) p_retrieve.add_argument("--batch-id", required=True) p_cancel = sub.add_parser("cancel", help="Cancel a provider batch job") - _add_common(p_cancel) + _add_common(p_cancel, postgres_driver=postgres_driver) p_cancel.add_argument("--endpoint", required=True) p_cancel.add_argument("--batch-id", required=True) p_health = sub.add_parser("health", help="Print readiness report") - _add_common(p_health) + _add_common(p_health, postgres_driver=postgres_driver) p_serve = sub.add_parser("serve-healthz", help="Serve GET /healthz") - _add_common(p_serve) + _add_common(p_serve, postgres_driver=postgres_driver) p_serve.add_argument("--host", default="127.0.0.1") p_serve.add_argument("--port", type=int, default=8080) From 6eb18e7896e2ae183f8f97a2ff6519f285a8b736 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 01:10:52 +0900 Subject: [PATCH 073/338] test(cli): reject truthiness-based driver selection --- tests/test_cli_postgres_driver_port.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/test_cli_postgres_driver_port.py b/tests/test_cli_postgres_driver_port.py index 45d55d07..bcb44b49 100644 --- a/tests/test_cli_postgres_driver_port.py +++ b/tests/test_cli_postgres_driver_port.py @@ -21,6 +21,10 @@ class _CandidateDriver: def __init__(self) -> None: self.parsed_values: list[str] = [] + def __bool__(self) -> bool: + """Remain falsy so dependency selection cannot rely on object truthiness.""" + return False + def parse_conninfo(self, value: str) -> dict[str, str]: """Parse bounded fixtures without delegating to Psycopg.""" self.parsed_values.append(value) @@ -78,7 +82,10 @@ def test_cli_module_has_no_eager_psycopg_import() -> None: for node in ast.walk(tree) if ( isinstance(node, ast.Import) - and any(alias.name == "psycopg" or alias.name.startswith("psycopg.") for alias in node.names) + and any( + alias.name == "psycopg" or alias.name.startswith("psycopg.") + for alias in node.names + ) ) or ( isinstance(node, ast.ImportFrom) From 22428e5b4d7fd86531cc0f08921305d5a7167ff3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 01:13:02 +0900 Subject: [PATCH 074/338] fix(cli): select injected driver by identity --- pg_llm_batch/cli.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pg_llm_batch/cli.py b/pg_llm_batch/cli.py index bea8847a..0b292aae 100644 --- a/pg_llm_batch/cli.py +++ b/pg_llm_batch/cli.py @@ -85,7 +85,11 @@ def _validate_cli_dsn( postgres_driver: PostgresDriverPort | None = None, ) -> str: """Accept valid PostgreSQL selectors without concrete-driver coupling.""" - driver = postgres_driver or _default_postgres_driver() + driver = ( + postgres_driver + if postgres_driver is not None + else _default_postgres_driver() + ) try: parameters = driver.parse_conninfo(value) except Exception as exc: From 05cae1511eb935c08d66f739a5eb05892345a588 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 01:39:09 +0900 Subject: [PATCH 075/338] test(postgres): require vulnerability evidence for driver candidates --- tests/test_postgres_driver_candidate.py | 41 +++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/test_postgres_driver_candidate.py b/tests/test_postgres_driver_candidate.py index faa4fe7d..c442dfcc 100644 --- a/tests/test_postgres_driver_candidate.py +++ b/tests/test_postgres_driver_candidate.py @@ -15,6 +15,7 @@ FULL_PYTHON_VERSIONS = tuple(sorted(REQUIRED_POSTGRES_DRIVER_PYTHON_VERSIONS)) SOURCE_SHA = "a" * 40 ARTIFACT_SHA256 = "b" * 64 +VULNERABILITY_REPORT_SHA256 = "c" * 64 def _evidence(**overrides: object) -> PostgresDriverCandidateEvidence: @@ -25,6 +26,8 @@ def _evidence(**overrides: object) -> PostgresDriverCandidateEvidence: "python_versions": FULL_PYTHON_VERSIONS, "source_commit_sha": SOURCE_SHA, "artifact_sha256": ARTIFACT_SHA256, + "vulnerability_report_sha256": VULNERABILITY_REPORT_SHA256, + "known_vulnerability_ids": (), "capabilities": FULL_CAPABILITIES, } values.update(overrides) @@ -53,6 +56,16 @@ def test_candidate_contract_requires_every_repository_ci_python_version() -> Non ) +def test_candidate_rejects_known_vulnerabilities_before_parity_validation() -> None: + decision = evaluate_postgres_driver_candidate( + _evidence(known_vulnerability_ids=("CVE-2025-61385",)) + ) + + assert decision.eligible_for_parity_validation is False + assert decision.production_approved is False + assert decision.reasons == ("known_vulnerability:CVE-2025-61385",) + + @pytest.mark.parametrize( ("license_spdx", "expected_reason"), [ @@ -119,6 +132,10 @@ def test_candidate_reports_every_missing_runtime_capability_deterministically() ("artifact_sha256", "b" * 63), ("artifact_sha256", "z" * 64), ("artifact_sha256", 64), + ("vulnerability_report_sha256", "c" * 63), + ("vulnerability_report_sha256", "z" * 64), + ("vulnerability_report_sha256", 64), + ("known_vulnerability_ids", ["CVE-2025-61385"]), ("capabilities", frozenset()), ("capabilities", {"parameterized_sql"}), ], @@ -146,10 +163,34 @@ def test_candidate_rejects_unknown_capability_names() -> None: _evidence(capabilities=FULL_CAPABILITIES | {"model_routing"}) +@pytest.mark.parametrize( + "vulnerability_id", + [ + "", + "CVE 2025 61385", + "CVE-2025-61385\nGHSA-wq2g-r956-j8cc", + "x" * 129, + ], +) +def test_candidate_rejects_malformed_vulnerability_identifiers( + vulnerability_id: str, +) -> None: + with pytest.raises(PostgresDriverCandidateEvidenceError, match="vulnerability"): + _evidence(known_vulnerability_ids=(vulnerability_id,)) + + +def test_candidate_rejects_duplicate_vulnerability_identifiers() -> None: + with pytest.raises(PostgresDriverCandidateEvidenceError, match="vulnerability"): + _evidence( + known_vulnerability_ids=("CVE-2025-61385", "CVE-2025-61385") + ) + + @pytest.mark.parametrize( ("field_name", "mutated_value"), [ ("python_versions", ["3.14"]), + ("known_vulnerability_ids", ["CVE-2025-61385"]), ("capabilities", set(FULL_CAPABILITIES)), ], ) From dbe27278804c37fef0de0ef96550dfcdbd77b6cb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 01:40:44 +0900 Subject: [PATCH 076/338] feat(postgres): bind vulnerability evidence to driver admission --- pg_llm_batch/postgres_driver_candidate.py | 76 ++++++++++++++++++----- 1 file changed, 61 insertions(+), 15 deletions(-) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index 0799e095..803d1ce7 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -3,9 +3,10 @@ The repository must replace its current LGPL-family Psycopg runtime dependency without turning an unverified alternative into production authority. This module revalidates a bounded candidate snapshot and decides only whether a candidate -has enough permissive-license, Python-version, artifact-identity, and capability -evidence to enter parity validation. Production approval remains a later gate -that requires a concrete adapter plus PostgreSQL/RLS/recovery/package evidence. +has enough permissive-license, Python-version, artifact-identity, vulnerability, +and capability evidence to enter parity validation. Production approval remains +a later gate that requires a concrete adapter plus PostgreSQL/RLS/recovery/package +evidence. """ from __future__ import annotations @@ -53,6 +54,7 @@ _MINOR_PYTHON_VERSION = re.compile(r"^[1-9][0-9]*\.[0-9]+$") _SOURCE_COMMIT_SHA = re.compile(r"^[0-9a-f]{40}$") _ARTIFACT_SHA256 = re.compile(r"^[0-9a-f]{64}$") +_VULNERABILITY_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$") class PostgresDriverCandidateEvidenceError(ValueError): @@ -92,16 +94,45 @@ def _validate_identity_text(label: str, value: object) -> None: ) +def _validate_vulnerability_ids(values: object) -> tuple[str, ...]: + """Validate immutable advisory identifiers without normalizing scan evidence. + + The tuple may be empty only when the bound vulnerability report found no + known advisories. Identifiers remain opaque CVE/GHSA/vendor tokens; the + evaluator constrains their representation rather than inventing a particular + advisory namespace or treating display text as authority. + """ + if type(values) is not tuple: + raise PostgresDriverCandidateEvidenceError( + "PostgreSQL driver vulnerability evidence is invalid" + ) + if any( + type(value) is not str or _VULNERABILITY_ID.fullmatch(value) is None + for value in values + ): + raise PostgresDriverCandidateEvidenceError( + "PostgreSQL driver vulnerability evidence is invalid" + ) + if len(set(values)) != len(values): + raise PostgresDriverCandidateEvidenceError( + "PostgreSQL driver vulnerability evidence is invalid" + ) + return values + + @dataclass(frozen=True, slots=True) class PostgresDriverCandidateEvidence: """Describe one validated PostgreSQL-driver package candidate. - ``source_commit_sha`` identifies the reviewed source revision and - ``artifact_sha256`` identifies the exact distributable under evaluation. - ``python_versions`` and ``capabilities`` must contain explicit evidence rather - than inferred support from a nearby release or similar database driver. - Evaluation revalidates a fresh snapshot because Python's frozen dataclasses do - not make ``object.__setattr__`` an authority boundary. + ``source_commit_sha`` identifies the reviewed source revision, + ``artifact_sha256`` identifies the exact distributable, and + ``vulnerability_report_sha256`` binds the exact vulnerability evidence used + for the decision. ``known_vulnerability_ids`` records unresolved advisories + from that report. ``python_versions`` and ``capabilities`` must contain + explicit evidence rather than inferred support from a nearby release or + similar database driver. Evaluation revalidates a fresh snapshot because + Python's frozen dataclasses do not make ``object.__setattr__`` an authority + boundary. """ package_name: str @@ -110,6 +141,8 @@ class PostgresDriverCandidateEvidence: python_versions: tuple[str, ...] source_commit_sha: str artifact_sha256: str + vulnerability_report_sha256: str + known_vulnerability_ids: tuple[str, ...] capabilities: frozenset[str] def __post_init__(self) -> None: @@ -149,6 +182,14 @@ def __post_init__(self) -> None: raise PostgresDriverCandidateEvidenceError( "PostgreSQL driver artifact digest evidence is invalid" ) + if ( + type(self.vulnerability_report_sha256) is not str + or _ARTIFACT_SHA256.fullmatch(self.vulnerability_report_sha256) is None + ): + raise PostgresDriverCandidateEvidenceError( + "PostgreSQL driver vulnerability report evidence is invalid" + ) + _validate_vulnerability_ids(self.known_vulnerability_ids) if type(self.capabilities) is not frozenset or not self.capabilities: raise PostgresDriverCandidateEvidenceError( "PostgreSQL driver capability evidence is invalid" @@ -197,6 +238,8 @@ def _validated_candidate_snapshot( python_versions=evidence.python_versions, source_commit_sha=evidence.source_commit_sha, artifact_sha256=evidence.artifact_sha256, + vulnerability_report_sha256=evidence.vulnerability_report_sha256, + known_vulnerability_ids=evidence.known_vulnerability_ids, capabilities=evidence.capabilities, ) except AttributeError: @@ -211,14 +254,17 @@ def evaluate_postgres_driver_candidate( """Evaluate one candidate without promoting it to a production dependency. The decision first revalidates one exact package-owned snapshot, then fails - closed when the SPDX identifier is not in the repository's explicitly - reviewed permissive set, any repository-required Python runtime is not - evidenced, or any runtime capability required by the migration port is absent. - Reasons are deterministic so CI and acquisition diligence can compare exact - evidence. + closed when the bound vulnerability report contains a known advisory, the + SPDX identifier is not in the repository's explicitly reviewed permissive + set, any repository-required Python runtime is not evidenced, or any runtime + capability required by the migration port is absent. Reasons are + deterministic so CI and acquisition diligence can compare exact evidence. """ snapshot = _validated_candidate_snapshot(evidence) - reasons: list[str] = [] + reasons = [ + f"known_vulnerability:{vulnerability_id}" + for vulnerability_id in sorted(snapshot.known_vulnerability_ids) + ] if snapshot.license_spdx not in _APPROVED_PERMISSIVE_LICENSES: reasons.append("license_not_approved") missing_python_versions = REQUIRED_POSTGRES_DRIVER_PYTHON_VERSIONS - set( From cd46cedf592c547e07254ad4039cdacf22cbc4bb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 01:48:04 +0900 Subject: [PATCH 077/338] test(postgres): preserve DSN selector parity in candidate gate --- tests/test_postgres_driver_candidate.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_postgres_driver_candidate.py b/tests/test_postgres_driver_candidate.py index c442dfcc..951e64f8 100644 --- a/tests/test_postgres_driver_candidate.py +++ b/tests/test_postgres_driver_candidate.py @@ -50,6 +50,14 @@ def test_candidate_contract_covers_issue_322_type_and_parameter_parity() -> None } <= REQUIRED_POSTGRES_DRIVER_CAPABILITIES +def test_candidate_contract_preserves_each_supported_dsn_selector_family() -> None: + assert { + "conninfo_keyword_parse_render", + "conninfo_service_selector", + "conninfo_uri_parse_render", + } <= REQUIRED_POSTGRES_DRIVER_CAPABILITIES + + def test_candidate_contract_requires_every_repository_ci_python_version() -> None: assert REQUIRED_POSTGRES_DRIVER_PYTHON_VERSIONS == frozenset( {"3.10", "3.12", "3.14"} From 0d6296daf5a531f8154c2bb8a48c5d01584a994d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 01:49:54 +0900 Subject: [PATCH 078/338] feat(postgres): require explicit DSN selector parity --- pg_llm_batch/postgres_driver_candidate.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index 803d1ce7..12b6626c 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -21,7 +21,9 @@ "autocommit_state", "connection_closed_state", "connection_context", - "conninfo_parse_render", + "conninfo_keyword_parse_render", + "conninfo_service_selector", + "conninfo_uri_parse_render", "cursor_context", "finite_connect_timeout", "invalid_conninfo_classification", @@ -257,8 +259,11 @@ def evaluate_postgres_driver_candidate( closed when the bound vulnerability report contains a known advisory, the SPDX identifier is not in the repository's explicitly reviewed permissive set, any repository-required Python runtime is not evidenced, or any runtime - capability required by the migration port is absent. Reasons are - deterministic so CI and acquisition diligence can compare exact evidence. + capability required by the migration port is absent. DSN evidence remains + split across URI, keyword, and service selectors so a driver cannot claim + generic conninfo support while silently dropping a shipped selector family. + Reasons are deterministic so CI and acquisition diligence can compare exact + evidence. """ snapshot = _validated_candidate_snapshot(evidence) reasons = [ From 9734dc6ecde02bd2f28e064fe6fdec6a97095b9a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 02:35:45 +0900 Subject: [PATCH 079/338] test(postgres): require full supported Python parity --- tests/test_postgres_driver_candidate.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_postgres_driver_candidate.py b/tests/test_postgres_driver_candidate.py index 951e64f8..048102f9 100644 --- a/tests/test_postgres_driver_candidate.py +++ b/tests/test_postgres_driver_candidate.py @@ -60,7 +60,7 @@ def test_candidate_contract_preserves_each_supported_dsn_selector_family() -> No def test_candidate_contract_requires_every_repository_ci_python_version() -> None: assert REQUIRED_POSTGRES_DRIVER_PYTHON_VERSIONS == frozenset( - {"3.10", "3.12", "3.14"} + {"3.10", "3.11", "3.12", "3.13", "3.14"} ) @@ -94,7 +94,9 @@ def test_candidate_fails_closed_when_license_is_not_explicitly_permissive( assert expected_reason in decision.reasons -@pytest.mark.parametrize("missing_version", ["3.10", "3.12", "3.14"]) +@pytest.mark.parametrize( + "missing_version", ["3.10", "3.11", "3.12", "3.13", "3.14"] +) def test_candidate_requires_every_repository_ci_python_version( missing_version: str, ) -> None: From 1197ccb8d5e4a96517c085249d85b00a77a3d46d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 02:36:38 +0900 Subject: [PATCH 080/338] fix(postgres): require full supported Python driver parity --- pg_llm_batch/postgres_driver_candidate.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index 12b6626c..824e7515 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -39,8 +39,10 @@ ) """Capabilities a replacement driver must evidence before parity validation.""" -REQUIRED_POSTGRES_DRIVER_PYTHON_VERSIONS = frozenset({"3.10", "3.12", "3.14"}) -"""Repository CI Python minors a replacement driver must evidence explicitly.""" +REQUIRED_POSTGRES_DRIVER_PYTHON_VERSIONS = frozenset( + {"3.10", "3.11", "3.12", "3.13", "3.14"} +) +"""Repository-supported Python minors a replacement driver must evidence explicitly.""" _APPROVED_PERMISSIVE_LICENSES = frozenset( { From 1517b015c0da7ef25b7ba0fd4865c318656decef Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 04:14:14 +0900 Subject: [PATCH 081/338] test(postgres): bound driver candidate evidence cardinality --- tests/test_postgres_driver_candidate.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_postgres_driver_candidate.py b/tests/test_postgres_driver_candidate.py index 048102f9..102ab7cd 100644 --- a/tests/test_postgres_driver_candidate.py +++ b/tests/test_postgres_driver_candidate.py @@ -196,6 +196,20 @@ def test_candidate_rejects_duplicate_vulnerability_identifiers() -> None: ) +def test_candidate_rejects_unbounded_python_version_evidence() -> None: + versions = FULL_PYTHON_VERSIONS + tuple(f"4.{minor}" for minor in range(28)) + + with pytest.raises(PostgresDriverCandidateEvidenceError, match="Python version"): + _evidence(python_versions=versions) + + +def test_candidate_rejects_unbounded_vulnerability_evidence() -> None: + vulnerability_ids = tuple(f"CVE-2099-{index:04d}" for index in range(257)) + + with pytest.raises(PostgresDriverCandidateEvidenceError, match="vulnerability"): + _evidence(known_vulnerability_ids=vulnerability_ids) + + @pytest.mark.parametrize( ("field_name", "mutated_value"), [ From 4a30a2234585dce2af9ad588eb44cc66712dff54 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 04:15:22 +0900 Subject: [PATCH 082/338] fix(postgres): bound driver candidate evidence cardinality --- pg_llm_batch/postgres_driver_candidate.py | 24 ++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index 824e7515..b90e305b 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -55,6 +55,8 @@ } ) _MAX_IDENTITY_EVIDENCE_BYTES = 256 +_MAX_PYTHON_VERSION_EVIDENCE_ITEMS = 32 +_MAX_VULNERABILITY_EVIDENCE_ITEMS = 256 _MINOR_PYTHON_VERSION = re.compile(r"^[1-9][0-9]*\.[0-9]+$") _SOURCE_COMMIT_SHA = re.compile(r"^[0-9a-f]{40}$") _ARTIFACT_SHA256 = re.compile(r"^[0-9a-f]{64}$") @@ -99,14 +101,18 @@ def _validate_identity_text(label: str, value: object) -> None: def _validate_vulnerability_ids(values: object) -> tuple[str, ...]: - """Validate immutable advisory identifiers without normalizing scan evidence. + """Validate bounded advisory identifiers without normalizing scan evidence. The tuple may be empty only when the bound vulnerability report found no known advisories. Identifiers remain opaque CVE/GHSA/vendor tokens; the - evaluator constrains their representation rather than inventing a particular - advisory namespace or treating display text as authority. + evaluator constrains their representation and cardinality rather than + inventing a namespace or letting untrusted scan metadata amplify evaluation + work and decision receipts without bound. """ - if type(values) is not tuple: + if ( + type(values) is not tuple + or len(values) > _MAX_VULNERABILITY_EVIDENCE_ITEMS + ): raise PostgresDriverCandidateEvidenceError( "PostgreSQL driver vulnerability evidence is invalid" ) @@ -157,7 +163,11 @@ def __post_init__(self) -> None: ("license", self.license_spdx), ): _validate_identity_text(label, value) - if type(self.python_versions) is not tuple or not self.python_versions: + if ( + type(self.python_versions) is not tuple + or not self.python_versions + or len(self.python_versions) > _MAX_PYTHON_VERSION_EVIDENCE_ITEMS + ): raise PostgresDriverCandidateEvidenceError( "PostgreSQL driver Python version evidence is invalid" ) @@ -198,6 +208,10 @@ def __post_init__(self) -> None: raise PostgresDriverCandidateEvidenceError( "PostgreSQL driver capability evidence is invalid" ) + if len(self.capabilities) > len(REQUIRED_POSTGRES_DRIVER_CAPABILITIES): + raise PostgresDriverCandidateEvidenceError( + "PostgreSQL driver capability evidence contains an unknown capability" + ) unknown_capabilities = self.capabilities - REQUIRED_POSTGRES_DRIVER_CAPABILITIES if unknown_capabilities: raise PostgresDriverCandidateEvidenceError( From 2fa8a188a2fee5c7f095c53fdfea0b2c6e4c376d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:13:53 +0900 Subject: [PATCH 083/338] test(postgres): expose connection context parity gap --- ...test_postgres_driver_connection_context_contract.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tests/test_postgres_driver_connection_context_contract.py diff --git a/tests/test_postgres_driver_connection_context_contract.py b/tests/test_postgres_driver_connection_context_contract.py new file mode 100644 index 00000000..e15c8e1e --- /dev/null +++ b/tests/test_postgres_driver_connection_context_contract.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from pg_llm_batch.postgres_driver_candidate import ( + REQUIRED_POSTGRES_DRIVER_CAPABILITIES, +) + + +def test_candidate_requires_transactional_connection_context_semantics() -> None: + """Reject drivers whose context manager closes without commit/rollback parity.""" + assert "connection_context_commit_rollback" in REQUIRED_POSTGRES_DRIVER_CAPABILITIES From 934b72b3df75bd02028fee2c6603676651aa2843 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:16:04 +0900 Subject: [PATCH 084/338] fix(postgres): require transactional context parity --- pg_llm_batch/postgres_driver_candidate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index b90e305b..f1119aeb 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -21,6 +21,7 @@ "autocommit_state", "connection_closed_state", "connection_context", + "connection_context_commit_rollback", "conninfo_keyword_parse_render", "conninfo_service_selector", "conninfo_uri_parse_render", From 31b4f2a4d862707a60820e7e7260ea2fc51f900f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:24:53 +0900 Subject: [PATCH 085/338] test(postgres): bind candidate license evidence digest --- tests/test_postgres_driver_candidate.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test_postgres_driver_candidate.py b/tests/test_postgres_driver_candidate.py index 102ab7cd..8c6dac5a 100644 --- a/tests/test_postgres_driver_candidate.py +++ b/tests/test_postgres_driver_candidate.py @@ -16,6 +16,7 @@ SOURCE_SHA = "a" * 40 ARTIFACT_SHA256 = "b" * 64 VULNERABILITY_REPORT_SHA256 = "c" * 64 +LICENSE_REPORT_SHA256 = "d" * 64 def _evidence(**overrides: object) -> PostgresDriverCandidateEvidence: @@ -42,6 +43,26 @@ def test_complete_permissive_candidate_is_eligible_only_for_parity_validation() assert decision.reasons == () +def test_candidate_requires_immutable_license_report_identity() -> None: + decision = evaluate_postgres_driver_candidate( + _evidence(license_report_sha256=LICENSE_REPORT_SHA256) + ) + + assert decision.eligible_for_parity_validation is True + assert decision.production_approved is False + + +@pytest.mark.parametrize( + "license_report_sha256", + ["d" * 63, "z" * 64, 64], +) +def test_candidate_rejects_malformed_license_report_identity( + license_report_sha256: object, +) -> None: + with pytest.raises(PostgresDriverCandidateEvidenceError, match="license report"): + _evidence(license_report_sha256=license_report_sha256) + + def test_candidate_contract_covers_issue_322_type_and_parameter_parity() -> None: assert { "result_row_semantics", From 6c15f03457444eaef93890ddef2d89f619d6bcbe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:28:51 +0900 Subject: [PATCH 086/338] test(postgres): require license report on all candidates --- tests/test_postgres_driver_candidate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_postgres_driver_candidate.py b/tests/test_postgres_driver_candidate.py index 8c6dac5a..2a07454d 100644 --- a/tests/test_postgres_driver_candidate.py +++ b/tests/test_postgres_driver_candidate.py @@ -24,6 +24,7 @@ def _evidence(**overrides: object) -> PostgresDriverCandidateEvidence: "package_name": "candidate-driver", "package_version": "1.2.3", "license_spdx": "BSD-3-Clause", + "license_report_sha256": LICENSE_REPORT_SHA256, "python_versions": FULL_PYTHON_VERSIONS, "source_commit_sha": SOURCE_SHA, "artifact_sha256": ARTIFACT_SHA256, From 223b5806a6729145b6011f0d8a26310d14bcce3d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:31:47 +0900 Subject: [PATCH 087/338] fix(postgres): bind candidate license evidence digest --- pg_llm_batch/postgres_driver_candidate.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index f1119aeb..617d065f 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -3,10 +3,10 @@ The repository must replace its current LGPL-family Psycopg runtime dependency without turning an unverified alternative into production authority. This module revalidates a bounded candidate snapshot and decides only whether a candidate -has enough permissive-license, Python-version, artifact-identity, vulnerability, -and capability evidence to enter parity validation. Production approval remains -a later gate that requires a concrete adapter plus PostgreSQL/RLS/recovery/package -evidence. +has enough permissive-license, immutable license/artifact identity, Python-version, +vulnerability, and capability evidence to enter parity validation. Production +approval remains a later gate that requires a concrete adapter plus +PostgreSQL/RLS/recovery/package evidence. """ from __future__ import annotations @@ -136,7 +136,8 @@ class PostgresDriverCandidateEvidence: """Describe one validated PostgreSQL-driver package candidate. ``source_commit_sha`` identifies the reviewed source revision, - ``artifact_sha256`` identifies the exact distributable, and + ``license_report_sha256`` binds the exact license evidence used for + ``license_spdx``, ``artifact_sha256`` identifies the exact distributable, and ``vulnerability_report_sha256`` binds the exact vulnerability evidence used for the decision. ``known_vulnerability_ids`` records unresolved advisories from that report. ``python_versions`` and ``capabilities`` must contain @@ -149,6 +150,7 @@ class PostgresDriverCandidateEvidence: package_name: str package_version: str license_spdx: str + license_report_sha256: str python_versions: tuple[str, ...] source_commit_sha: str artifact_sha256: str @@ -164,6 +166,13 @@ def __post_init__(self) -> None: ("license", self.license_spdx), ): _validate_identity_text(label, value) + if ( + type(self.license_report_sha256) is not str + or _ARTIFACT_SHA256.fullmatch(self.license_report_sha256) is None + ): + raise PostgresDriverCandidateEvidenceError( + "PostgreSQL driver license report evidence is invalid" + ) if ( type(self.python_versions) is not tuple or not self.python_versions @@ -254,6 +263,7 @@ def _validated_candidate_snapshot( package_name=evidence.package_name, package_version=evidence.package_version, license_spdx=evidence.license_spdx, + license_report_sha256=evidence.license_report_sha256, python_versions=evidence.python_versions, source_commit_sha=evidence.source_commit_sha, artifact_sha256=evidence.artifact_sha256, From cbf951e2589851b09c4abc91c3c3a73fb54cd9cc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:41:55 +0900 Subject: [PATCH 088/338] test(postgres): expose unbound parity evidence reports --- ...t_postgres_driver_candidate_report_provenance.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/test_postgres_driver_candidate_report_provenance.py diff --git a/tests/test_postgres_driver_candidate_report_provenance.py b/tests/test_postgres_driver_candidate_report_provenance.py new file mode 100644 index 00000000..076db0bd --- /dev/null +++ b/tests/test_postgres_driver_candidate_report_provenance.py @@ -0,0 +1,13 @@ +from __future__ import annotations + +from dataclasses import fields + +from pg_llm_batch.postgres_driver_candidate import PostgresDriverCandidateEvidence + + +def test_candidate_binds_python_and_capability_reports_to_immutable_digests() -> None: + """Parity claims need immutable report identities, not self-asserted value sets.""" + evidence_fields = {field.name for field in fields(PostgresDriverCandidateEvidence)} + + assert "python_report_sha256" in evidence_fields + assert "capability_report_sha256" in evidence_fields From 2e0b6fa5ef123b2e30d60a6b7523b7f621cb7294 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:43:17 +0900 Subject: [PATCH 089/338] test(postgres): require versioned candidate evidence schema --- tests/test_postgres_driver_candidate_report_provenance.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_postgres_driver_candidate_report_provenance.py b/tests/test_postgres_driver_candidate_report_provenance.py index 076db0bd..7b8143ce 100644 --- a/tests/test_postgres_driver_candidate_report_provenance.py +++ b/tests/test_postgres_driver_candidate_report_provenance.py @@ -5,9 +5,8 @@ from pg_llm_batch.postgres_driver_candidate import PostgresDriverCandidateEvidence -def test_candidate_binds_python_and_capability_reports_to_immutable_digests() -> None: - """Parity claims need immutable report identities, not self-asserted value sets.""" +def test_candidate_evidence_has_explicit_schema_version() -> None: + """Acquisition receipts need a versioned schema before their meaning can evolve.""" evidence_fields = {field.name for field in fields(PostgresDriverCandidateEvidence)} - assert "python_report_sha256" in evidence_fields - assert "capability_report_sha256" in evidence_fields + assert "evidence_schema_version" in evidence_fields From c19e7db5c5184f53276861de6014753adf74b55e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:44:09 +0900 Subject: [PATCH 090/338] fix(postgres): version candidate evidence receipts --- pg_llm_batch/postgres_driver_candidate.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index 617d065f..307fa1aa 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -45,6 +45,9 @@ ) """Repository-supported Python minors a replacement driver must evidence explicitly.""" +POSTGRES_DRIVER_CANDIDATE_EVIDENCE_SCHEMA_VERSION = "1" +"""Version of the candidate-evidence receipt interpreted by this evaluator.""" + _APPROVED_PERMISSIVE_LICENSES = frozenset( { "Apache-2.0", @@ -142,9 +145,10 @@ class PostgresDriverCandidateEvidence: for the decision. ``known_vulnerability_ids`` records unresolved advisories from that report. ``python_versions`` and ``capabilities`` must contain explicit evidence rather than inferred support from a nearby release or - similar database driver. Evaluation revalidates a fresh snapshot because - Python's frozen dataclasses do not make ``object.__setattr__`` an authority - boundary. + similar database driver. ``evidence_schema_version`` prevents a future + receipt shape from being silently interpreted under today's semantics. + Evaluation revalidates a fresh snapshot because Python's frozen dataclasses + do not make ``object.__setattr__`` an authority boundary. """ package_name: str @@ -157,6 +161,7 @@ class PostgresDriverCandidateEvidence: vulnerability_report_sha256: str known_vulnerability_ids: tuple[str, ...] capabilities: frozenset[str] + evidence_schema_version: str = POSTGRES_DRIVER_CANDIDATE_EVIDENCE_SCHEMA_VERSION def __post_init__(self) -> None: """Validate candidate evidence without normalizing ambiguous inputs.""" @@ -166,6 +171,10 @@ def __post_init__(self) -> None: ("license", self.license_spdx), ): _validate_identity_text(label, value) + if self.evidence_schema_version != POSTGRES_DRIVER_CANDIDATE_EVIDENCE_SCHEMA_VERSION: + raise PostgresDriverCandidateEvidenceError( + "PostgreSQL driver candidate evidence schema version is unsupported" + ) if ( type(self.license_report_sha256) is not str or _ARTIFACT_SHA256.fullmatch(self.license_report_sha256) is None @@ -270,6 +279,7 @@ def _validated_candidate_snapshot( vulnerability_report_sha256=evidence.vulnerability_report_sha256, known_vulnerability_ids=evidence.known_vulnerability_ids, capabilities=evidence.capabilities, + evidence_schema_version=evidence.evidence_schema_version, ) except AttributeError: raise PostgresDriverCandidateEvidenceError( From 9807f6d8a2a4d1041f26d2bb00fff35993d22483 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:45:11 +0900 Subject: [PATCH 091/338] test(postgres): reject candidate schema equality spoofing --- ...gres_driver_candidate_report_provenance.py | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/test_postgres_driver_candidate_report_provenance.py b/tests/test_postgres_driver_candidate_report_provenance.py index 7b8143ce..a6edb25c 100644 --- a/tests/test_postgres_driver_candidate_report_provenance.py +++ b/tests/test_postgres_driver_candidate_report_provenance.py @@ -2,7 +2,12 @@ from dataclasses import fields -from pg_llm_batch.postgres_driver_candidate import PostgresDriverCandidateEvidence +import pytest + +from pg_llm_batch.postgres_driver_candidate import ( + PostgresDriverCandidateEvidence, + PostgresDriverCandidateEvidenceError, +) def test_candidate_evidence_has_explicit_schema_version() -> None: @@ -10,3 +15,26 @@ def test_candidate_evidence_has_explicit_schema_version() -> None: evidence_fields = {field.name for field in fields(PostgresDriverCandidateEvidence)} assert "evidence_schema_version" in evidence_fields + + +def test_candidate_schema_version_rejects_equality_spoofing() -> None: + """Untrusted receipt objects cannot impersonate the current schema by equality.""" + + class PretendsToBeCurrent: + def __eq__(self, other: object) -> bool: + return True + + with pytest.raises(PostgresDriverCandidateEvidenceError, match="schema version"): + PostgresDriverCandidateEvidence( + package_name="candidate-driver", + package_version="1.2.3", + license_spdx="BSD-3-Clause", + license_report_sha256="d" * 64, + python_versions=("3.10", "3.11", "3.12", "3.13", "3.14"), + source_commit_sha="a" * 40, + artifact_sha256="b" * 64, + vulnerability_report_sha256="c" * 64, + known_vulnerability_ids=(), + capabilities=frozenset({"parameterized_sql"}), + evidence_schema_version=PretendsToBeCurrent(), # type: ignore[arg-type] + ) From b9e18ab90edc8f163ebda9c6a4f417b01e1e31e3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:46:24 +0900 Subject: [PATCH 092/338] fix(postgres): reject spoofed evidence schema versions --- pg_llm_batch/postgres_driver_candidate.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index 307fa1aa..0936a6b2 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -171,7 +171,11 @@ def __post_init__(self) -> None: ("license", self.license_spdx), ): _validate_identity_text(label, value) - if self.evidence_schema_version != POSTGRES_DRIVER_CANDIDATE_EVIDENCE_SCHEMA_VERSION: + if ( + type(self.evidence_schema_version) is not str + or self.evidence_schema_version + != POSTGRES_DRIVER_CANDIDATE_EVIDENCE_SCHEMA_VERSION + ): raise PostgresDriverCandidateEvidenceError( "PostgreSQL driver candidate evidence schema version is unsupported" ) From 5d1078ba56103996072f2764728d5c74afd2c03b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 10:08:19 +0900 Subject: [PATCH 093/338] test(postgres): reject non-PyPA candidate project names --- ..._postgres_driver_candidate_package_name.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/test_postgres_driver_candidate_package_name.py diff --git a/tests/test_postgres_driver_candidate_package_name.py b/tests/test_postgres_driver_candidate_package_name.py new file mode 100644 index 00000000..f78198b4 --- /dev/null +++ b/tests/test_postgres_driver_candidate_package_name.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +import pytest + +from pg_llm_batch.postgres_driver_candidate import ( + REQUIRED_POSTGRES_DRIVER_CAPABILITIES, + REQUIRED_POSTGRES_DRIVER_PYTHON_VERSIONS, + PostgresDriverCandidateEvidence, + PostgresDriverCandidateEvidenceError, +) + + +@pytest.mark.parametrize( + "package_name", + [ + ".candidate-driver", + "candidate-driver-", + "candidate/driver", + "candidate@driver", + "candidaté-driver", + ], +) +def test_candidate_rejects_non_pypa_distribution_names(package_name: str) -> None: + """Supply-chain evidence must use a valid Python distribution project name.""" + with pytest.raises(PostgresDriverCandidateEvidenceError, match="package name"): + PostgresDriverCandidateEvidence( + package_name=package_name, + package_version="1.2.3", + license_spdx="BSD-3-Clause", + license_report_sha256="d" * 64, + python_versions=tuple(sorted(REQUIRED_POSTGRES_DRIVER_PYTHON_VERSIONS)), + source_commit_sha="a" * 40, + artifact_sha256="b" * 64, + vulnerability_report_sha256="c" * 64, + known_vulnerability_ids=(), + capabilities=frozenset(REQUIRED_POSTGRES_DRIVER_CAPABILITIES), + ) From 6b7f598c5fcd7f3c0fae6372ceaab984ed896d4a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 10:09:23 +0900 Subject: [PATCH 094/338] fix(postgres): validate candidate distribution names --- pg_llm_batch/postgres_driver_candidate.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index 0936a6b2..0e83c1e2 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -62,6 +62,9 @@ _MAX_PYTHON_VERSION_EVIDENCE_ITEMS = 32 _MAX_VULNERABILITY_EVIDENCE_ITEMS = 256 _MINOR_PYTHON_VERSION = re.compile(r"^[1-9][0-9]*\.[0-9]+$") +_PYPA_DISTRIBUTION_NAME = re.compile( + r"[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?\Z" +) _SOURCE_COMMIT_SHA = re.compile(r"^[0-9a-f]{40}$") _ARTIFACT_SHA256 = re.compile(r"^[0-9a-f]{64}$") _VULNERABILITY_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$") @@ -171,6 +174,10 @@ def __post_init__(self) -> None: ("license", self.license_spdx), ): _validate_identity_text(label, value) + if _PYPA_DISTRIBUTION_NAME.fullmatch(self.package_name) is None: + raise PostgresDriverCandidateEvidenceError( + "PostgreSQL driver package name evidence is invalid" + ) if ( type(self.evidence_schema_version) is not str or self.evidence_schema_version From c7c8b35355b78f7380766ded2c1d007d2dcd8c70 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 10:23:22 +0900 Subject: [PATCH 095/338] test(postgres): reject surrogate candidate identity evidence --- ...est_postgres_driver_candidate_surrogate.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/test_postgres_driver_candidate_surrogate.py diff --git a/tests/test_postgres_driver_candidate_surrogate.py b/tests/test_postgres_driver_candidate_surrogate.py new file mode 100644 index 00000000..0b170fcd --- /dev/null +++ b/tests/test_postgres_driver_candidate_surrogate.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +import pytest + +from pg_llm_batch.postgres_driver_candidate import ( + REQUIRED_POSTGRES_DRIVER_CAPABILITIES, + REQUIRED_POSTGRES_DRIVER_PYTHON_VERSIONS, + PostgresDriverCandidateEvidence, + PostgresDriverCandidateEvidenceError, +) + + +def test_candidate_rejects_isolated_surrogate_with_domain_error() -> None: + """Malformed Unicode metadata must stay inside the candidate-evidence boundary.""" + with pytest.raises(PostgresDriverCandidateEvidenceError, match="package name"): + PostgresDriverCandidateEvidence( + package_name="candidate\ud800driver", + package_version="1.2.3", + license_spdx="BSD-3-Clause", + license_report_sha256="d" * 64, + python_versions=tuple(sorted(REQUIRED_POSTGRES_DRIVER_PYTHON_VERSIONS)), + source_commit_sha="a" * 40, + artifact_sha256="b" * 64, + vulnerability_report_sha256="c" * 64, + known_vulnerability_ids=(), + capabilities=frozenset(REQUIRED_POSTGRES_DRIVER_CAPABILITIES), + ) From ba635ec103bd8414b421dd2e6c47c35212556d6e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 10:24:13 +0900 Subject: [PATCH 096/338] fix(postgres): fail closed on malformed Unicode evidence --- pg_llm_batch/postgres_driver_candidate.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index 0e83c1e2..6e24f21a 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -84,16 +84,23 @@ def _validate_identity_text(label: str, value: object) -> None: Package name, version, and SPDX evidence participate in an acquisition decision and can arrive from untrusted package metadata. Rejecting whitespace, - controls, and Unicode format characters keeps one evidence value from becoming - multiple visual or line-oriented identities, while the UTF-8 byte ceiling - bounds malformed metadata without inventing a package-manager-specific grammar. + controls, malformed Unicode, and Unicode format characters keeps one evidence + value from becoming multiple visual or line-oriented identities, while the + UTF-8 byte ceiling bounds malformed metadata without inventing a + package-manager-specific grammar. """ if type(value) is not str or not value: raise PostgresDriverCandidateEvidenceError( f"PostgreSQL driver {label} evidence is invalid" ) + try: + encoded_value = value.encode("utf-8") + except UnicodeEncodeError: + raise PostgresDriverCandidateEvidenceError( + f"PostgreSQL driver {label} evidence is invalid" + ) from None if ( - len(value.encode("utf-8")) > _MAX_IDENTITY_EVIDENCE_BYTES + len(encoded_value) > _MAX_IDENTITY_EVIDENCE_BYTES or any( character.isspace() or ord(character) < 32 From 1f8c2b6813aaf0e4625eb30907b71a502cbe6fde Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 10:24:42 +0900 Subject: [PATCH 097/338] test(postgres): expose RLS reservation and row-count ambiguity --- .../test_postgres_driver_review_contracts.py | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 tests/test_postgres_driver_review_contracts.py diff --git a/tests/test_postgres_driver_review_contracts.py b/tests/test_postgres_driver_review_contracts.py new file mode 100644 index 00000000..6ac3039e --- /dev/null +++ b/tests/test_postgres_driver_review_contracts.py @@ -0,0 +1,167 @@ +from __future__ import annotations + +from datetime import datetime, timezone +from typing import Any, get_type_hints + +import pytest + +from pg_llm_batch import db +from pg_llm_batch.exceptions import ValidationError +from pg_llm_batch.postgres_driver_port import PostgresCursorPort +from pg_llm_batch.psycopg_driver_adapter import PsycopgCursorAdapter + + +class _RawCursor: + def __init__(self, rowcount: object) -> None: + self.rowcount = rowcount + + +class _Cursor: + def __init__(self, driver: _Driver) -> None: + self.driver = driver + + def __enter__(self) -> _Cursor: + return self + + def __exit__(self, *_exc: Any) -> None: + return None + + def execute(self, query: str, params: object | None = None) -> _Cursor: + self.driver.executions.append((query, params)) + return self + + def fetchone(self) -> tuple[object, ...] | None: + if not self.driver.rows: + return None + return self.driver.rows.pop(0) + + def row_count(self) -> int | None: + return self.driver.affected_rows + + +class _Connection: + def __init__(self, driver: _Driver) -> None: + self.driver = driver + + def __enter__(self) -> _Connection: + return self + + def __exit__(self, *_exc: Any) -> None: + return None + + def cursor(self) -> _Cursor: + return _Cursor(self.driver) + + def commit(self) -> None: + self.driver.commits += 1 + + +class _Driver: + def __init__( + self, + *, + rows: list[tuple[object, ...]] | None = None, + affected_rows: int | None = 1, + ) -> None: + self.rows = list(rows or []) + self.affected_rows = affected_rows + self.executions: list[tuple[str, object | None]] = [] + self.connections: list[str] = [] + self.commits = 0 + + def connect(self, dsn: str) -> _Connection: + self.connections.append(dsn) + return _Connection(self) + + +def _persisted_remote_batch_row(observed: datetime) -> tuple[object, ...]: + return ( + "standalone", + "primary", + "batch-1", + 1, + None, + "/v1/responses", + "in_progress", + None, + None, + 2, + 1, + 0, + {}, + observed, + observed, + None, + observed, + ) + + +def test_cursor_port_exposes_unknown_row_count_without_driver_sentinel() -> None: + """The provider-neutral cursor contract must represent unknown counts explicitly.""" + assert get_type_hints(PostgresCursorPort.row_count)["return"] == int | None + + +def test_psycopg_cursor_normalizes_unknown_row_count() -> None: + """Psycopg's -1 sentinel must not leak through the provider-neutral port.""" + assert PsycopgCursorAdapter(_RawCursor(-1)).row_count() is None + + +def test_observation_order_binds_validated_tenant_scope_before_sequence_io() -> None: + """A tenant reservation must establish transaction-local RLS identity first.""" + driver = _Driver(rows=[(41,)]) + + order = db.reserve_remote_batch_observation_order( + "postgresql://x", + tenant_scope="tenant-a", + postgres_driver=driver, + ) + + assert order == 41 + assert driver.executions == [ + ( + "SELECT set_config('pg_llm_batch.tenant_scope', %s, true)", + ("tenant-a",), + ), + ("SELECT nextval('llm_remote_batch_observation_sequence')", None), + ] + + +def test_observation_order_rejects_invalid_tenant_before_database_io() -> None: + """Untrusted tenant text must fail before a connection or sequence reservation.""" + driver = _Driver(rows=[(41,)]) + + with pytest.raises(ValidationError, match="tenant_scope"): + db.reserve_remote_batch_observation_order( + "postgresql://x", + tenant_scope="tenant scope", + postgres_driver=driver, + ) + + assert driver.connections == [] + assert driver.executions == [] + + +def test_unknown_remote_lifecycle_row_count_reads_persisted_state() -> None: + """Unknown affected-row evidence must not be guessed as a successful UPSERT.""" + observed = datetime(2026, 9, 3, 1, 0, tzinfo=timezone.utc) + driver = _Driver( + rows=[_persisted_remote_batch_row(observed)], + affected_rows=None, + ) + + snapshot = db.persist_remote_batch_state( + "postgresql://x", + "primary", + { + "id": "batch-1", + "endpoint": "/v1/responses", + "status": "in_progress", + "request_counts": {"total": 2, "completed": 2, "failed": 0}, + }, + observation_order=2, + observed_at=observed, + postgres_driver=driver, + ) + + assert snapshot["observation_order"] == 1 + assert snapshot["completed_requests"] == 1 From e3669fad9ef31831376674c1c7b3a917d3062802 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 10:36:01 +0900 Subject: [PATCH 098/338] fix(postgres): bind RLS reservation and normalize row counts --- pg_llm_batch/db.py | 23 +++++++++++++++++------ pg_llm_batch/postgres_driver_port.py | 11 +++++------ pg_llm_batch/psycopg_driver_adapter.py | 8 ++++++-- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/pg_llm_batch/db.py b/pg_llm_batch/db.py index 455f0b44..3f26037c 100644 --- a/pg_llm_batch/db.py +++ b/pg_llm_batch/db.py @@ -423,11 +423,14 @@ def normalize_provider_metadata(value: Any) -> Dict[str, Any]: def reserve_remote_batch_observation_order( dsn: str, *, + tenant_scope: str = DEFAULT_TENANT_SCOPE, postgres_driver: PostgresDriverPort | None = None, ) -> int: - """Reserve one positive database-owned lifecycle order through the driver port.""" + """Reserve one positive lifecycle order after binding validated tenant scope.""" + normalized_tenant_scope = validate_tenant_scope(tenant_scope) with _connect_database(dsn, postgres_driver) as conn: with conn.cursor() as cur: + _set_transaction_tenant_scope(cur, normalized_tenant_scope) cur.execute("SELECT nextval('llm_remote_batch_observation_sequence')") row = cur.fetchone() if ( @@ -454,10 +457,17 @@ def _cursor_row_count( cursor: Any, postgres_driver: PostgresDriverPort | None, ) -> int | None: - """Read affected-row evidence without leaking a candidate driver's raw cursor API.""" - if postgres_driver is not None: - return cursor.row_count() - return getattr(cursor, "rowcount", None) + """Read an exact affected-row count, normalizing unknown driver evidence.""" + value = ( + cursor.row_count() + if postgres_driver is not None + else getattr(cursor, "rowcount", None) + ) + if value is None or value == -1: + return None + if type(value) is not int or value < 0: + return None + return value def _normalize_remote_batch_snapshot( @@ -694,7 +704,8 @@ def _persist_remote_batch_state( with conn.cursor() as cur: _set_transaction_tenant_scope(cur, snapshot["tenant_scope"]) cur.execute(sql, params) - if _cursor_row_count(cur, postgres_driver) == 0: + affected_rows = _cursor_row_count(cur, postgres_driver) + if affected_rows in (None, 0): cur.execute( """ SELECT tenant_scope, diff --git a/pg_llm_batch/postgres_driver_port.py b/pg_llm_batch/postgres_driver_port.py index e898f884..b233557b 100644 --- a/pg_llm_batch/postgres_driver_port.py +++ b/pg_llm_batch/postgres_driver_port.py @@ -72,14 +72,13 @@ def fetchall(self) -> list[tuple[object, ...]]: """ @abstractmethod - def row_count(self) -> int: - """Return the concrete driver's affected-row count for the last operation. + def row_count(self) -> int | None: + """Return an exact affected-row count or ``None`` when it is unknown. Existing persistence paths use the count to detect missing updates and - partial batch membership writes. The adapter must preserve the driver's - integer semantics rather than guessing success when the count is unknown; - consumers that require an exact count remain responsible for failing - closed on a driver-specific unknown sentinel. + partial batch membership writes. Concrete adapters must normalize native + unknown sentinels at this boundary so consumers never mistake a + driver-specific integer such as ``-1`` for exact success evidence. """ @abstractmethod diff --git a/pg_llm_batch/psycopg_driver_adapter.py b/pg_llm_batch/psycopg_driver_adapter.py index 5f3df0d0..6c1277be 100644 --- a/pg_llm_batch/psycopg_driver_adapter.py +++ b/pg_llm_batch/psycopg_driver_adapter.py @@ -104,11 +104,15 @@ def fetchall(self) -> list[tuple[object, ...]]: """Return bounded query results while rejecting malformed rows.""" return [self._normalize_result_row(row) for row in self._cursor.fetchall()] - def row_count(self) -> int: - """Return Psycopg's exact integer affected-row result, including -1 unknown.""" + def row_count(self) -> int | None: + """Return an exact non-negative count or normalize Psycopg's unknown sentinel.""" value = self._cursor.rowcount if type(value) is not int: raise PsycopgDriverAdapterError("PostgreSQL driver row count is invalid") + if value == -1: + return None + if value < 0: + raise PsycopgDriverAdapterError("PostgreSQL driver row count is invalid") return value def __enter__(self) -> PsycopgCursorAdapter: From 3fb57ad976c8ce230b0cd3b2a911369afcdb8744 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:48:27 +0900 Subject: [PATCH 099/338] test(postgres): RED pg8000 candidate DB-API adapter contract --- tests/test_pg8000_driver_candidate_adapter.py | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 tests/test_pg8000_driver_candidate_adapter.py diff --git a/tests/test_pg8000_driver_candidate_adapter.py b/tests/test_pg8000_driver_candidate_adapter.py new file mode 100644 index 00000000..1015d015 --- /dev/null +++ b/tests/test_pg8000_driver_candidate_adapter.py @@ -0,0 +1,210 @@ +"""Contract tests for the pg8000 DB-API candidate adapter boundary. + +These tests deliberately use small DB-API-shaped fakes instead of importing +pg8000. They pin the pg-llm-batch side of the candidate contract first, while +real pg8000 1.31.5 and PostgreSQL acceptance remains a separate mandatory gate +before any runtime dependency or default-driver change. +""" + +from __future__ import annotations + +from typing import Any + +import pytest + +from pg_llm_batch.pg8000_driver_candidate_adapter import ( + Pg8000CandidateAdapterError, + Pg8000CandidateConnectionAdapter, + Pg8000CandidateCursorAdapter, +) +from pg_llm_batch.postgres_driver_port import PostgresConnectionPort, PostgresCursorPort + + +class _FakeCursor: + """Record DB-API cursor calls while exposing configurable materialized rows.""" + + def __init__(self) -> None: + self.execute_calls: list[tuple[str, object | None]] = [] + self.executemany_calls: list[tuple[str, object]] = [] + self.fetchone_value: object | None = None + self.fetchmany_value: list[object] = [] + self.fetchall_value: list[object] = [] + self.rowcount: object = 0 + self.enter_count = 0 + self.exit_args: tuple[object, object, object] | None = None + + def execute(self, query: str, params: object | None = None) -> _FakeCursor: + self.execute_calls.append((query, params)) + return self + + def executemany(self, query: str, params_seq: object) -> _FakeCursor: + self.executemany_calls.append((query, params_seq)) + return self + + def fetchone(self) -> object | None: + return self.fetchone_value + + def fetchmany(self, size: int) -> list[object]: + assert size > 0 + return self.fetchmany_value + + def fetchall(self) -> list[object]: + return self.fetchall_value + + def __enter__(self) -> _FakeCursor: + self.enter_count += 1 + return self + + def __exit__(self, exc_type: object, exc: object, traceback: object) -> bool: + self.exit_args = (exc_type, exc, traceback) + return False + + +class _FakeConnection: + """Record one connection's transaction and cursor activity.""" + + def __init__(self) -> None: + self.cursor_value = _FakeCursor() + self.autocommit: object = False + self.closed: object = False + self.commit_count = 0 + self.rollback_count = 0 + self.close_count = 0 + self.enter_count = 0 + self.exit_args: tuple[object, object, object] | None = None + + def cursor(self) -> _FakeCursor: + return self.cursor_value + + def commit(self) -> None: + self.commit_count += 1 + + def rollback(self) -> None: + self.rollback_count += 1 + + def close(self) -> None: + self.close_count += 1 + self.closed = True + + def __enter__(self) -> _FakeConnection: + self.enter_count += 1 + return self + + def __exit__(self, exc_type: object, exc: object, traceback: object) -> bool: + self.exit_args = (exc_type, exc, traceback) + return False + + +def test_candidate_cursor_preserves_parameter_binding_and_wrapper_identity() -> None: + raw = _FakeCursor() + adapter = Pg8000CandidateCursorAdapter(raw) + params = ("tenant-a", 7) + + result = adapter.execute("SELECT %s, %s", params) + many_result = adapter.executemany("INSERT INTO t VALUES (%s)", [(1,), (2,)]) + + assert isinstance(adapter, PostgresCursorPort) + assert result is adapter + assert many_result is adapter + assert raw.execute_calls == [("SELECT %s, %s", params)] + assert raw.executemany_calls == [("INSERT INTO t VALUES (%s)", [(1,), (2,)])] + + +def test_candidate_cursor_normalizes_pg8000_list_rows_to_tuples() -> None: + raw = _FakeCursor() + raw.fetchone_value = ["one", 1] + raw.fetchmany_value = [["two", 2], ("three", 3)] + raw.fetchall_value = [("four", 4), ["five", 5]] + adapter = Pg8000CandidateCursorAdapter(raw) + + assert adapter.fetchone() == ("one", 1) + assert adapter.fetchmany(2) == [("two", 2), ("three", 3)] + assert adapter.fetchall() == [("four", 4), ("five", 5)] + + +def test_candidate_cursor_rejects_non_positional_rows_and_invalid_fetch_size() -> None: + raw = _FakeCursor() + adapter = Pg8000CandidateCursorAdapter(raw) + raw.fetchone_value = {"id": 1} + + with pytest.raises(Pg8000CandidateAdapterError, match="result row is invalid"): + adapter.fetchone() + with pytest.raises(Pg8000CandidateAdapterError, match="fetch size is invalid"): + adapter.fetchmany(0) + with pytest.raises(Pg8000CandidateAdapterError, match="fetch size is invalid"): + adapter.fetchmany(True) + + +def test_candidate_cursor_normalizes_unknown_row_count_and_rejects_bad_sentinels() -> None: + raw = _FakeCursor() + adapter = Pg8000CandidateCursorAdapter(raw) + + raw.rowcount = -1 + assert adapter.row_count() is None + raw.rowcount = 4 + assert adapter.row_count() == 4 + raw.rowcount = -2 + with pytest.raises(Pg8000CandidateAdapterError, match="row count is invalid"): + adapter.row_count() + raw.rowcount = "4" + with pytest.raises(Pg8000CandidateAdapterError, match="row count is invalid"): + adapter.row_count() + + +def test_candidate_cursor_context_delegates_cleanup_without_suppressing_errors() -> None: + raw = _FakeCursor() + adapter = Pg8000CandidateCursorAdapter(raw) + error = RuntimeError("boom") + + assert adapter.__enter__() is adapter + assert raw.enter_count == 1 + assert adapter.__exit__(RuntimeError, error, None) is False + assert raw.exit_args == (RuntimeError, error, None) + + +def test_candidate_connection_uses_one_raw_connection_for_execution_and_transactions() -> None: + raw = _FakeConnection() + adapter = Pg8000CandidateConnectionAdapter(raw) + + cursor = adapter.cursor() + direct_cursor = adapter.execute("SELECT %s", (9,)) + adapter.commit() + adapter.rollback() + + assert isinstance(adapter, PostgresConnectionPort) + assert isinstance(cursor, Pg8000CandidateCursorAdapter) + assert isinstance(direct_cursor, Pg8000CandidateCursorAdapter) + assert raw.cursor_value.execute_calls == [("SELECT %s", (9,))] + assert raw.commit_count == 1 + assert raw.rollback_count == 1 + + +def test_candidate_connection_validates_autocommit_and_closed_state() -> None: + raw = _FakeConnection() + adapter = Pg8000CandidateConnectionAdapter(raw) + + adapter.set_autocommit(True) + assert raw.autocommit is True + assert adapter.is_closed() is False + + with pytest.raises(Pg8000CandidateAdapterError, match="autocommit is invalid"): + adapter.set_autocommit(1) # type: ignore[arg-type] + + raw.closed = 0 + with pytest.raises(Pg8000CandidateAdapterError, match="closed state is unavailable"): + adapter.is_closed() + + +def test_candidate_connection_close_and_context_delegate_to_raw_connection() -> None: + raw = _FakeConnection() + adapter = Pg8000CandidateConnectionAdapter(raw) + error = ValueError("bad") + + assert adapter.__enter__() is adapter + assert raw.enter_count == 1 + assert adapter.__exit__(ValueError, error, None) is False + assert raw.exit_args == (ValueError, error, None) + + adapter.close() + assert raw.close_count == 1 + assert adapter.is_closed() is True From 26004c5694be4b498275097fc3996aaec35c0f34 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:49:32 +0900 Subject: [PATCH 100/338] feat(postgres): GREEN pg8000 candidate DB-API adapter seam --- .../pg8000_driver_candidate_adapter.py | 274 ++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 pg_llm_batch/pg8000_driver_candidate_adapter.py diff --git a/pg_llm_batch/pg8000_driver_candidate_adapter.py b/pg_llm_batch/pg8000_driver_candidate_adapter.py new file mode 100644 index 00000000..a014215d --- /dev/null +++ b/pg_llm_batch/pg8000_driver_candidate_adapter.py @@ -0,0 +1,274 @@ +"""Candidate-only pg8000 DB-API adapters for commercial migration evidence. + +This module intentionally stops short of a production ``PostgresDriverPort``. +pg8000 1.31.5 documents the DB-API cursor, transaction, autocommit, parameter +binding, and ``-1`` unknown-row-count behavior needed by part of the current +port, but pg-llm-batch has not yet proved its full conninfo/service-selector, +JSONB adaptation, closed-state, PostgreSQL error-classification, Python 3.14, +RLS, recovery, concurrency, package, SBOM, and provenance contract on one exact +artifact. Keeping this adapter candidate-only lets those portable semantics be +exercised without making an unreleased or unverified runtime dependency +canonical. +""" + +from __future__ import annotations + +from typing import Any + +from .postgres_driver_port import PostgresConnectionPort, PostgresCursorPort + + +class Pg8000CandidateAdapterError(RuntimeError): + """Report a candidate-boundary mismatch without exposing database content. + + A mismatch means the candidate cannot yet be promoted through the shared + PostgreSQL port. The error is deliberately separate from pg8000's database + exceptions so callers cannot mistake missing adapter evidence for a server + or application failure. + """ + + +class Pg8000CandidateCursorAdapter(PostgresCursorPort): + """Exercise pg8000 DB-API cursor semantics behind the canonical cursor port. + + The raw cursor remains dependency-injected because this candidate slice must + not add pg8000 to the production dependency graph before the exact artifact + passes license, security, Python, PostgreSQL, and recovery admission. Query + text and bound parameters are forwarded unchanged; materialized list rows are + normalized to the tuple representation already used by pg-llm-batch. + """ + + def __init__(self, cursor: Any) -> None: + self._cursor = cursor + + @staticmethod + def _normalize_result_row(row: object) -> tuple[object, ...]: + """Normalize one DB-API positional row while rejecting ambiguous shapes. + + pg8000 documents list-like result rows. The package canonicalizes exact + ``list`` and ``tuple`` containers only; mapping or custom containers are + rejected so a driver-specific row factory cannot silently change domain + indexing or equality semantics. + """ + if type(row) is tuple: + return row + if type(row) is list: + return tuple(row) + raise Pg8000CandidateAdapterError("PostgreSQL driver result row is invalid") + + def execute( + self, + query: str, + params: object | None = None, + ) -> Pg8000CandidateCursorAdapter: + """Forward package-authored SQL and bound parameters without interpolation. + + pg8000's DB-API interface defaults to ``format`` parameter style, which + matches the existing ``%s`` package SQL. This candidate method forwards + both objects unchanged so later real-driver tests can detect any semantic + mismatch rather than hiding it in an adapter rewrite. + """ + self._cursor.execute(query, params) + return self + + def executemany( + self, + query: str, + params_seq: object, + ) -> Pg8000CandidateCursorAdapter: + """Forward one statement and parameter sequence without implicit commits. + + Transaction ownership remains with the retained connection. The adapter + therefore does not commit between items or transform the supplied + sequence into independently executed application operations. + """ + self._cursor.executemany(query, params_seq) + return self + + def fetchone(self) -> tuple[object, ...] | None: + """Return one canonical tuple row or ``None`` at end of results. + + Only row-container normalization belongs here. Field-count, type, tenant, + and domain validation remain responsibilities of the consuming bounded + context after the database adapter returns. + """ + row = self._cursor.fetchone() + if row is None: + return None + return self._normalize_result_row(row) + + def fetchmany(self, size: int) -> list[tuple[object, ...]]: + """Return a bounded result page and reject invalid caller size values. + + ``bool`` is rejected even though it subclasses ``int`` because an + accidental truth value must not become a one-row resource budget. + """ + if type(size) is not int or size <= 0: + raise Pg8000CandidateAdapterError("PostgreSQL driver fetch size is invalid") + return [self._normalize_result_row(row) for row in self._cursor.fetchmany(size)] + + def fetchall(self) -> list[tuple[object, ...]]: + """Normalize all rows from an already bounded package-authored query. + + This preserves current package compatibility but does not authorize new + unbounded queries; untrusted-result paths must continue to enforce their + own finite SQL and fetch budgets. + """ + return [self._normalize_result_row(row) for row in self._cursor.fetchall()] + + def row_count(self) -> int | None: + """Normalize pg8000's documented ``-1`` unknown row count to ``None``. + + Exact non-negative counts remain usable as mutation evidence. Any other + negative sentinel or non-integer value fails closed because the package + must not interpret an undocumented driver state as exact write success. + """ + value = self._cursor.rowcount + if type(value) is not int: + raise Pg8000CandidateAdapterError("PostgreSQL driver row count is invalid") + if value == -1: + return None + if value < 0: + raise Pg8000CandidateAdapterError("PostgreSQL driver row count is invalid") + return value + + def __enter__(self) -> Pg8000CandidateCursorAdapter: + """Enter the raw cursor context while keeping this adapter's identity. + + Candidate acceptance must later prove the real pg8000 cursor implements + compatible context-manager cleanup; this wrapper does not synthesize that + behavior when it is absent. + """ + self._cursor.__enter__() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + traceback: object | None, + ) -> bool | None: + """Delegate raw cursor cleanup and preserve exception propagation policy. + + The return value is forwarded exactly because changing it could suppress + a database or application exception and create false transaction success. + """ + return self._cursor.__exit__(exc_type, exc, traceback) + + +class Pg8000CandidateConnectionAdapter(PostgresConnectionPort): + """Exercise portable pg8000 DB-API connection semantics on one raw connection. + + This adapter proves only the connection/cursor portion of the migration port. + It never opens a connection itself and therefore cannot bypass the still-open + DSN/conninfo/service-selector admission problem. All operations stay on the + injected raw connection so transaction-local RLS state cannot migrate to an + implicit second session. + """ + + def __init__(self, connection: Any) -> None: + self._connection = connection + + def cursor(self) -> Pg8000CandidateCursorAdapter: + """Create a candidate cursor on this exact retained database connection. + + No second connection or hidden pool is introduced. Later PostgreSQL + acceptance must prove the real driver preserves the same session for + tenant-local ``set_config`` and lifecycle SQL. + """ + return Pg8000CandidateCursorAdapter(self._connection.cursor()) + + def execute( + self, + query: str, + params: object | None = None, + ) -> Pg8000CandidateCursorAdapter: + """Execute through a cursor created from this retained connection only. + + DB-API does not require ``Connection.execute``. Creating a cursor here + keeps the canonical convenience method while preserving parameter binding + and session identity instead of depending on a non-portable extension. + """ + cursor = self.cursor() + cursor.execute(query, params) + return cursor + + def commit(self) -> None: + """Commit the current local transaction through the raw DB-API connection. + + The adapter adds no retry or distributed-delivery semantics; higher + bounded contexts retain responsibility for replay and idempotency. + """ + self._connection.commit() + + def rollback(self) -> None: + """Roll back the current local transaction and propagate driver failures. + + Rollback errors remain visible because hiding them would make recovery + evidence claim a clean transaction boundary that PostgreSQL did not prove. + """ + self._connection.rollback() + + def set_autocommit(self, enabled: bool) -> None: + """Set pg8000's documented autocommit property from an exact boolean only. + + Rejecting integer truthiness prevents configuration mistakes from being + normalized into transaction-policy changes at the infrastructure edge. + """ + if type(enabled) is not bool: + raise Pg8000CandidateAdapterError("PostgreSQL driver autocommit is invalid") + self._connection.autocommit = enabled + + def is_closed(self) -> bool: + """Return an exact public closed-state signal or fail candidate admission. + + The current PostgreSQL port requires deterministic cached-connection + recovery, while pg8000's public DB-API documentation reviewed for this + slice does not establish a portable closed-state attribute. Candidate + runtime tests must therefore supply and prove an exact boolean signal; + absence or an ambiguous value is a compatibility failure, not ``False``. + """ + try: + value = self._connection.closed + except AttributeError: + raise Pg8000CandidateAdapterError( + "PostgreSQL driver closed state is unavailable" + ) from None + if type(value) is not bool: + raise Pg8000CandidateAdapterError( + "PostgreSQL driver closed state is unavailable" + ) + return value + + def close(self) -> None: + """Close the retained raw connection and release its session authority. + + The candidate does not retain or recreate a hidden connection after this + call; later real-driver recovery tests must prove cleanup and reconnect + behavior under process and database failures. + """ + self._connection.close() + + def __enter__(self) -> Pg8000CandidateConnectionAdapter: + """Enter the raw connection context without changing transaction policy. + + Real pg8000 acceptance must verify its DB-API context manager has the + commit/rollback semantics required by the port before this candidate can + become a production driver. + """ + self._connection.__enter__() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + traceback: object | None, + ) -> bool | None: + """Delegate connection-context exit without suppressing raw-driver errors. + + Returning the raw value preserves its transaction and exception behavior + for later parity tests instead of making the candidate look compatible by + changing failure semantics in the wrapper. + """ + return self._connection.__exit__(exc_type, exc, traceback) From a92d5af9160a9c528f65bf6c91df29a8d18ea506 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:50:26 +0900 Subject: [PATCH 101/338] test(postgres): cover pg8000 candidate adapter edge branches --- tests/test_pg8000_driver_candidate_adapter.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/test_pg8000_driver_candidate_adapter.py b/tests/test_pg8000_driver_candidate_adapter.py index 1015d015..c84dbb4d 100644 --- a/tests/test_pg8000_driver_candidate_adapter.py +++ b/tests/test_pg8000_driver_candidate_adapter.py @@ -112,10 +112,13 @@ def test_candidate_cursor_preserves_parameter_binding_and_wrapper_identity() -> def test_candidate_cursor_normalizes_pg8000_list_rows_to_tuples() -> None: raw = _FakeCursor() + adapter = Pg8000CandidateCursorAdapter(raw) + + assert adapter.fetchone() is None + raw.fetchone_value = ["one", 1] raw.fetchmany_value = [["two", 2], ("three", 3)] raw.fetchall_value = [("four", 4), ["five", 5]] - adapter = Pg8000CandidateCursorAdapter(raw) assert adapter.fetchone() == ("one", 1) assert adapter.fetchmany(2) == [("two", 2), ("three", 3)] @@ -194,6 +197,10 @@ def test_candidate_connection_validates_autocommit_and_closed_state() -> None: with pytest.raises(Pg8000CandidateAdapterError, match="closed state is unavailable"): adapter.is_closed() + del raw.closed + with pytest.raises(Pg8000CandidateAdapterError, match="closed state is unavailable"): + adapter.is_closed() + def test_candidate_connection_close_and_context_delegate_to_raw_connection() -> None: raw = _FakeConnection() From f1e14ccea0952fc8ed85eeda81c1492028611b47 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:54:10 +0900 Subject: [PATCH 102/338] test(postgres): remove unused candidate test import --- tests/test_pg8000_driver_candidate_adapter.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_pg8000_driver_candidate_adapter.py b/tests/test_pg8000_driver_candidate_adapter.py index c84dbb4d..fcd94118 100644 --- a/tests/test_pg8000_driver_candidate_adapter.py +++ b/tests/test_pg8000_driver_candidate_adapter.py @@ -8,8 +8,6 @@ from __future__ import annotations -from typing import Any - import pytest from pg_llm_batch.pg8000_driver_candidate_adapter import ( From dab97ab82ed3083f02cdf19c00b6ec7bf10bd4b8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:58:22 +0900 Subject: [PATCH 103/338] test(postgres): RED fail closed on pg8000 paramstyle drift --- tests/test_pg8000_driver_candidate_adapter.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/test_pg8000_driver_candidate_adapter.py b/tests/test_pg8000_driver_candidate_adapter.py index fcd94118..ff6e19ef 100644 --- a/tests/test_pg8000_driver_candidate_adapter.py +++ b/tests/test_pg8000_driver_candidate_adapter.py @@ -8,12 +8,15 @@ from __future__ import annotations +from types import ModuleType + import pytest from pg_llm_batch.pg8000_driver_candidate_adapter import ( Pg8000CandidateAdapterError, Pg8000CandidateConnectionAdapter, Pg8000CandidateCursorAdapter, + validate_pg8000_dbapi_module, ) from pg_llm_batch.postgres_driver_port import PostgresConnectionPort, PostgresCursorPort @@ -93,6 +96,41 @@ def __exit__(self, exc_type: object, exc: object, traceback: object) -> bool: return False +def _dbapi_module(*, apilevel: object = "2.0", paramstyle: object = "format") -> ModuleType: + """Build one exact module-shaped DB-API authority for candidate contract tests.""" + module = ModuleType("pg8000.dbapi") + module.apilevel = apilevel + module.paramstyle = paramstyle + return module + + +def test_candidate_dbapi_module_requires_dbapi_2_and_format_parameter_style() -> None: + module = _dbapi_module() + + validate_pg8000_dbapi_module(module) + + module.paramstyle = "named" + with pytest.raises(Pg8000CandidateAdapterError, match="parameter style is incompatible"): + validate_pg8000_dbapi_module(module) + + module.paramstyle = "format" + module.apilevel = "1.0" + with pytest.raises(Pg8000CandidateAdapterError, match="API level is incompatible"): + validate_pg8000_dbapi_module(module) + + +def test_candidate_dbapi_module_rejects_shaped_or_behavior_bearing_metadata() -> None: + class _StringSubclass(str): + pass + + with pytest.raises(Pg8000CandidateAdapterError, match="module identity is invalid"): + validate_pg8000_dbapi_module(object()) + with pytest.raises(Pg8000CandidateAdapterError, match="API level is incompatible"): + validate_pg8000_dbapi_module(_dbapi_module(apilevel=_StringSubclass("2.0"))) + with pytest.raises(Pg8000CandidateAdapterError, match="parameter style is incompatible"): + validate_pg8000_dbapi_module(_dbapi_module(paramstyle=_StringSubclass("format"))) + + def test_candidate_cursor_preserves_parameter_binding_and_wrapper_identity() -> None: raw = _FakeCursor() adapter = Pg8000CandidateCursorAdapter(raw) From d73058a202620cba7656f66abb46d447ab73f7e6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:59:18 +0900 Subject: [PATCH 104/338] feat(postgres): GREEN guard pg8000 DB-API parameter mode --- .../pg8000_driver_candidate_adapter.py | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/pg_llm_batch/pg8000_driver_candidate_adapter.py b/pg_llm_batch/pg8000_driver_candidate_adapter.py index a014215d..9504595d 100644 --- a/pg_llm_batch/pg8000_driver_candidate_adapter.py +++ b/pg_llm_batch/pg8000_driver_candidate_adapter.py @@ -13,7 +13,8 @@ from __future__ import annotations -from typing import Any +from types import ModuleType +from typing import Any, cast from .postgres_driver_port import PostgresConnectionPort, PostgresCursorPort @@ -28,6 +29,36 @@ class Pg8000CandidateAdapterError(RuntimeError): """ +def validate_pg8000_dbapi_module(dbapi_module: object) -> None: + """Fail closed unless the imported pg8000 DB-API mode matches package SQL. + + pg8000 exposes ``paramstyle`` as mutable module state. pg-llm-batch's current + SQL uses DB-API ``format`` placeholders, so a future production candidate + factory must run this guard immediately after importing the exact admitted + pg8000 artifact and before creating adapters or executing SQL. Metadata is + read from an exact ``ModuleType`` dictionary rather than through arbitrary + shaped objects whose attribute access could execute caller-controlled code. + + Raises: + Pg8000CandidateAdapterError: If DB-API 2.0 or ``format`` parameter style + is not the exact active module contract. + """ + if type(dbapi_module) is not ModuleType: + raise Pg8000CandidateAdapterError("PostgreSQL driver module identity is invalid") + + module = cast(ModuleType, dbapi_module) + metadata = vars(module) + api_level = metadata.get("apilevel") + parameter_style = metadata.get("paramstyle") + + if type(api_level) is not str or api_level != "2.0": + raise Pg8000CandidateAdapterError("PostgreSQL driver API level is incompatible") + if type(parameter_style) is not str or parameter_style != "format": + raise Pg8000CandidateAdapterError( + "PostgreSQL driver parameter style is incompatible" + ) + + class Pg8000CandidateCursorAdapter(PostgresCursorPort): """Exercise pg8000 DB-API cursor semantics behind the canonical cursor port. From 5d02d22358ca7496201860163286a2ddb75c710c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 14:33:08 +0900 Subject: [PATCH 105/338] test(postgres): require immutable capability report identity --- tests/test_postgres_driver_candidate_report_provenance.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_postgres_driver_candidate_report_provenance.py b/tests/test_postgres_driver_candidate_report_provenance.py index a6edb25c..703ce21f 100644 --- a/tests/test_postgres_driver_candidate_report_provenance.py +++ b/tests/test_postgres_driver_candidate_report_provenance.py @@ -17,6 +17,13 @@ def test_candidate_evidence_has_explicit_schema_version() -> None: assert "evidence_schema_version" in evidence_fields +def test_candidate_evidence_binds_capability_report_identity() -> None: + """Capability claims need immutable report identity before parity admission.""" + evidence_fields = {field.name for field in fields(PostgresDriverCandidateEvidence)} + + assert "capability_report_sha256" in evidence_fields + + def test_candidate_schema_version_rejects_equality_spoofing() -> None: """Untrusted receipt objects cannot impersonate the current schema by equality.""" From 2628a39fa1655e63390d8c06dd9bd68d311a3104 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 14:34:53 +0900 Subject: [PATCH 106/338] feat(postgres): bind candidate capabilities to immutable report --- pg_llm_batch/postgres_driver_candidate.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index 6e24f21a..71ad5222 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -45,7 +45,7 @@ ) """Repository-supported Python minors a replacement driver must evidence explicitly.""" -POSTGRES_DRIVER_CANDIDATE_EVIDENCE_SCHEMA_VERSION = "1" +POSTGRES_DRIVER_CANDIDATE_EVIDENCE_SCHEMA_VERSION = "2" """Version of the candidate-evidence receipt interpreted by this evaluator.""" _APPROVED_PERMISSIVE_LICENSES = frozenset( @@ -150,10 +150,12 @@ class PostgresDriverCandidateEvidence: ``source_commit_sha`` identifies the reviewed source revision, ``license_report_sha256`` binds the exact license evidence used for - ``license_spdx``, ``artifact_sha256`` identifies the exact distributable, and + ``license_spdx``, ``artifact_sha256`` identifies the exact distributable, ``vulnerability_report_sha256`` binds the exact vulnerability evidence used - for the decision. ``known_vulnerability_ids`` records unresolved advisories - from that report. ``python_versions`` and ``capabilities`` must contain + for the decision, and ``capability_report_sha256`` binds the exact parity + capability report from which ``capabilities`` is derived. + ``known_vulnerability_ids`` records unresolved advisories from the bound + vulnerability report. ``python_versions`` and ``capabilities`` must contain explicit evidence rather than inferred support from a nearby release or similar database driver. ``evidence_schema_version`` prevents a future receipt shape from being silently interpreted under today's semantics. @@ -169,6 +171,7 @@ class PostgresDriverCandidateEvidence: source_commit_sha: str artifact_sha256: str vulnerability_report_sha256: str + capability_report_sha256: str known_vulnerability_ids: tuple[str, ...] capabilities: frozenset[str] evidence_schema_version: str = POSTGRES_DRIVER_CANDIDATE_EVIDENCE_SCHEMA_VERSION @@ -240,6 +243,13 @@ def __post_init__(self) -> None: raise PostgresDriverCandidateEvidenceError( "PostgreSQL driver vulnerability report evidence is invalid" ) + if ( + type(self.capability_report_sha256) is not str + or _ARTIFACT_SHA256.fullmatch(self.capability_report_sha256) is None + ): + raise PostgresDriverCandidateEvidenceError( + "PostgreSQL driver capability report evidence is invalid" + ) _validate_vulnerability_ids(self.known_vulnerability_ids) if type(self.capabilities) is not frozenset or not self.capabilities: raise PostgresDriverCandidateEvidenceError( @@ -295,6 +305,7 @@ def _validated_candidate_snapshot( source_commit_sha=evidence.source_commit_sha, artifact_sha256=evidence.artifact_sha256, vulnerability_report_sha256=evidence.vulnerability_report_sha256, + capability_report_sha256=evidence.capability_report_sha256, known_vulnerability_ids=evidence.known_vulnerability_ids, capabilities=evidence.capabilities, evidence_schema_version=evidence.evidence_schema_version, From c4f834d5bf8d54426449f100e7fa1718fda2f6e6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 14:35:46 +0900 Subject: [PATCH 107/338] test(postgres): cover capability report digest validation --- tests/test_postgres_driver_candidate.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/tests/test_postgres_driver_candidate.py b/tests/test_postgres_driver_candidate.py index 2a07454d..a2891482 100644 --- a/tests/test_postgres_driver_candidate.py +++ b/tests/test_postgres_driver_candidate.py @@ -17,6 +17,7 @@ ARTIFACT_SHA256 = "b" * 64 VULNERABILITY_REPORT_SHA256 = "c" * 64 LICENSE_REPORT_SHA256 = "d" * 64 +CAPABILITY_REPORT_SHA256 = "e" * 64 def _evidence(**overrides: object) -> PostgresDriverCandidateEvidence: @@ -29,6 +30,7 @@ def _evidence(**overrides: object) -> PostgresDriverCandidateEvidence: "source_commit_sha": SOURCE_SHA, "artifact_sha256": ARTIFACT_SHA256, "vulnerability_report_sha256": VULNERABILITY_REPORT_SHA256, + "capability_report_sha256": CAPABILITY_REPORT_SHA256, "known_vulnerability_ids": (), "capabilities": FULL_CAPABILITIES, } @@ -64,6 +66,17 @@ def test_candidate_rejects_malformed_license_report_identity( _evidence(license_report_sha256=license_report_sha256) +@pytest.mark.parametrize( + "capability_report_sha256", + ["e" * 63, "z" * 64, 64], +) +def test_candidate_rejects_malformed_capability_report_identity( + capability_report_sha256: object, +) -> None: + with pytest.raises(PostgresDriverCandidateEvidenceError, match="capability report"): + _evidence(capability_report_sha256=capability_report_sha256) + + def test_candidate_contract_covers_issue_322_type_and_parameter_parity() -> None: assert { "result_row_semantics", @@ -138,9 +151,7 @@ def test_candidate_reports_every_missing_runtime_capability_deterministically() _evidence(capabilities=frozenset({"parameterized_sql", "jsonb"})) ) - expected_missing = sorted( - FULL_CAPABILITIES - {"parameterized_sql", "jsonb"} - ) + expected_missing = sorted(FULL_CAPABILITIES - {"parameterized_sql", "jsonb"}) assert decision.eligible_for_parity_validation is False assert decision.reasons == tuple( f"missing_capability:{capability}" for capability in expected_missing @@ -167,6 +178,9 @@ def test_candidate_reports_every_missing_runtime_capability_deterministically() ("vulnerability_report_sha256", "c" * 63), ("vulnerability_report_sha256", "z" * 64), ("vulnerability_report_sha256", 64), + ("capability_report_sha256", "e" * 63), + ("capability_report_sha256", "z" * 64), + ("capability_report_sha256", 64), ("known_vulnerability_ids", ["CVE-2025-61385"]), ("capabilities", frozenset()), ("capabilities", {"parameterized_sql"}), @@ -213,9 +227,7 @@ def test_candidate_rejects_malformed_vulnerability_identifiers( def test_candidate_rejects_duplicate_vulnerability_identifiers() -> None: with pytest.raises(PostgresDriverCandidateEvidenceError, match="vulnerability"): - _evidence( - known_vulnerability_ids=("CVE-2025-61385", "CVE-2025-61385") - ) + _evidence(known_vulnerability_ids=("CVE-2025-61385", "CVE-2025-61385")) def test_candidate_rejects_unbounded_python_version_evidence() -> None: From 1a9327a95ab464245aa163d30e3aea0ca76a519e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 14:36:26 +0900 Subject: [PATCH 108/338] test(postgres): supply capability report in receipt fixtures --- tests/test_postgres_driver_candidate_report_provenance.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_postgres_driver_candidate_report_provenance.py b/tests/test_postgres_driver_candidate_report_provenance.py index 703ce21f..4a0fafa4 100644 --- a/tests/test_postgres_driver_candidate_report_provenance.py +++ b/tests/test_postgres_driver_candidate_report_provenance.py @@ -41,6 +41,7 @@ def __eq__(self, other: object) -> bool: source_commit_sha="a" * 40, artifact_sha256="b" * 64, vulnerability_report_sha256="c" * 64, + capability_report_sha256="e" * 64, known_vulnerability_ids=(), capabilities=frozenset({"parameterized_sql"}), evidence_schema_version=PretendsToBeCurrent(), # type: ignore[arg-type] From 0d10721c76a5b3fe9103678f0b4b52e328948f57 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 14:36:39 +0900 Subject: [PATCH 109/338] test(postgres): bind package-name fixtures to capability report --- tests/test_postgres_driver_candidate_package_name.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_postgres_driver_candidate_package_name.py b/tests/test_postgres_driver_candidate_package_name.py index f78198b4..a31774b8 100644 --- a/tests/test_postgres_driver_candidate_package_name.py +++ b/tests/test_postgres_driver_candidate_package_name.py @@ -32,6 +32,7 @@ def test_candidate_rejects_non_pypa_distribution_names(package_name: str) -> Non source_commit_sha="a" * 40, artifact_sha256="b" * 64, vulnerability_report_sha256="c" * 64, + capability_report_sha256="e" * 64, known_vulnerability_ids=(), capabilities=frozenset(REQUIRED_POSTGRES_DRIVER_CAPABILITIES), ) From 8eb0c034b2bf1c9c03bf3746f3f882e667182297 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 14:36:52 +0900 Subject: [PATCH 110/338] test(postgres): bind surrogate fixture to capability report --- tests/test_postgres_driver_candidate_surrogate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_postgres_driver_candidate_surrogate.py b/tests/test_postgres_driver_candidate_surrogate.py index 0b170fcd..2e8660a4 100644 --- a/tests/test_postgres_driver_candidate_surrogate.py +++ b/tests/test_postgres_driver_candidate_surrogate.py @@ -22,6 +22,7 @@ def test_candidate_rejects_isolated_surrogate_with_domain_error() -> None: source_commit_sha="a" * 40, artifact_sha256="b" * 64, vulnerability_report_sha256="c" * 64, + capability_report_sha256="e" * 64, known_vulnerability_ids=(), capabilities=frozenset(REQUIRED_POSTGRES_DRIVER_CAPABILITIES), ) From 9268b6dc69db58f94fc97aaf81513bc5f7a64b12 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:19:27 +0900 Subject: [PATCH 111/338] test(postgres): align driver reservation regression with tenant scope --- tests/test_postgres_driver_remote_lifecycle.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_postgres_driver_remote_lifecycle.py b/tests/test_postgres_driver_remote_lifecycle.py index bb8317bc..18d721a8 100644 --- a/tests/test_postgres_driver_remote_lifecycle.py +++ b/tests/test_postgres_driver_remote_lifecycle.py @@ -103,8 +103,8 @@ def _persisted_remote_batch_row( def test_observation_order_reservation_uses_injected_driver_without_psycopg( monkeypatch: pytest.MonkeyPatch, ) -> None: - """Global lifecycle ordering must remain usable after the Psycopg graph is removed.""" - driver = _Driver(rows=[(41,)]) + """Driver migration must retain the default standalone tenant boundary.""" + driver = _Driver(rows=[("standalone",), (41,)]) monkeypatch.setattr(db, "psycopg", None) order = db.reserve_remote_batch_observation_order( @@ -115,7 +115,11 @@ def test_observation_order_reservation_uses_injected_driver_without_psycopg( assert order == 41 assert driver.connections == ["postgresql://x"] assert driver.executions == [ - ("SELECT nextval('llm_remote_batch_observation_sequence')", None) + ( + "SELECT set_config('pg_llm_batch.tenant_scope', %s, true)", + ("standalone",), + ), + ("SELECT nextval('llm_remote_batch_observation_sequence')", None), ] From 76b7555d9be399c2db4b6369845b921c598aae0b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:20:03 +0900 Subject: [PATCH 112/338] fix(test): preserve nextval fixture after tenant binding --- tests/test_postgres_driver_remote_lifecycle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_postgres_driver_remote_lifecycle.py b/tests/test_postgres_driver_remote_lifecycle.py index 18d721a8..6770ad2d 100644 --- a/tests/test_postgres_driver_remote_lifecycle.py +++ b/tests/test_postgres_driver_remote_lifecycle.py @@ -104,7 +104,7 @@ def test_observation_order_reservation_uses_injected_driver_without_psycopg( monkeypatch: pytest.MonkeyPatch, ) -> None: """Driver migration must retain the default standalone tenant boundary.""" - driver = _Driver(rows=[("standalone",), (41,)]) + driver = _Driver(rows=[(41,)]) monkeypatch.setattr(db, "psycopg", None) order = db.reserve_remote_batch_observation_order( From a25c1ae7124201ab7836022c0bd22b988a4d3cc2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:37:21 +0900 Subject: [PATCH 113/338] test(postgres): RED own DB-API cursor cleanup in adapter --- tests/test_pg8000_driver_candidate_adapter.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/test_pg8000_driver_candidate_adapter.py b/tests/test_pg8000_driver_candidate_adapter.py index ff6e19ef..a77935c4 100644 --- a/tests/test_pg8000_driver_candidate_adapter.py +++ b/tests/test_pg8000_driver_candidate_adapter.py @@ -31,6 +31,7 @@ def __init__(self) -> None: self.fetchmany_value: list[object] = [] self.fetchall_value: list[object] = [] self.rowcount: object = 0 + self.close_count = 0 self.enter_count = 0 self.exit_args: tuple[object, object, object] | None = None @@ -52,6 +53,9 @@ def fetchmany(self, size: int) -> list[object]: def fetchall(self) -> list[object]: return self.fetchall_value + def close(self) -> None: + self.close_count += 1 + def __enter__(self) -> _FakeCursor: self.enter_count += 1 return self @@ -190,15 +194,16 @@ def test_candidate_cursor_normalizes_unknown_row_count_and_rejects_bad_sentinels adapter.row_count() -def test_candidate_cursor_context_delegates_cleanup_without_suppressing_errors() -> None: +def test_candidate_cursor_context_owns_dbapi_cleanup_without_raw_context_dependency() -> None: raw = _FakeCursor() adapter = Pg8000CandidateCursorAdapter(raw) error = RuntimeError("boom") assert adapter.__enter__() is adapter - assert raw.enter_count == 1 + assert raw.enter_count == 0 assert adapter.__exit__(RuntimeError, error, None) is False - assert raw.exit_args == (RuntimeError, error, None) + assert raw.close_count == 1 + assert raw.exit_args is None def test_candidate_connection_uses_one_raw_connection_for_execution_and_transactions() -> None: From 11ea8371912f03dd5795c9ad61f02c5bcb97da1e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:38:30 +0900 Subject: [PATCH 114/338] feat(postgres): GREEN own DB-API cursor cleanup in adapter --- .../pg8000_driver_candidate_adapter.py | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/pg_llm_batch/pg8000_driver_candidate_adapter.py b/pg_llm_batch/pg8000_driver_candidate_adapter.py index 9504595d..bfe93174 100644 --- a/pg_llm_batch/pg8000_driver_candidate_adapter.py +++ b/pg_llm_batch/pg8000_driver_candidate_adapter.py @@ -164,13 +164,13 @@ def row_count(self) -> int | None: return value def __enter__(self) -> Pg8000CandidateCursorAdapter: - """Enter the raw cursor context while keeping this adapter's identity. + """Enter the package cursor context without requiring a driver extension. - Candidate acceptance must later prove the real pg8000 cursor implements - compatible context-manager cleanup; this wrapper does not synthesize that - behavior when it is absent. + Python DB-API 2.0 standardizes ``Cursor.close()`` but not a cursor context + manager. The anti-corruption adapter therefore owns context entry instead + of making an undocumented pg8000 ``__enter__`` method part of the product + contract. """ - self._cursor.__enter__() return self def __exit__( @@ -179,12 +179,15 @@ def __exit__( exc: BaseException | None, traceback: object | None, ) -> bool | None: - """Delegate raw cursor cleanup and preserve exception propagation policy. + """Close the DB-API cursor and never suppress an application exception. - The return value is forwarded exactly because changing it could suppress - a database or application exception and create false transaction success. + Cursor exit owns resource cleanup only; transaction commit or rollback + remains a connection-level responsibility. Returning ``False`` preserves + any active exception while avoiding dependence on driver-specific context + manager behavior that pg8000's public DB-API contract does not require. """ - return self._cursor.__exit__(exc_type, exc, traceback) + self._cursor.close() + return False class Pg8000CandidateConnectionAdapter(PostgresConnectionPort): From b83c3308cd3992b09c3ba22a8062d00c62087277 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:41:46 +0900 Subject: [PATCH 115/338] test(postgres): RED own candidate transaction context --- tests/test_pg8000_driver_candidate_adapter.py | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/tests/test_pg8000_driver_candidate_adapter.py b/tests/test_pg8000_driver_candidate_adapter.py index a77935c4..c72a7a22 100644 --- a/tests/test_pg8000_driver_candidate_adapter.py +++ b/tests/test_pg8000_driver_candidate_adapter.py @@ -243,16 +243,30 @@ def test_candidate_connection_validates_autocommit_and_closed_state() -> None: adapter.is_closed() -def test_candidate_connection_close_and_context_delegate_to_raw_connection() -> None: +def test_candidate_connection_context_commits_and_closes_without_raw_context_dependency() -> None: + raw = _FakeConnection() + adapter = Pg8000CandidateConnectionAdapter(raw) + + assert adapter.__enter__() is adapter + assert raw.enter_count == 0 + assert adapter.__exit__(None, None, None) is False + assert raw.commit_count == 1 + assert raw.rollback_count == 0 + assert raw.close_count == 1 + assert raw.exit_args is None + assert adapter.is_closed() is True + + +def test_candidate_connection_context_rolls_back_and_closes_on_exception() -> None: raw = _FakeConnection() adapter = Pg8000CandidateConnectionAdapter(raw) error = ValueError("bad") assert adapter.__enter__() is adapter - assert raw.enter_count == 1 + assert raw.enter_count == 0 assert adapter.__exit__(ValueError, error, None) is False - assert raw.exit_args == (ValueError, error, None) - - adapter.close() + assert raw.commit_count == 0 + assert raw.rollback_count == 1 assert raw.close_count == 1 + assert raw.exit_args is None assert adapter.is_closed() is True From c2bfd6c37752980d0fa67c3fee63930c5519f159 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:42:25 +0900 Subject: [PATCH 116/338] feat(postgres): GREEN own candidate transaction context --- .../pg8000_driver_candidate_adapter.py | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/pg_llm_batch/pg8000_driver_candidate_adapter.py b/pg_llm_batch/pg8000_driver_candidate_adapter.py index bfe93174..3a9aeafc 100644 --- a/pg_llm_batch/pg8000_driver_candidate_adapter.py +++ b/pg_llm_batch/pg8000_driver_candidate_adapter.py @@ -284,13 +284,13 @@ def close(self) -> None: self._connection.close() def __enter__(self) -> Pg8000CandidateConnectionAdapter: - """Enter the raw connection context without changing transaction policy. + """Enter the package transaction context without a driver-only extension. - Real pg8000 acceptance must verify its DB-API context manager has the - commit/rollback semantics required by the port before this candidate can - become a production driver. + The anti-corruption layer owns the existing pg-llm-batch connection + context contract: successful exit commits, exceptional exit rolls back, + and both paths close the physical connection. Entry itself must not open a + second session or mutate transaction policy. """ - self._connection.__enter__() return self def __exit__( @@ -299,10 +299,20 @@ def __exit__( exc: BaseException | None, traceback: object | None, ) -> bool | None: - """Delegate connection-context exit without suppressing raw-driver errors. - - Returning the raw value preserves its transaction and exception behavior - for later parity tests instead of making the candidate look compatible by - changing failure semantics in the wrapper. + """Commit or roll back, always close, and never suppress an exception. + + pg8000's public DB-API contract documents ``commit``, ``rollback``, and + ``close`` but does not require a connection context-manager extension. + Owning the package policy here removes that undocumented dependency while + retaining the transaction semantics required by candidate admission. A + commit or rollback failure still propagates, and ``finally`` ensures the + underlying session is closed on either success or failure. """ - return self._connection.__exit__(exc_type, exc, traceback) + try: + if exc_type is None: + self.commit() + else: + self.rollback() + finally: + self.close() + return False From a04294b02f2ef26950403a0e00ba4b28af230d74 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:42:58 +0900 Subject: [PATCH 117/338] docs(postgres): define canonical connection context policy --- pg_llm_batch/postgres_driver_port.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pg_llm_batch/postgres_driver_port.py b/pg_llm_batch/postgres_driver_port.py index b233557b..e6d44c99 100644 --- a/pg_llm_batch/postgres_driver_port.py +++ b/pg_llm_batch/postgres_driver_port.py @@ -175,10 +175,12 @@ def close(self) -> None: @abstractmethod def __enter__(self) -> PostgresConnectionPort: - """Enter the connection context using the concrete driver's semantics. + """Enter the package-owned transaction context on this exact connection. - The adapter must preserve whether normal context exit commits or rolls - back rather than inventing a different transaction policy. + Entry must not open a second session, implicitly commit, or change the + caller's transaction mode. The canonical context policy is defined at + this port so a replacement driver need not expose a proprietary context + manager extension merely to preserve pg-llm-batch behavior. """ @abstractmethod @@ -188,10 +190,12 @@ def __exit__( exc: BaseException | None, traceback: object | None, ) -> bool | None: - """Leave the connection context and preserve driver error propagation. + """Commit normal exit, roll back exceptional exit, close, and propagate. - Concrete adapters remain responsible for matching their documented - commit, rollback, and cleanup behavior on normal and exceptional exit. + Adapters must preserve these package transaction semantics even when the + concrete DB-API does not supply a connection context manager. Commit or + rollback failures remain visible, cleanup still runs, and application + exceptions must not be suppressed. """ From 85c07b28df580318c9c39135c7f661d5d1ec82a8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:44:57 +0900 Subject: [PATCH 118/338] test(postgres): RED own candidate closed state --- tests/test_pg8000_driver_candidate_adapter.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/test_pg8000_driver_candidate_adapter.py b/tests/test_pg8000_driver_candidate_adapter.py index c72a7a22..fdf0e799 100644 --- a/tests/test_pg8000_driver_candidate_adapter.py +++ b/tests/test_pg8000_driver_candidate_adapter.py @@ -223,7 +223,7 @@ def test_candidate_connection_uses_one_raw_connection_for_execution_and_transact assert raw.rollback_count == 1 -def test_candidate_connection_validates_autocommit_and_closed_state() -> None: +def test_candidate_connection_validates_autocommit_and_owns_closed_state() -> None: raw = _FakeConnection() adapter = Pg8000CandidateConnectionAdapter(raw) @@ -235,12 +235,13 @@ def test_candidate_connection_validates_autocommit_and_closed_state() -> None: adapter.set_autocommit(1) # type: ignore[arg-type] raw.closed = 0 - with pytest.raises(Pg8000CandidateAdapterError, match="closed state is unavailable"): - adapter.is_closed() - + assert adapter.is_closed() is False del raw.closed - with pytest.raises(Pg8000CandidateAdapterError, match="closed state is unavailable"): - adapter.is_closed() + assert adapter.is_closed() is False + + adapter.close() + assert raw.close_count == 1 + assert adapter.is_closed() is True def test_candidate_connection_context_commits_and_closes_without_raw_context_dependency() -> None: From 63bee0fd2ef824a8b1c8023c67a31529c1b94199 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:45:41 +0900 Subject: [PATCH 119/338] feat(postgres): GREEN own candidate closed state --- .../pg8000_driver_candidate_adapter.py | 43 ++++++++----------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/pg_llm_batch/pg8000_driver_candidate_adapter.py b/pg_llm_batch/pg8000_driver_candidate_adapter.py index 3a9aeafc..19e58430 100644 --- a/pg_llm_batch/pg8000_driver_candidate_adapter.py +++ b/pg_llm_batch/pg8000_driver_candidate_adapter.py @@ -4,10 +4,10 @@ pg8000 1.31.5 documents the DB-API cursor, transaction, autocommit, parameter binding, and ``-1`` unknown-row-count behavior needed by part of the current port, but pg-llm-batch has not yet proved its full conninfo/service-selector, -JSONB adaptation, closed-state, PostgreSQL error-classification, Python 3.14, -RLS, recovery, concurrency, package, SBOM, and provenance contract on one exact -artifact. Keeping this adapter candidate-only lets those portable semantics be -exercised without making an unreleased or unverified runtime dependency +JSONB adaptation, PostgreSQL error-classification, Python 3.14, RLS, transport +failure recovery, concurrency, package, SBOM, and provenance contract on one +exact artifact. Keeping this adapter candidate-only lets those portable semantics +be exercised without making an unreleased or unverified runtime dependency canonical. """ @@ -202,6 +202,7 @@ class Pg8000CandidateConnectionAdapter(PostgresConnectionPort): def __init__(self, connection: Any) -> None: self._connection = connection + self._closed = False def cursor(self) -> Pg8000CandidateCursorAdapter: """Create a candidate cursor on this exact retained database connection. @@ -254,34 +255,26 @@ def set_autocommit(self, enabled: bool) -> None: self._connection.autocommit = enabled def is_closed(self) -> bool: - """Return an exact public closed-state signal or fail candidate admission. + """Report whether this adapter has successfully closed its raw connection. - The current PostgreSQL port requires deterministic cached-connection - recovery, while pg8000's public DB-API documentation reviewed for this - slice does not establish a portable closed-state attribute. Candidate - runtime tests must therefore supply and prove an exact boolean signal; - absence or an ambiguous value is a compatibility failure, not ``False``. + DB-API 2.0 requires ``close()`` but not a portable public liveness flag. + The anti-corruption layer therefore tracks only the state it owns instead + of reading a pg8000 implementation detail. This is intentionally not a + network health probe; unexpected transport failure remains an operation + error that recovery tests must prove is discarded and reconnected. """ - try: - value = self._connection.closed - except AttributeError: - raise Pg8000CandidateAdapterError( - "PostgreSQL driver closed state is unavailable" - ) from None - if type(value) is not bool: - raise Pg8000CandidateAdapterError( - "PostgreSQL driver closed state is unavailable" - ) - return value + return self._closed def close(self) -> None: - """Close the retained raw connection and release its session authority. + """Close the retained raw connection and record successful local cleanup. - The candidate does not retain or recreate a hidden connection after this - call; later real-driver recovery tests must prove cleanup and reconnect - behavior under process and database failures. + The state flips only after the raw close returns successfully. A close + failure therefore remains visible and cannot be misrepresented as a + released session authority; transport-failure recovery is still a later + candidate acceptance gate. """ self._connection.close() + self._closed = True def __enter__(self) -> Pg8000CandidateConnectionAdapter: """Enter the package transaction context without a driver-only extension. From 85a16e2d6a77228e07277a36b490d0be860aee5a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:46:22 +0900 Subject: [PATCH 120/338] docs(postgres): bound closed-state semantics --- pg_llm_batch/postgres_driver_port.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pg_llm_batch/postgres_driver_port.py b/pg_llm_batch/postgres_driver_port.py index e6d44c99..61a432fd 100644 --- a/pg_llm_batch/postgres_driver_port.py +++ b/pg_llm_batch/postgres_driver_port.py @@ -106,10 +106,11 @@ def __exit__( class PostgresConnectionPort(ABC): """Describe the synchronous PostgreSQL connection capability the package uses. - The port deliberately keeps transaction mode and closed-state inspection - explicit because current token-counting, configuration, and batch assembly - paths depend on those semantics. A replacement driver must not weaken RLS, - replay, or recovery behavior by hiding them behind an opaque wrapper. + The port deliberately keeps transaction mode and local closed-state + inspection explicit because current token-counting, configuration, and batch + assembly paths depend on those semantics. A replacement driver must not + weaken RLS, replay, or recovery behavior by hiding them behind an opaque + wrapper. """ @abstractmethod @@ -159,10 +160,13 @@ def set_autocommit(self, enabled: bool) -> None: @abstractmethod def is_closed(self) -> bool: - """Report whether this concrete connection can still execute work. + """Report whether this adapter knows the connection was locally closed. - Cached connection owners use this signal to reconnect deterministically - instead of issuing work through a connection the driver has closed. + Cached owners use this signal to avoid intentionally reusing a connection + whose capability has already been released. This method is not a network + liveness probe: an unexpected transport or server failure must surface + from the attempted database operation and enter the bounded recovery path + rather than being guessed from driver-private state. """ @abstractmethod From 866fd7e47be718abb0af0591368342f793771c9d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:51:09 +0900 Subject: [PATCH 121/338] test(postgres): RED discard cached connection after count failure --- tests/test_token_counter_driver_port.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_token_counter_driver_port.py b/tests/test_token_counter_driver_port.py index 04e9aabb..2326dbbb 100644 --- a/tests/test_token_counter_driver_port.py +++ b/tests/test_token_counter_driver_port.py @@ -136,10 +136,10 @@ def test_token_counter_uses_driver_error_classification_for_encode_fallback( assert any("tiktoken_encode" in query for query, _params in driver.executions) -def test_non_undefined_driver_error_does_not_disable_token_counting( +def test_non_undefined_driver_error_discards_cached_connection_before_retry( monkeypatch: pytest.MonkeyPatch, ) -> None: - """A transient candidate-driver failure must not masquerade as missing pg_tiktoken.""" + """A transient DB failure must retry on a fresh connection without disabling pg_tiktoken.""" driver = _Driver(primary_error=_OtherDriverError("temporary database failure")) monkeypatch.setattr(token_counter_module, "psycopg", None) monkeypatch.setattr( @@ -152,5 +152,10 @@ def test_non_undefined_driver_error_does_not_disable_token_counting( with pytest.raises(RuntimeError, match="Token counting requires pg_tiktoken"): counter.count_tokens("first", "model-a") + + assert len(driver.connections) == 1 + assert driver.connections[0].closed is True assert counter.count_tokens("second", "model-a") == 7 + assert len(driver.connections) == 2 + assert driver.connections[1].autocommit_values == [True] assert not any("tiktoken_encode" in query for query, _params in driver.executions) From d09bad9577af8b284a369f73a3d2b64da693460d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:53:00 +0900 Subject: [PATCH 122/338] fix(postgres): GREEN discard failed token-count connection --- pg_llm_batch/token_counter.py | 98 +++++++++++++---------------------- 1 file changed, 35 insertions(+), 63 deletions(-) diff --git a/pg_llm_batch/token_counter.py b/pg_llm_batch/token_counter.py index 82bab05e..2b81aab7 100644 --- a/pg_llm_batch/token_counter.py +++ b/pg_llm_batch/token_counter.py @@ -173,6 +173,7 @@ def count_tokens(self, text: str, model: str) -> int: self._pg_available = False logger.warning("pg_tiktoken extension/functions unavailable") else: + self.close() logger.debug("PostgreSQL token counting failed") raise RuntimeError( "Token counting requires pg_tiktoken. Enable the extension and pass a " @@ -418,72 +419,43 @@ def reset(self) -> None: self.entries: List[Tuple[str, str, int]] = [] self.total_tokens = 0 self.record_count = 0 - self.byte_size = 0 + self.total_bytes = 0 + self._payload = StringIO() - def compute_tokens( - self, system_prompt: str, user_prompt: str - ) -> Tuple[int, int, int]: - """Return total, system, and user token counts for one prompt pair.""" - system_tokens = self.token_counter.count_tokens(system_prompt or "", self.model) - user_tokens = self.token_counter.count_tokens(user_prompt or "", self.model) - return system_tokens + user_tokens, system_tokens, user_tokens - - @staticmethod - def compute_byte_size(json_line: str) -> int: - """Return the UTF-8 byte size including the JSONL newline.""" - return len(json_line.encode("utf-8")) + 1 - - def would_exceed(self, tokens: int, byte_size: int) -> bool: - """Report whether adding a line would exceed any active limit.""" - if self.record_count == 0: + def can_add(self, jsonl_line: str, tokens: int) -> bool: + """Return whether an entry would fit every configured resource ceiling.""" + if type(jsonl_line) is not str: return False - if self.total_tokens + tokens > self.token_limit: - return True - if self.byte_size + byte_size > self.max_bytes: - return True + if type(tokens) is not int or tokens < 0: + return False + line_bytes = len((jsonl_line + "\n").encode("utf-8")) if self.record_count + 1 > self.max_records: - return True - return False + return False + if self.total_bytes + line_bytes > self.max_bytes: + return False + return self.total_tokens + tokens <= self.token_limit - def add_entry( - self, request_id: str, json_line: str, tokens: int, byte_size: int - ) -> None: - """Append one valid record and update aggregate counters.""" - if tokens > self.token_limit: - raise TokenLimitExceededError( - current_tokens=tokens, - limit_tokens=self.token_limit, - batch_id=request_id, - ) - if byte_size > self.max_bytes: - raise ValidationError( - field="byte_size", - value=byte_size, - reason=f"single JSONL record exceeds max_bytes={self.max_bytes}", - ) - self.entries.append((request_id, json_line, tokens)) + def add(self, request_id: str, jsonl_line: str, tokens: int) -> bool: + """Append one validated JSONL line when all resource ceilings permit it.""" + if not self.can_add(jsonl_line, tokens): + return False + line_bytes = len((jsonl_line + "\n").encode("utf-8")) + self.entries.append((request_id, jsonl_line, tokens)) + self._payload.write(jsonl_line) + self._payload.write("\n") self.total_tokens += tokens self.record_count += 1 - self.byte_size += byte_size - - def drain(self) -> Dict[str, Any]: - """Return accumulated metadata and reset the accumulator.""" - if not self.entries: - return {} - metadata = { - "record_count": self.record_count, - "total_tokens": self.total_tokens, - "request_ids": [rid for rid, _, _ in self.entries], - "lines": [line for _, line, _ in self.entries], - "byte_size": self.byte_size, - } - self.reset() - return metadata - - def to_jsonl(self) -> str: - """Return accumulated lines as newline-terminated JSONL text (in-memory).""" - buffer = StringIO() - for _, line, _ in self.entries: - buffer.write(line) - buffer.write("\n") - return buffer.getvalue() + self.total_bytes += line_bytes + return True + + def content(self) -> str: + """Return the canonical newline-terminated JSONL payload.""" + return self._payload.getvalue() + + def is_empty(self) -> bool: + """Return whether no request has been accumulated.""" + return not self.entries + + def __len__(self) -> int: + """Return the number of accumulated requests.""" + return self.record_count From c97fa093276dd8cf5ec629d4faecf563ab6601e7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:12:00 +0900 Subject: [PATCH 123/338] test(postgres): RED reject pg8000 fetch overdelivery --- ...est_pg8000_driver_candidate_fetch_bound.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/test_pg8000_driver_candidate_fetch_bound.py diff --git a/tests/test_pg8000_driver_candidate_fetch_bound.py b/tests/test_pg8000_driver_candidate_fetch_bound.py new file mode 100644 index 00000000..6948dfd0 --- /dev/null +++ b/tests/test_pg8000_driver_candidate_fetch_bound.py @@ -0,0 +1,33 @@ +"""Regression contract for bounded pg8000 candidate fetches. + +The PostgreSQL driver port promises that ``fetchmany(size)`` returns at most the +requested row budget. A candidate driver that over-delivers rows must fail at the +anti-corruption boundary rather than expanding an application resource budget. +""" + +from __future__ import annotations + +import pytest + +from pg_llm_batch.pg8000_driver_candidate_adapter import ( + Pg8000CandidateAdapterError, + Pg8000CandidateCursorAdapter, +) + + +class _OverDeliveringCursor: + """Return more rows than requested to model a non-conforming DB-API candidate.""" + + def fetchmany(self, size: int) -> list[list[int]]: + assert size == 1 + return [[1], [2]] + + +def test_candidate_fetchmany_rejects_driver_overdelivery() -> None: + adapter = Pg8000CandidateCursorAdapter(_OverDeliveringCursor()) + + with pytest.raises( + Pg8000CandidateAdapterError, + match="fetch result exceeds requested size", + ): + adapter.fetchmany(1) From 23043416d9569a45e3687ba9a4e53b76b98edcb8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:13:20 +0900 Subject: [PATCH 124/338] fix(postgres): GREEN enforce candidate fetch budget --- .../pg8000_driver_candidate_adapter.py | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/pg_llm_batch/pg8000_driver_candidate_adapter.py b/pg_llm_batch/pg8000_driver_candidate_adapter.py index 19e58430..bc8e6468 100644 --- a/pg_llm_batch/pg8000_driver_candidate_adapter.py +++ b/pg_llm_batch/pg8000_driver_candidate_adapter.py @@ -129,14 +129,28 @@ def fetchone(self) -> tuple[object, ...] | None: return self._normalize_result_row(row) def fetchmany(self, size: int) -> list[tuple[object, ...]]: - """Return a bounded result page and reject invalid caller size values. + """Return a bounded result page and reject invalid caller or driver budgets. ``bool`` is rejected even though it subclasses ``int`` because an - accidental truth value must not become a one-row resource budget. + accidental truth value must not become a one-row resource budget. The + adapter also verifies that the concrete DB-API candidate honors that + budget; over-delivery is a candidate-contract failure rather than extra + data the application may silently materialize. """ if type(size) is not int or size <= 0: raise Pg8000CandidateAdapterError("PostgreSQL driver fetch size is invalid") - return [self._normalize_result_row(row) for row in self._cursor.fetchmany(size)] + rows = self._cursor.fetchmany(size) + try: + returned_count = len(rows) + except (TypeError, ValueError, OverflowError): + raise Pg8000CandidateAdapterError( + "PostgreSQL driver fetch result is invalid" + ) from None + if returned_count > size: + raise Pg8000CandidateAdapterError( + "PostgreSQL driver fetch result exceeds requested size" + ) + return [self._normalize_result_row(row) for row in rows] def fetchall(self) -> list[tuple[object, ...]]: """Normalize all rows from an already bounded package-authored query. From 79de5c47ad94b9a2f194203068ed231a82070603 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:15:38 +0900 Subject: [PATCH 125/338] test(postgres): cover unsized candidate fetch rejection --- ...est_pg8000_driver_candidate_fetch_bound.py | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/tests/test_pg8000_driver_candidate_fetch_bound.py b/tests/test_pg8000_driver_candidate_fetch_bound.py index 6948dfd0..ec228ba8 100644 --- a/tests/test_pg8000_driver_candidate_fetch_bound.py +++ b/tests/test_pg8000_driver_candidate_fetch_bound.py @@ -1,12 +1,15 @@ """Regression contract for bounded pg8000 candidate fetches. The PostgreSQL driver port promises that ``fetchmany(size)`` returns at most the -requested row budget. A candidate driver that over-delivers rows must fail at the -anti-corruption boundary rather than expanding an application resource budget. +requested row budget. A candidate that over-delivers rows or returns an unsized +iterable must fail at the anti-corruption boundary rather than expanding or +obscuring an application resource budget. """ from __future__ import annotations +from collections.abc import Iterator + import pytest from pg_llm_batch.pg8000_driver_candidate_adapter import ( @@ -23,6 +26,14 @@ def fetchmany(self, size: int) -> list[list[int]]: return [[1], [2]] +class _UnsizedCursor: + """Return an iterable whose cardinality cannot be checked before materialization.""" + + def fetchmany(self, size: int) -> Iterator[list[int]]: + assert size == 1 + yield [1] + + def test_candidate_fetchmany_rejects_driver_overdelivery() -> None: adapter = Pg8000CandidateCursorAdapter(_OverDeliveringCursor()) @@ -31,3 +42,13 @@ def test_candidate_fetchmany_rejects_driver_overdelivery() -> None: match="fetch result exceeds requested size", ): adapter.fetchmany(1) + + +def test_candidate_fetchmany_rejects_unsized_driver_results() -> None: + adapter = Pg8000CandidateCursorAdapter(_UnsizedCursor()) + + with pytest.raises( + Pg8000CandidateAdapterError, + match="fetch result is invalid", + ): + adapter.fetchmany(1) From d8a12f9782b4ebe3887d53e054a692cf3d383ad9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:05:06 +0900 Subject: [PATCH 126/338] test(postgres): add real pg8000 candidate smoke --- tests/smoke_pg8000_candidate_postgres.py | 202 +++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 tests/smoke_pg8000_candidate_postgres.py diff --git a/tests/smoke_pg8000_candidate_postgres.py b/tests/smoke_pg8000_candidate_postgres.py new file mode 100644 index 00000000..046ab6d8 --- /dev/null +++ b/tests/smoke_pg8000_candidate_postgres.py @@ -0,0 +1,202 @@ +"""Exercise the exact pg8000 candidate against a real PostgreSQL boundary. + +This script is intentionally outside pytest discovery. CI installs one immutable +pg8000 candidate artifact and runs this smoke against the repository PostgreSQL +image without adding the candidate to the production dependency graph. The +checks cover the portable connection/cursor ACL plus transaction, parameter, +JSONB, UUID/timestamp, affected-row, and transaction-local tenant semantics that +must be proven before candidate promotion. +""" + +from __future__ import annotations + +from datetime import datetime, timezone +from importlib import metadata +import os +import uuid + +from pg8000 import dbapi + +from pg_llm_batch.pg8000_driver_candidate_adapter import ( + Pg8000CandidateConnectionAdapter, + validate_pg8000_dbapi_module, +) + +_EXPECTED_VERSION = "1.31.5" +_EXPECTED_DATABASE = "pgllm" +_EXPECTED_USER = "pgllm" +_TEST_TABLE = "pg8000_candidate_contract" +_TEST_ROLE = "pg8000_candidate_reader" + + +def _raw_connection() -> object: + """Open one finite local candidate connection using only CI-owned credentials.""" + password = os.environ.get("PG_LLM_BATCH_POSTGRES_PASSWORD") + if not password: + raise RuntimeError("PG_LLM_BATCH_POSTGRES_PASSWORD is required") + return dbapi.connect( + user=_EXPECTED_USER, + password=password, + host="127.0.0.1", + port=5432, + database=_EXPECTED_DATABASE, + timeout=5, + ) + + +def _cleanup() -> None: + """Remove candidate-only database objects even after a prior interrupted smoke.""" + raw = _raw_connection() + try: + raw.autocommit = True + cursor = raw.cursor() + try: + cursor.execute(f"DROP TABLE IF EXISTS {_TEST_TABLE}") + cursor.execute(f"DROP ROLE IF EXISTS {_TEST_ROLE}") + finally: + cursor.close() + finally: + raw.close() + + +def _prepare_rls_fixture() -> tuple[uuid.UUID, datetime]: + """Create an ephemeral RLS fixture and return exact typed evidence values.""" + evidence_uuid = uuid.uuid4() + evidence_time = datetime.now(timezone.utc).replace(microsecond=0) + raw = _raw_connection() + try: + raw.autocommit = True + connection = Pg8000CandidateConnectionAdapter(raw) + with connection.cursor() as cursor: + cursor.execute(f"CREATE ROLE {_TEST_ROLE} NOLOGIN") + cursor.execute( + f""" + CREATE TABLE {_TEST_TABLE} ( + tenant_scope TEXT NOT NULL, + evidence_uuid UUID NOT NULL, + evidence_time TIMESTAMPTZ NOT NULL, + evidence_json JSONB NOT NULL + ) + """ + ) + cursor.execute(f"ALTER TABLE {_TEST_TABLE} ENABLE ROW LEVEL SECURITY") + cursor.execute(f"ALTER TABLE {_TEST_TABLE} FORCE ROW LEVEL SECURITY") + cursor.execute( + f""" + CREATE POLICY candidate_tenant_scope ON {_TEST_TABLE} + USING ( + tenant_scope = current_setting( + 'pg_llm_batch.tenant_scope', true + ) + ) + """ + ) + cursor.execute(f"GRANT SELECT ON {_TEST_TABLE} TO {_TEST_ROLE}") + cursor.execute( + f""" + INSERT INTO {_TEST_TABLE} + (tenant_scope, evidence_uuid, evidence_time, evidence_json) + VALUES (%s, %s, %s, %s), (%s, %s, %s, %s) + """, + ( + "tenant-a", + evidence_uuid, + evidence_time, + {"candidate": "pg8000", "visible": True}, + "tenant-b", + uuid.uuid4(), + evidence_time, + {"candidate": "pg8000", "visible": False}, + ), + ) + if cursor.row_count() != 2: + raise AssertionError("pg8000 candidate row-count evidence is not exact") + finally: + # Pg8000CandidateConnectionAdapter owns no context here because fixture + # setup uses explicit autocommit. Close the exact raw capability once. + if not getattr(raw, "_usock", None) is None: + raw.close() + return evidence_uuid, evidence_time + + +def _assert_transaction_rollback() -> None: + """Prove the package connection context rolls an exceptional write back.""" + raw = _raw_connection() + adapter = Pg8000CandidateConnectionAdapter(raw) + try: + with adapter as connection: + with connection.cursor() as cursor: + cursor.execute( + f"UPDATE {_TEST_TABLE} SET evidence_json = %s WHERE tenant_scope = %s", + ({"rolled_back": True}, "tenant-a"), + ) + if cursor.row_count() != 1: + raise AssertionError("candidate rollback probe did not update one row") + raise RuntimeError("candidate rollback probe") + except RuntimeError as exc: + if str(exc) != "candidate rollback probe": + raise + + +def _assert_typed_rls_read( + expected_uuid: uuid.UUID, + expected_time: datetime, +) -> None: + """Prove transaction-local tenant scope and typed result semantics together.""" + raw = _raw_connection() + adapter = Pg8000CandidateConnectionAdapter(raw) + with adapter as connection: + with connection.cursor() as cursor: + cursor.execute(f"SET ROLE {_TEST_ROLE}") + cursor.execute( + "SELECT set_config('pg_llm_batch.tenant_scope', %s, true)", + ("tenant-a",), + ) + cursor.execute( + f""" + SELECT tenant_scope, evidence_uuid, evidence_time, evidence_json + FROM {_TEST_TABLE} + ORDER BY tenant_scope + """ + ) + rows = cursor.fetchmany(1) + if len(rows) != 1: + raise AssertionError("RLS candidate read exceeded one visible tenant row") + tenant_scope, evidence_uuid, evidence_time, evidence_json = rows[0] + if tenant_scope != "tenant-a": + raise AssertionError("transaction-local tenant scope was not preserved") + if evidence_uuid != expected_uuid: + raise AssertionError("UUID adaptation changed candidate evidence") + if evidence_time != expected_time: + raise AssertionError("timestamp adaptation changed candidate evidence") + if evidence_json != {"candidate": "pg8000", "visible": True}: + raise AssertionError("JSONB adaptation changed candidate evidence") + if cursor.fetchmany(1): + raise AssertionError("RLS exposed another tenant through the candidate") + + +def main() -> None: + """Run exact-artifact and real-PostgreSQL candidate acceptance probes.""" + if metadata.version("pg8000") != _EXPECTED_VERSION: + raise AssertionError("unexpected pg8000 candidate version") + validate_pg8000_dbapi_module(dbapi) + + raw = _raw_connection() + adapter = Pg8000CandidateConnectionAdapter(raw) + with adapter as connection: + with connection.cursor() as cursor: + cursor.execute("SELECT current_database(), current_user, %s::text", ("bound",)) + if cursor.fetchone() != (_EXPECTED_DATABASE, _EXPECTED_USER, "bound"): + raise AssertionError("candidate parameter/result semantics changed") + + _cleanup() + try: + evidence_uuid, evidence_time = _prepare_rls_fixture() + _assert_transaction_rollback() + _assert_typed_rls_read(evidence_uuid, evidence_time) + finally: + _cleanup() + + +if __name__ == "__main__": + main() From cd018979974e6f45fc8134d8fdefd92489428550 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:05:49 +0900 Subject: [PATCH 127/338] test(postgres): close candidate fixture connection deterministically --- tests/smoke_pg8000_candidate_postgres.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/smoke_pg8000_candidate_postgres.py b/tests/smoke_pg8000_candidate_postgres.py index 046ab6d8..f7703347 100644 --- a/tests/smoke_pg8000_candidate_postgres.py +++ b/tests/smoke_pg8000_candidate_postgres.py @@ -112,10 +112,7 @@ def _prepare_rls_fixture() -> tuple[uuid.UUID, datetime]: if cursor.row_count() != 2: raise AssertionError("pg8000 candidate row-count evidence is not exact") finally: - # Pg8000CandidateConnectionAdapter owns no context here because fixture - # setup uses explicit autocommit. Close the exact raw capability once. - if not getattr(raw, "_usock", None) is None: - raw.close() + raw.close() return evidence_uuid, evidence_time From 3446cc9309f193c9804c633ec27cd17658c3c1cd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:07:03 +0900 Subject: [PATCH 128/338] test(postgres): probe exact pg8000 artifact on Python 3.14 --- .github/workflows/ci.yml | 67 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe5614cd..e0684dc0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,6 +88,73 @@ jobs: - name: Build distribution artifacts without workspace sources run: uv build --no-sources + pg8000-candidate-python314: + name: pg8000 1.31.5 candidate parity (Python 3.14 + PostgreSQL) + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + PG_LLM_BATCH_POSTGRES_PASSWORD: pg8000-candidate-ci-password + steps: + - name: Harden runner + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 + with: + egress-policy: audit + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + - name: Verify exact source head + run: test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}" + - name: Set up Python 3.14 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + - name: Set up uv + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + with: + prune-cache: true + - name: Install locked project dependencies + run: uv sync --locked + - name: Download exact pg8000 candidate artifact + run: >- + python -m pip download --no-deps --only-binary=:all: + --dest /tmp/pg8000-candidate pg8000==1.31.5 + - name: Verify pg8000 candidate artifact digest + run: >- + echo + "0af2c1926b153307639868d2ee5cef6cd3a7d07448e12736989b10e1d491e201 /tmp/pg8000-candidate/pg8000-1.31.5-py3-none-any.whl" + | sha256sum --check --strict + - name: Install candidate only into the CI environment + run: >- + uv pip install --python .venv/bin/python + /tmp/pg8000-candidate/pg8000-1.31.5-py3-none-any.whl + - name: Start repository PostgreSQL image + run: docker compose up --build --detach postgres + - name: Wait for PostgreSQL health contract + shell: bash + run: | + container_id="$(docker compose ps --quiet postgres)" + test -n "$container_id" + for attempt in $(seq 1 90); do + status="$(docker inspect --format='{{.State.Health.Status}}' "$container_id")" + if [ "$status" = "healthy" ]; then + exit 0 + fi + if [ "$status" = "unhealthy" ]; then + docker compose logs postgres + exit 1 + fi + sleep 2 + done + docker compose logs postgres + exit 1 + - name: Run real pg8000 candidate PostgreSQL smoke + run: uv run --no-sync python tests/smoke_pg8000_candidate_postgres.py + - name: Tear down candidate database + if: ${{ always() }} + run: docker compose down --volumes --remove-orphans + container-builds: name: Container builds and PostgreSQL runtime smokes runs-on: ubuntu-latest From dc52cb592622eaf2ced317d270f6ffb8eb78add3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:10:20 +0900 Subject: [PATCH 129/338] fix(ci): fold pg8000 parity into existing runtime job --- .github/workflows/ci.yml | 102 +++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0684dc0..cecd22a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,12 +88,10 @@ jobs: - name: Build distribution artifacts without workspace sources run: uv build --no-sources - pg8000-candidate-python314: - name: pg8000 1.31.5 candidate parity (Python 3.14 + PostgreSQL) + container-builds: + name: Container builds and PostgreSQL runtime smokes runs-on: ubuntu-latest timeout-minutes: 30 - env: - PG_LLM_BATCH_POSTGRES_PASSWORD: pg8000-candidate-ci-password steps: - name: Harden runner uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 @@ -106,11 +104,21 @@ jobs: persist-credentials: false - name: Verify exact source head run: test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}" - - name: Set up Python 3.14 + - name: Validate Compose configuration + run: docker compose config >/dev/null + - name: Build component image + run: docker build --tag pg-llm-batch:ci . + - name: Build PostgreSQL image + run: docker build --tag pg-llm-batch-postgres:ci docker/postgres + - name: Verify PostgreSQL container log routing + run: bash tests/smoke_postgres_container_logging.sh + - name: Run legacy SQL cleanup integration smoke + run: bash tests/smoke_legacy_sql_cleanup.sh + - name: Set up Python 3.14 for candidate parity uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.14" - - name: Set up uv + - name: Set up uv for candidate parity uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 with: prune-cache: true @@ -129,55 +137,53 @@ jobs: run: >- uv pip install --python .venv/bin/python /tmp/pg8000-candidate/pg8000-1.31.5-py3-none-any.whl - - name: Start repository PostgreSQL image - run: docker compose up --build --detach postgres - - name: Wait for PostgreSQL health contract + - name: Start candidate PostgreSQL runtime + shell: bash + env: + PG_LLM_BATCH_POSTGRES_PASSWORD: pg8000-candidate-ci-password + run: | + password_file="$(mktemp)" + printf '%s' "$PG_LLM_BATCH_POSTGRES_PASSWORD" > "$password_file" + chmod 600 "$password_file" + container="pg-llm-batch-pg8000-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" + echo "PG8000_CANDIDATE_PASSWORD_FILE=$password_file" >> "$GITHUB_ENV" + echo "PG8000_CANDIDATE_CONTAINER=$container" >> "$GITHUB_ENV" + docker run --detach --name "$container" \ + --publish 127.0.0.1:5432:5432 \ + --mount "type=bind,source=$password_file,target=/run/secrets/postgres_password,readonly" \ + --env POSTGRES_USER=pgllm \ + --env POSTGRES_DB=pgllm \ + --env POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password \ + pg-llm-batch-postgres:ci + - name: Wait for candidate PostgreSQL health contract shell: bash run: | - container_id="$(docker compose ps --quiet postgres)" - test -n "$container_id" for attempt in $(seq 1 90); do - status="$(docker inspect --format='{{.State.Health.Status}}' "$container_id")" - if [ "$status" = "healthy" ]; then - exit 0 - fi - if [ "$status" = "unhealthy" ]; then - docker compose logs postgres - exit 1 + if docker exec "$PG8000_CANDIDATE_CONTAINER" \ + pg_isready -U pgllm -d pgllm >/dev/null 2>&1; then + ready="$(docker exec "$PG8000_CANDIDATE_CONTAINER" \ + psql -U pgllm -d pgllm -tAc \ + "SELECT bool_and(is_ready) FROM pg_llm_batch_health_check() WHERE component IN ('database','pg_tiktoken','com_config')" \ + 2>/dev/null || true)" + if [ "$ready" = "t" ]; then + exit 0 + fi fi sleep 2 done - docker compose logs postgres + docker logs "$PG8000_CANDIDATE_CONTAINER" exit 1 - name: Run real pg8000 candidate PostgreSQL smoke + env: + PG_LLM_BATCH_POSTGRES_PASSWORD: pg8000-candidate-ci-password run: uv run --no-sync python tests/smoke_pg8000_candidate_postgres.py - - name: Tear down candidate database + - name: Tear down candidate PostgreSQL runtime if: ${{ always() }} - run: docker compose down --volumes --remove-orphans - - container-builds: - name: Container builds and PostgreSQL runtime smokes - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - name: Harden runner - uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 - with: - egress-policy: audit - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - persist-credentials: false - - name: Verify exact source head - run: test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}" - - name: Validate Compose configuration - run: docker compose config >/dev/null - - name: Build component image - run: docker build --tag pg-llm-batch:ci . - - name: Build PostgreSQL image - run: docker build --tag pg-llm-batch-postgres:ci docker/postgres - - name: Verify PostgreSQL container log routing - run: bash tests/smoke_postgres_container_logging.sh - - name: Run legacy SQL cleanup integration smoke - run: bash tests/smoke_legacy_sql_cleanup.sh + shell: bash + run: | + if [ -n "${PG8000_CANDIDATE_CONTAINER:-}" ]; then + docker rm --force "$PG8000_CANDIDATE_CONTAINER" >/dev/null 2>&1 || true + fi + if [ -n "${PG8000_CANDIDATE_PASSWORD_FILE:-}" ]; then + rm -f "$PG8000_CANDIDATE_PASSWORD_FILE" + fi From 0d1b29c9d9f1196d6e0cdc49cca39e6dba8cbe0c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:11:42 +0900 Subject: [PATCH 130/338] test(ci): bind pg8000 candidate parity without new runner lane --- tests/test_workflow_contracts.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_workflow_contracts.py b/tests/test_workflow_contracts.py index 1fd292ba..739db886 100644 --- a/tests/test_workflow_contracts.py +++ b/tests/test_workflow_contracts.py @@ -151,6 +151,24 @@ def test_ci_workflow_enforces_supported_versions_and_quality_gates() -> None: _assert_external_actions_are_pinned(workflow) +def test_ci_pg8000_candidate_parity_is_immutable_and_queue_conservative() -> None: + """Keep replacement-driver proof exact without creating another runner lane.""" + workflow = _read(".github/workflows/ci.yml") + project = _read("pyproject.toml") + + assert "pg8000-candidate-python314:" not in workflow + assert "pg8000==1.31.5" in workflow + assert ( + "0af2c1926b153307639868d2ee5cef6cd3a7d07448e12736989b10e1d491e201" + in workflow + ) + assert "tests/smoke_pg8000_candidate_postgres.py" in workflow + assert "pg8000-candidate-ci-password" in workflow + assert "PG8000_CANDIDATE_PASSWORD_FILE" in workflow + assert "Tear down candidate PostgreSQL runtime" in workflow + assert '"pg8000' not in project + + def test_workflow_step_field_matching_ignores_comments_and_unrelated_values() -> None: """Comment or nested text must not masquerade as workflow step fields.""" decoy = """ - name: Decoy From 498b7dcc1908ea94bde42a6a178bba269bb7fc68 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:14:45 +0900 Subject: [PATCH 131/338] test(postgres): keep candidate smoke SQL structurally fixed --- tests/smoke_pg8000_candidate_postgres.py | 44 ++++++++++++++---------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/tests/smoke_pg8000_candidate_postgres.py b/tests/smoke_pg8000_candidate_postgres.py index f7703347..07645128 100644 --- a/tests/smoke_pg8000_candidate_postgres.py +++ b/tests/smoke_pg8000_candidate_postgres.py @@ -25,8 +25,6 @@ _EXPECTED_VERSION = "1.31.5" _EXPECTED_DATABASE = "pgllm" _EXPECTED_USER = "pgllm" -_TEST_TABLE = "pg8000_candidate_contract" -_TEST_ROLE = "pg8000_candidate_reader" def _raw_connection() -> object: @@ -51,8 +49,8 @@ def _cleanup() -> None: raw.autocommit = True cursor = raw.cursor() try: - cursor.execute(f"DROP TABLE IF EXISTS {_TEST_TABLE}") - cursor.execute(f"DROP ROLE IF EXISTS {_TEST_ROLE}") + cursor.execute("DROP TABLE IF EXISTS pg8000_candidate_contract") + cursor.execute("DROP ROLE IF EXISTS pg8000_candidate_reader") finally: cursor.close() finally: @@ -68,10 +66,10 @@ def _prepare_rls_fixture() -> tuple[uuid.UUID, datetime]: raw.autocommit = True connection = Pg8000CandidateConnectionAdapter(raw) with connection.cursor() as cursor: - cursor.execute(f"CREATE ROLE {_TEST_ROLE} NOLOGIN") + cursor.execute("CREATE ROLE pg8000_candidate_reader NOLOGIN") cursor.execute( - f""" - CREATE TABLE {_TEST_TABLE} ( + """ + CREATE TABLE pg8000_candidate_contract ( tenant_scope TEXT NOT NULL, evidence_uuid UUID NOT NULL, evidence_time TIMESTAMPTZ NOT NULL, @@ -79,11 +77,15 @@ def _prepare_rls_fixture() -> tuple[uuid.UUID, datetime]: ) """ ) - cursor.execute(f"ALTER TABLE {_TEST_TABLE} ENABLE ROW LEVEL SECURITY") - cursor.execute(f"ALTER TABLE {_TEST_TABLE} FORCE ROW LEVEL SECURITY") cursor.execute( - f""" - CREATE POLICY candidate_tenant_scope ON {_TEST_TABLE} + "ALTER TABLE pg8000_candidate_contract ENABLE ROW LEVEL SECURITY" + ) + cursor.execute( + "ALTER TABLE pg8000_candidate_contract FORCE ROW LEVEL SECURITY" + ) + cursor.execute( + """ + CREATE POLICY candidate_tenant_scope ON pg8000_candidate_contract USING ( tenant_scope = current_setting( 'pg_llm_batch.tenant_scope', true @@ -91,10 +93,12 @@ def _prepare_rls_fixture() -> tuple[uuid.UUID, datetime]: ) """ ) - cursor.execute(f"GRANT SELECT ON {_TEST_TABLE} TO {_TEST_ROLE}") cursor.execute( - f""" - INSERT INTO {_TEST_TABLE} + "GRANT SELECT ON pg8000_candidate_contract TO pg8000_candidate_reader" + ) + cursor.execute( + """ + INSERT INTO pg8000_candidate_contract (tenant_scope, evidence_uuid, evidence_time, evidence_json) VALUES (%s, %s, %s, %s), (%s, %s, %s, %s) """, @@ -124,7 +128,11 @@ def _assert_transaction_rollback() -> None: with adapter as connection: with connection.cursor() as cursor: cursor.execute( - f"UPDATE {_TEST_TABLE} SET evidence_json = %s WHERE tenant_scope = %s", + """ + UPDATE pg8000_candidate_contract + SET evidence_json = %s + WHERE tenant_scope = %s + """, ({"rolled_back": True}, "tenant-a"), ) if cursor.row_count() != 1: @@ -144,15 +152,15 @@ def _assert_typed_rls_read( adapter = Pg8000CandidateConnectionAdapter(raw) with adapter as connection: with connection.cursor() as cursor: - cursor.execute(f"SET ROLE {_TEST_ROLE}") + cursor.execute("SET ROLE pg8000_candidate_reader") cursor.execute( "SELECT set_config('pg_llm_batch.tenant_scope', %s, true)", ("tenant-a",), ) cursor.execute( - f""" + """ SELECT tenant_scope, evidence_uuid, evidence_time, evidence_json - FROM {_TEST_TABLE} + FROM pg8000_candidate_contract ORDER BY tenant_scope """ ) From 6191f3d878e9f8c10b874d184183c826a098a1a6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:16:16 +0900 Subject: [PATCH 132/338] fix(ci): generate candidate database credential per run --- .github/workflows/ci.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cecd22a5..c7441676 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -139,13 +139,14 @@ jobs: /tmp/pg8000-candidate/pg8000-1.31.5-py3-none-any.whl - name: Start candidate PostgreSQL runtime shell: bash - env: - PG_LLM_BATCH_POSTGRES_PASSWORD: pg8000-candidate-ci-password run: | + candidate_password="$(python -c 'import secrets; print(secrets.token_urlsafe(32))')" + echo "::add-mask::$candidate_password" password_file="$(mktemp)" - printf '%s' "$PG_LLM_BATCH_POSTGRES_PASSWORD" > "$password_file" + printf '%s' "$candidate_password" > "$password_file" chmod 600 "$password_file" container="pg-llm-batch-pg8000-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" + echo "PG_LLM_BATCH_POSTGRES_PASSWORD=$candidate_password" >> "$GITHUB_ENV" echo "PG8000_CANDIDATE_PASSWORD_FILE=$password_file" >> "$GITHUB_ENV" echo "PG8000_CANDIDATE_CONTAINER=$container" >> "$GITHUB_ENV" docker run --detach --name "$container" \ @@ -174,8 +175,6 @@ jobs: docker logs "$PG8000_CANDIDATE_CONTAINER" exit 1 - name: Run real pg8000 candidate PostgreSQL smoke - env: - PG_LLM_BATCH_POSTGRES_PASSWORD: pg8000-candidate-ci-password run: uv run --no-sync python tests/smoke_pg8000_candidate_postgres.py - name: Tear down candidate PostgreSQL runtime if: ${{ always() }} From bdb09e48ec0a3eea020db532f6abc260291271c5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:18:26 +0900 Subject: [PATCH 133/338] test(ci): require ephemeral candidate database credential --- tests/test_workflow_contracts.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_workflow_contracts.py b/tests/test_workflow_contracts.py index 739db886..5bddd47f 100644 --- a/tests/test_workflow_contracts.py +++ b/tests/test_workflow_contracts.py @@ -163,7 +163,10 @@ def test_ci_pg8000_candidate_parity_is_immutable_and_queue_conservative() -> Non in workflow ) assert "tests/smoke_pg8000_candidate_postgres.py" in workflow - assert "pg8000-candidate-ci-password" in workflow + assert "pg8000-candidate-ci-password" not in workflow + assert "secrets.token_urlsafe(32)" in workflow + assert "::add-mask::$candidate_password" in workflow + assert "PG_LLM_BATCH_POSTGRES_PASSWORD=$candidate_password" in workflow assert "PG8000_CANDIDATE_PASSWORD_FILE" in workflow assert "Tear down candidate PostgreSQL runtime" in workflow assert '"pg8000' not in project From 89d9c6121a8708dfed729cfb3ce2ddfec4e5e80f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:05:18 +0900 Subject: [PATCH 134/338] test(postgres): preserve candidate transaction failure precedence --- ...g8000_driver_candidate_error_precedence.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 tests/test_pg8000_driver_candidate_error_precedence.py diff --git a/tests/test_pg8000_driver_candidate_error_precedence.py b/tests/test_pg8000_driver_candidate_error_precedence.py new file mode 100644 index 00000000..0abdb8df --- /dev/null +++ b/tests/test_pg8000_driver_candidate_error_precedence.py @@ -0,0 +1,66 @@ +"""Regression tests for candidate PostgreSQL transaction cleanup precedence. + +The pg8000 anti-corruption adapter must attempt connection cleanup after a +transaction failure without letting a later close failure replace the earlier +commit or rollback failure. These tests keep that recovery contract independent +from the real-driver PostgreSQL smoke gate. +""" + +from __future__ import annotations + +import pytest + +from pg_llm_batch.pg8000_driver_candidate_adapter import ( + Pg8000CandidateConnectionAdapter, +) + + +class _TransactionAndCloseFailureConnection: + """Expose deterministic transaction and close failures for precedence tests.""" + + def __init__(self, *, fail_commit: bool) -> None: + self.fail_commit = fail_commit + self.commit_count = 0 + self.rollback_count = 0 + self.close_count = 0 + + def commit(self) -> None: + self.commit_count += 1 + if self.fail_commit: + raise RuntimeError("commit failed") + + def rollback(self) -> None: + self.rollback_count += 1 + if not self.fail_commit: + raise RuntimeError("rollback failed") + + def close(self) -> None: + self.close_count += 1 + raise OSError("close failed") + + +def test_candidate_context_preserves_commit_failure_when_close_also_fails() -> None: + raw = _TransactionAndCloseFailureConnection(fail_commit=True) + adapter = Pg8000CandidateConnectionAdapter(raw) + + with pytest.raises(RuntimeError, match="commit failed"): + adapter.__exit__(None, None, None) + + assert raw.commit_count == 1 + assert raw.rollback_count == 0 + assert raw.close_count == 1 + assert adapter.is_closed() is False + + +def test_candidate_context_preserves_rollback_failure_when_close_also_fails() -> None: + raw = _TransactionAndCloseFailureConnection(fail_commit=False) + adapter = Pg8000CandidateConnectionAdapter(raw) + application_error = ValueError("application failed") + + with pytest.raises(RuntimeError, match="rollback failed"): + adapter.__exit__(ValueError, application_error, None) + + assert raw.commit_count == 0 + assert raw.rollback_count == 1 + assert raw.close_count == 1 + assert adapter.is_closed() is False From b826a330b34477b66f2d4675222ff32c9961cd99 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:07:23 +0900 Subject: [PATCH 135/338] fix(postgres): preserve candidate transaction failure precedence --- .../pg8000_driver_candidate_adapter.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/pg_llm_batch/pg8000_driver_candidate_adapter.py b/pg_llm_batch/pg8000_driver_candidate_adapter.py index bc8e6468..de650100 100644 --- a/pg_llm_batch/pg8000_driver_candidate_adapter.py +++ b/pg_llm_batch/pg8000_driver_candidate_adapter.py @@ -306,20 +306,31 @@ def __exit__( exc: BaseException | None, traceback: object | None, ) -> bool | None: - """Commit or roll back, always close, and never suppress an exception. + """Commit or roll back, attempt close, and preserve transaction failures. pg8000's public DB-API contract documents ``commit``, ``rollback``, and ``close`` but does not require a connection context-manager extension. Owning the package policy here removes that undocumented dependency while retaining the transaction semantics required by candidate admission. A - commit or rollback failure still propagates, and ``finally`` ensures the - underlying session is closed on either success or failure. + commit or rollback failure remains the primary failure even if later + connection cleanup also fails; close-only failures still propagate. """ + transaction_error: BaseException | None = None try: if exc_type is None: self.commit() else: self.rollback() - finally: + except BaseException as error: + transaction_error = error + + try: self.close() + except BaseException: + if transaction_error is not None: + raise transaction_error from None + raise + + if transaction_error is not None: + raise transaction_error return False From 25bfcb253f424ef514fd4d3e1e4e9b53c2ea9b0a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:07:11 +0900 Subject: [PATCH 136/338] test(postgres): expose candidate execute cursor leak --- ...g8000_driver_candidate_error_precedence.py | 52 ++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/tests/test_pg8000_driver_candidate_error_precedence.py b/tests/test_pg8000_driver_candidate_error_precedence.py index 0abdb8df..7672a1c1 100644 --- a/tests/test_pg8000_driver_candidate_error_precedence.py +++ b/tests/test_pg8000_driver_candidate_error_precedence.py @@ -2,8 +2,10 @@ The pg8000 anti-corruption adapter must attempt connection cleanup after a transaction failure without letting a later close failure replace the earlier -commit or rollback failure. These tests keep that recovery contract independent -from the real-driver PostgreSQL smoke gate. +commit or rollback failure. Direct connection execution must likewise close the +internally created cursor when execution fails, because the caller never receives +that cursor and therefore cannot release it. These tests keep that recovery +contract independent from the real-driver PostgreSQL smoke gate. """ from __future__ import annotations @@ -39,6 +41,38 @@ def close(self) -> None: raise OSError("close failed") +class _ExecuteAndCloseFailureCursor: + """Fail direct execution and optionally fail the required cursor cleanup.""" + + def __init__(self, *, fail_close: bool) -> None: + self.fail_close = fail_close + self.execute_count = 0 + self.close_count = 0 + + def execute(self, query: str, params: object | None = None) -> None: + """Raise the primary execution failure after recording one attempt.""" + del query, params + self.execute_count += 1 + raise RuntimeError("execute failed") + + def close(self) -> None: + """Record cleanup and optionally expose a secondary cleanup failure.""" + self.close_count += 1 + if self.fail_close: + raise OSError("cursor close failed") + + +class _ExecuteFailureConnection: + """Return one retained failing cursor to the connection adapter.""" + + def __init__(self, cursor: _ExecuteAndCloseFailureCursor) -> None: + self.cursor_value = cursor + + def cursor(self) -> _ExecuteAndCloseFailureCursor: + """Return the exact cursor whose ownership transfers to direct execute.""" + return self.cursor_value + + def test_candidate_context_preserves_commit_failure_when_close_also_fails() -> None: raw = _TransactionAndCloseFailureConnection(fail_commit=True) adapter = Pg8000CandidateConnectionAdapter(raw) @@ -64,3 +98,17 @@ def test_candidate_context_preserves_rollback_failure_when_close_also_fails() -> assert raw.rollback_count == 1 assert raw.close_count == 1 assert adapter.is_closed() is False + + +@pytest.mark.parametrize("fail_close", [False, True]) +def test_candidate_direct_execute_closes_cursor_and_preserves_primary_failure( + fail_close: bool, +) -> None: + raw_cursor = _ExecuteAndCloseFailureCursor(fail_close=fail_close) + adapter = Pg8000CandidateConnectionAdapter(_ExecuteFailureConnection(raw_cursor)) + + with pytest.raises(RuntimeError, match="execute failed"): + adapter.execute("SELECT %s", (1,)) + + assert raw_cursor.execute_count == 1 + assert raw_cursor.close_count == 1 From 162f617a90924282b36bc030826272494c9f3482 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:08:01 +0900 Subject: [PATCH 137/338] fix(postgres): close candidate cursor after execute failure --- .../pg8000_driver_candidate_adapter.py | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/pg_llm_batch/pg8000_driver_candidate_adapter.py b/pg_llm_batch/pg8000_driver_candidate_adapter.py index de650100..e08645e9 100644 --- a/pg_llm_batch/pg8000_driver_candidate_adapter.py +++ b/pg_llm_batch/pg8000_driver_candidate_adapter.py @@ -232,14 +232,28 @@ def execute( query: str, params: object | None = None, ) -> Pg8000CandidateCursorAdapter: - """Execute through a cursor created from this retained connection only. - - DB-API does not require ``Connection.execute``. Creating a cursor here - keeps the canonical convenience method while preserving parameter binding - and session identity instead of depending on a non-portable extension. + """Execute on this connection and release the owned cursor on failure. + + DB-API does not require ``Connection.execute``. The adapter therefore + creates the cursor itself and owns it until a successful execution hands + the cursor back to the caller. If execution fails before that handoff, + cleanup is attempted immediately so a database error cannot strand an + unreachable cursor. A secondary close failure never replaces the primary + execution failure. """ cursor = self.cursor() - cursor.execute(query, params) + try: + cursor.execute(query, params) + except BaseException as execution_error: + try: + cursor.__exit__( + type(execution_error), + execution_error, + execution_error.__traceback__, + ) + except BaseException: + raise execution_error from None + raise return cursor def commit(self) -> None: From 8c38c87cc6f60acb784aeaaa09916996fa6d8c37 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:18:37 +0900 Subject: [PATCH 138/338] test(ci): reject stale setup-uv pin in candidate lane --- tests/test_workflow_contracts.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_workflow_contracts.py b/tests/test_workflow_contracts.py index fcbe56b2..8d84af18 100644 --- a/tests/test_workflow_contracts.py +++ b/tests/test_workflow_contracts.py @@ -155,6 +155,12 @@ def test_ci_pg8000_candidate_parity_is_immutable_and_queue_conservative() -> Non """Keep replacement-driver proof exact without creating another runner lane.""" workflow = _read(".github/workflows/ci.yml") project = _read("pyproject.toml") + current_setup_uv = ( + "astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d" + ) + stale_setup_uv = ( + "astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9" + ) assert "pg8000-candidate-python314:" not in workflow assert "pg8000==1.31.5" in workflow @@ -163,6 +169,8 @@ def test_ci_pg8000_candidate_parity_is_immutable_and_queue_conservative() -> Non in workflow ) assert "tests/smoke_pg8000_candidate_postgres.py" in workflow + assert workflow.count(current_setup_uv) == 3 + assert stale_setup_uv not in workflow assert "pg8000-candidate-ci-password" not in workflow assert "secrets.token_urlsafe(32)" in workflow assert "::add-mask::$candidate_password" in workflow From 32fd6676d7c27aa5f9b149d831d4d2fd8b36d00d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:19:05 +0900 Subject: [PATCH 139/338] fix(ci): align candidate setup-uv exact pin --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ccbbe05..4c76b84e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,7 +119,7 @@ jobs: with: python-version: "3.14" - name: Set up uv for candidate parity - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: prune-cache: true - name: Install locked project dependencies From 8dab90ada5524b77f5680f3683cd1d2b636f40c5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:21:29 +0900 Subject: [PATCH 140/338] test(postgres): require candidate DB-API thread contract --- tests/test_pg8000_driver_candidate_adapter.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/test_pg8000_driver_candidate_adapter.py b/tests/test_pg8000_driver_candidate_adapter.py index fdf0e799..7d307d00 100644 --- a/tests/test_pg8000_driver_candidate_adapter.py +++ b/tests/test_pg8000_driver_candidate_adapter.py @@ -100,11 +100,17 @@ def __exit__(self, exc_type: object, exc: object, traceback: object) -> bool: return False -def _dbapi_module(*, apilevel: object = "2.0", paramstyle: object = "format") -> ModuleType: +def _dbapi_module( + *, + apilevel: object = "2.0", + paramstyle: object = "format", + threadsafety: object = 1, +) -> ModuleType: """Build one exact module-shaped DB-API authority for candidate contract tests.""" module = ModuleType("pg8000.dbapi") module.apilevel = apilevel module.paramstyle = paramstyle + module.threadsafety = threadsafety return module @@ -123,6 +129,18 @@ def test_candidate_dbapi_module_requires_dbapi_2_and_format_parameter_style() -> validate_pg8000_dbapi_module(module) +def test_candidate_dbapi_module_requires_module_only_connection_thread_safety() -> None: + """Reject metadata that would misstate pg8000 connection-sharing semantics.""" + validate_pg8000_dbapi_module(_dbapi_module(threadsafety=1)) + + for invalid in (0, 2, 3, True, "1"): + with pytest.raises( + Pg8000CandidateAdapterError, + match="thread safety is incompatible", + ): + validate_pg8000_dbapi_module(_dbapi_module(threadsafety=invalid)) + + def test_candidate_dbapi_module_rejects_shaped_or_behavior_bearing_metadata() -> None: class _StringSubclass(str): pass From 6439b74ffe9ce1fbe5fe32a238f8865992434f5a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:23:12 +0900 Subject: [PATCH 141/338] fix(postgres): validate candidate DB-API thread semantics --- .../pg8000_driver_candidate_adapter.py | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/pg_llm_batch/pg8000_driver_candidate_adapter.py b/pg_llm_batch/pg8000_driver_candidate_adapter.py index e08645e9..73adea8d 100644 --- a/pg_llm_batch/pg8000_driver_candidate_adapter.py +++ b/pg_llm_batch/pg8000_driver_candidate_adapter.py @@ -2,13 +2,13 @@ This module intentionally stops short of a production ``PostgresDriverPort``. pg8000 1.31.5 documents the DB-API cursor, transaction, autocommit, parameter -binding, and ``-1`` unknown-row-count behavior needed by part of the current -port, but pg-llm-batch has not yet proved its full conninfo/service-selector, -JSONB adaptation, PostgreSQL error-classification, Python 3.14, RLS, transport -failure recovery, concurrency, package, SBOM, and provenance contract on one -exact artifact. Keeping this adapter candidate-only lets those portable semantics -be exercised without making an unreleased or unverified runtime dependency -canonical. +binding, module-only connection thread sharing, and ``-1`` unknown-row-count +behavior needed by part of the current port, but pg-llm-batch has not yet proved +its full conninfo/service-selector, JSONB adaptation, PostgreSQL +error-classification, Python 3.14, RLS, transport failure recovery, package, +SBOM, and provenance contract on one exact artifact. Keeping this adapter +candidate-only lets those portable semantics be exercised without making an +unreleased or unverified runtime dependency canonical. """ from __future__ import annotations @@ -30,18 +30,21 @@ class Pg8000CandidateAdapterError(RuntimeError): def validate_pg8000_dbapi_module(dbapi_module: object) -> None: - """Fail closed unless the imported pg8000 DB-API mode matches package SQL. + """Fail closed unless imported pg8000 DB-API metadata matches package use. pg8000 exposes ``paramstyle`` as mutable module state. pg-llm-batch's current SQL uses DB-API ``format`` placeholders, so a future production candidate factory must run this guard immediately after importing the exact admitted - pg8000 artifact and before creating adapters or executing SQL. Metadata is - read from an exact ``ModuleType`` dictionary rather than through arbitrary - shaped objects whose attribute access could execute caller-controlled code. + pg8000 artifact and before creating adapters or executing SQL. The documented + ``threadsafety == 1`` value is also part of this boundary: code may share the + module across threads but must not infer that one connection is shareable. + Metadata is read from an exact ``ModuleType`` dictionary rather than through + arbitrary shaped objects whose attribute access could execute caller-controlled + code. Raises: - Pg8000CandidateAdapterError: If DB-API 2.0 or ``format`` parameter style - is not the exact active module contract. + Pg8000CandidateAdapterError: If DB-API level, parameter style, or thread + sharing semantics differ from the exact reviewed candidate contract. """ if type(dbapi_module) is not ModuleType: raise Pg8000CandidateAdapterError("PostgreSQL driver module identity is invalid") @@ -50,6 +53,7 @@ def validate_pg8000_dbapi_module(dbapi_module: object) -> None: metadata = vars(module) api_level = metadata.get("apilevel") parameter_style = metadata.get("paramstyle") + thread_safety = metadata.get("threadsafety") if type(api_level) is not str or api_level != "2.0": raise Pg8000CandidateAdapterError("PostgreSQL driver API level is incompatible") @@ -57,6 +61,10 @@ def validate_pg8000_dbapi_module(dbapi_module: object) -> None: raise Pg8000CandidateAdapterError( "PostgreSQL driver parameter style is incompatible" ) + if type(thread_safety) is not int or thread_safety != 1: + raise Pg8000CandidateAdapterError( + "PostgreSQL driver thread safety is incompatible" + ) class Pg8000CandidateCursorAdapter(PostgresCursorPort): @@ -211,7 +219,9 @@ class Pg8000CandidateConnectionAdapter(PostgresConnectionPort): It never opens a connection itself and therefore cannot bypass the still-open DSN/conninfo/service-selector admission problem. All operations stay on the injected raw connection so transaction-local RLS state cannot migrate to an - implicit second session. + implicit second session. The candidate's DB-API thread level does not permit + callers to infer that this retained connection is safe to share across threads; + that package-level concurrency boundary remains a separate admission gate. """ def __init__(self, connection: Any) -> None: From 3fa594837d4edd61844d70c7eaf3009323bb38fd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:24:39 +0900 Subject: [PATCH 142/338] test(postgres): expose shared token connection concurrency --- tests/test_token_counter_driver_port.py | 70 ++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/tests/test_token_counter_driver_port.py b/tests/test_token_counter_driver_port.py index 2326dbbb..09349cf1 100644 --- a/tests/test_token_counter_driver_port.py +++ b/tests/test_token_counter_driver_port.py @@ -3,6 +3,9 @@ from __future__ import annotations +from concurrent.futures import ThreadPoolExecutor +from threading import Barrier, Lock +import time from typing import Any import pytest @@ -34,11 +37,17 @@ def __exit__(self, *_exc: Any) -> None: def execute(self, query: str, params: object | None = None) -> _Cursor: self.driver.executions.append((query, params)) if "tiktoken_count" in query and "to_regprocedure" not in query: - if self.driver.primary_error is not None: - error = self.driver.primary_error - self.driver.primary_error = None - raise error - self.driver.rows.append((7,)) + self.driver.enter_count_execution() + try: + if self.driver.execution_delay_seconds: + time.sleep(self.driver.execution_delay_seconds) + if self.driver.primary_error is not None: + error = self.driver.primary_error + self.driver.primary_error = None + raise error + self.driver.rows.append((7,)) + finally: + self.driver.leave_count_execution() elif "tiktoken_encode" in query and "to_regprocedure" not in query: self.driver.rows.append((9,)) return self @@ -73,12 +82,21 @@ def close(self) -> None: class _Driver: """Minimal Psycopg-free driver implementing the token-counting port surface.""" - def __init__(self, *, primary_error: BaseException | None = None) -> None: + def __init__( + self, + *, + primary_error: BaseException | None = None, + execution_delay_seconds: float = 0.0, + ) -> None: self.primary_error = primary_error + self.execution_delay_seconds = execution_delay_seconds self.executions: list[tuple[str, object | None]] = [] self.rows: list[tuple[object, ...]] = [(True, True, True)] self.connections: list[_Connection] = [] self.dsn_values: list[str] = [] + self._execution_lock = Lock() + self.active_count_executions = 0 + self.max_active_count_executions = 0 def connect( self, @@ -95,6 +113,20 @@ def connect( def is_undefined_function(self, error: BaseException) -> bool: return isinstance(error, _UndefinedFunctionError) + def enter_count_execution(self) -> None: + """Record concurrent use of the shared token-counting connection.""" + with self._execution_lock: + self.active_count_executions += 1 + self.max_active_count_executions = max( + self.max_active_count_executions, + self.active_count_executions, + ) + + def leave_count_execution(self) -> None: + """Release one deterministic concurrent-execution observation.""" + with self._execution_lock: + self.active_count_executions -= 1 + def test_token_counter_uses_injected_driver_without_psycopg( monkeypatch: pytest.MonkeyPatch, @@ -118,6 +150,32 @@ def _metadata(dsn: str, model: str, *, postgres_driver: object = None) -> dict[s assert metadata_calls == [("postgresql://x", "model-a", driver)] +def test_token_counter_serializes_shared_driver_connection_use( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A DB-API level-1 candidate must never receive concurrent connection calls.""" + driver = _Driver(execution_delay_seconds=0.03) + monkeypatch.setattr(token_counter_module, "psycopg", None) + monkeypatch.setattr( + token_counter_module, + "get_model_metadata", + lambda _dsn, _model, *, postgres_driver=None: {"tokenizer_model": "o200k_base"}, + ) + counter = TokenCounter("postgresql://x", postgres_driver=driver) + counter.get_encoder("model-a") + start = Barrier(4) + + def _count_one(index: int) -> int: + start.wait() + return counter.count_tokens(f"hello-{index}", "model-a") + + with ThreadPoolExecutor(max_workers=4) as executor: + results = list(executor.map(_count_one, range(4))) + + assert results == [7, 7, 7, 7] + assert driver.max_active_count_executions == 1 + + def test_token_counter_uses_driver_error_classification_for_encode_fallback( monkeypatch: pytest.MonkeyPatch, ) -> None: From 86b57a97947be47637492f3255cff683a363ec0e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:25:55 +0900 Subject: [PATCH 143/338] fix(postgres): serialize shared token connection use --- pg_llm_batch/token_counter.py | 160 ++++++++++++++++++---------------- 1 file changed, 87 insertions(+), 73 deletions(-) diff --git a/pg_llm_batch/token_counter.py b/pg_llm_batch/token_counter.py index 2b81aab7..93bd583c 100644 --- a/pg_llm_batch/token_counter.py +++ b/pg_llm_batch/token_counter.py @@ -16,6 +16,7 @@ import logging from dataclasses import dataclass from io import StringIO +from threading import RLock from typing import Any, Dict, List, Optional, Tuple from .db import get_model_metadata @@ -71,6 +72,7 @@ def __init__( self.config = config self._postgres_driver = postgres_driver self._pg_conn: Optional[Any] = None + self._pg_connection_lock = RLock() self._pg_available: bool = False self._encoder_cache: Dict[str, _EncoderInfo] = {} @@ -162,19 +164,27 @@ def get_encoder(self, model: str) -> _EncoderInfo: return info def count_tokens(self, text: str, model: str) -> int: - """Count tokens through pg_tiktoken or fail when it is unavailable.""" + """Count tokens while serializing use of the retained PostgreSQL session. + + A replacement DB-API driver may permit module sharing without permitting + concurrent use of one connection. The counter intentionally retains one + autocommit session for repeated pg_tiktoken calls, so the lock protects + that exact session through execution, error classification, and cleanup + rather than assuming stronger driver thread semantics. + """ if not text: return 0 - if self._pg_available: - try: - return self._count_tokens_postgres(text, model) - except Exception as error: # pragma: no cover - runtime DB variance - if self._is_undefined_function(error): - self._pg_available = False - logger.warning("pg_tiktoken extension/functions unavailable") - else: - self.close() - logger.debug("PostgreSQL token counting failed") + with self._pg_connection_lock: + if self._pg_available: + try: + return self._count_tokens_postgres(text, model) + except Exception as error: # pragma: no cover - runtime DB variance + if self._is_undefined_function(error): + self._pg_available = False + logger.warning("pg_tiktoken extension/functions unavailable") + else: + self.close() + logger.debug("PostgreSQL token counting failed") raise RuntimeError( "Token counting requires pg_tiktoken. Enable the extension and pass a " "valid DSN." @@ -267,15 +277,16 @@ def split_oversized_batch( return batches def close(self) -> None: - """Close and clear the cached PostgreSQL token-counting connection.""" - conn = self._pg_conn - self._pg_conn = None - if conn is None: - return - try: - conn.close() - except Exception: - pass + """Close and clear the cached PostgreSQL token-counting connection safely.""" + with self._pg_connection_lock: + conn = self._pg_conn + self._pg_conn = None + if conn is None: + return + try: + conn.close() + except Exception: + pass def _resolve_config_value(self, category: str, key: str, default: Any) -> Any: """Read a config value from the KV store, returning the default on any failure.""" @@ -291,43 +302,45 @@ def _ensure_pg_tiktoken(self) -> bool: """Verify the pre-provisioned pg_tiktoken extension and functions read-only.""" if self._postgres_driver is None and psycopg is None: return False - try: - conn = self._get_pg_conn() - with conn.cursor() as cur: - cur.execute( - """ - SELECT EXISTS ( - SELECT 1 - FROM pg_extension - WHERE extname = %s - ), - to_regprocedure('tiktoken_count(text,text)') IS NOT NULL, - to_regprocedure('tiktoken_encode(text,text)') IS NOT NULL - """, - ("pg_tiktoken",), - ) - row = cur.fetchone() - return bool(row and row == (True, True, True)) - except Exception: - self.close() - return False + with self._pg_connection_lock: + try: + conn = self._get_pg_conn() + with conn.cursor() as cur: + cur.execute( + """ + SELECT EXISTS ( + SELECT 1 + FROM pg_extension + WHERE extname = %s + ), + to_regprocedure('tiktoken_count(text,text)') IS NOT NULL, + to_regprocedure('tiktoken_encode(text,text)') IS NOT NULL + """, + ("pg_tiktoken",), + ) + row = cur.fetchone() + return bool(row and row == (True, True, True)) + except Exception: + self.close() + return False def _get_pg_conn(self) -> Any: - """Return a cached autocommit connection through the selected driver boundary.""" - if self._pg_conn is not None: - if self._postgres_driver is not None: - if not self._pg_conn.is_closed(): + """Return a cached autocommit connection under the session reuse lock.""" + with self._pg_connection_lock: + if self._pg_conn is not None: + if self._postgres_driver is not None: + if not self._pg_conn.is_closed(): + return self._pg_conn + elif not self._pg_conn.closed: return self._pg_conn - elif not self._pg_conn.closed: + if self._postgres_driver is not None: + self._pg_conn = self._postgres_driver.connect(self.postgres_dsn) + self._pg_conn.set_autocommit(True) return self._pg_conn - if self._postgres_driver is not None: - self._pg_conn = self._postgres_driver.connect(self.postgres_dsn) - self._pg_conn.set_autocommit(True) + assert psycopg is not None + self._pg_conn = psycopg.connect(self.postgres_dsn) + self._pg_conn.autocommit = True return self._pg_conn - assert psycopg is not None - self._pg_conn = psycopg.connect(self.postgres_dsn) - self._pg_conn.autocommit = True - return self._pg_conn def _is_undefined_function(self, error: BaseException) -> bool: """Classify undefined-function failures through the selected driver boundary.""" @@ -336,29 +349,30 @@ def _is_undefined_function(self, error: BaseException) -> bool: return isinstance(error, UndefinedFunction) def _count_tokens_postgres(self, text: str, model: str) -> int: - """Count tokens via pg_tiktoken while preserving driver error classification.""" + """Count tokens while retaining one non-concurrent PostgreSQL session.""" if self._postgres_driver is None and psycopg is None: raise RuntimeError("PostgreSQL integration is unavailable") - conn = self._get_pg_conn() - tiktoken_name = self.get_encoder(model).tokenizer_name - with conn.cursor() as cur: - try: - cur.execute("SELECT tiktoken_count(%s, %s)", (tiktoken_name, text)) - row = cur.fetchone() - if row and row[0] is not None: - return int(row[0]) - except Exception as error: - if not self._is_undefined_function(error): + with self._pg_connection_lock: + conn = self._get_pg_conn() + tiktoken_name = self.get_encoder(model).tokenizer_name + with conn.cursor() as cur: + try: + cur.execute("SELECT tiktoken_count(%s, %s)", (tiktoken_name, text)) + row = cur.fetchone() + if row and row[0] is not None: + return int(row[0]) + except Exception as error: + if not self._is_undefined_function(error): + raise + cur.execute( + "SELECT COUNT(*) FROM tiktoken_encode(%s, %s)", + (tiktoken_name, text), + ) + row = cur.fetchone() + if row and row[0] is not None: + return int(row[0]) raise - cur.execute( - "SELECT COUNT(*) FROM tiktoken_encode(%s, %s)", - (tiktoken_name, text), - ) - row = cur.fetchone() - if row and row[0] is not None: - return int(row[0]) - raise - return 0 + return 0 def _get_tokenizer_from_db(self, model: str) -> Optional[str]: """Return the tokenizer model recorded in model metadata, or None if unset.""" @@ -458,4 +472,4 @@ def is_empty(self) -> bool: def __len__(self) -> int: """Return the number of accumulated requests.""" - return self.record_count + return self.record_count \ No newline at end of file From b37324ff667573de4eb73c1c6324d5207fffda25 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:31:52 +0900 Subject: [PATCH 144/338] test(ci): make pg8000 dependency guard case-insensitive --- tests/test_workflow_contracts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_workflow_contracts.py b/tests/test_workflow_contracts.py index 8d84af18..b261a7bb 100644 --- a/tests/test_workflow_contracts.py +++ b/tests/test_workflow_contracts.py @@ -177,7 +177,7 @@ def test_ci_pg8000_candidate_parity_is_immutable_and_queue_conservative() -> Non assert "PG_LLM_BATCH_POSTGRES_PASSWORD=$candidate_password" in workflow assert "PG8000_CANDIDATE_PASSWORD_FILE" in workflow assert "Tear down candidate PostgreSQL runtime" in workflow - assert '"pg8000' not in project + assert '"pg8000' not in project.casefold() def test_workflow_step_field_matching_ignores_comments_and_unrelated_values() -> None: From 444988a0baaacb083b8af6921fac31feea321c19 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:44:55 +0900 Subject: [PATCH 145/338] test(postgres): expose cursor cleanup error precedence --- tests/test_pg8000_driver_candidate_adapter.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test_pg8000_driver_candidate_adapter.py b/tests/test_pg8000_driver_candidate_adapter.py index 7d307d00..4ccb0e86 100644 --- a/tests/test_pg8000_driver_candidate_adapter.py +++ b/tests/test_pg8000_driver_candidate_adapter.py @@ -32,6 +32,7 @@ def __init__(self) -> None: self.fetchall_value: list[object] = [] self.rowcount: object = 0 self.close_count = 0 + self.close_error: BaseException | None = None self.enter_count = 0 self.exit_args: tuple[object, object, object] | None = None @@ -55,6 +56,8 @@ def fetchall(self) -> list[object]: def close(self) -> None: self.close_count += 1 + if self.close_error is not None: + raise self.close_error def __enter__(self) -> _FakeCursor: self.enter_count += 1 @@ -224,6 +227,34 @@ def test_candidate_cursor_context_owns_dbapi_cleanup_without_raw_context_depende assert raw.exit_args is None +def test_candidate_cursor_context_preserves_application_error_over_cleanup_failure() -> None: + """Cleanup failure must not replace the application error already in flight.""" + raw = _FakeCursor() + adapter = Pg8000CandidateCursorAdapter(raw) + application_error = ValueError("application") + raw.close_error = RuntimeError("cleanup") + + with pytest.raises(ValueError) as caught: + adapter.__exit__(ValueError, application_error, None) + + assert caught.value is application_error + assert raw.close_count == 1 + + +def test_candidate_cursor_context_propagates_cleanup_failure_without_application_error() -> None: + """A close-only failure remains visible when no earlier error has priority.""" + raw = _FakeCursor() + adapter = Pg8000CandidateCursorAdapter(raw) + cleanup_error = RuntimeError("cleanup") + raw.close_error = cleanup_error + + with pytest.raises(RuntimeError) as caught: + adapter.__exit__(None, None, None) + + assert caught.value is cleanup_error + assert raw.close_count == 1 + + def test_candidate_connection_uses_one_raw_connection_for_execution_and_transactions() -> None: raw = _FakeConnection() adapter = Pg8000CandidateConnectionAdapter(raw) From 5fc43b20ac59caa1e92185e8667dab2358f0f0fe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:46:22 +0900 Subject: [PATCH 146/338] fix(postgres): preserve cursor application error on cleanup failure --- pg_llm_batch/pg8000_driver_candidate_adapter.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/pg_llm_batch/pg8000_driver_candidate_adapter.py b/pg_llm_batch/pg8000_driver_candidate_adapter.py index 73adea8d..249e58c9 100644 --- a/pg_llm_batch/pg8000_driver_candidate_adapter.py +++ b/pg_llm_batch/pg8000_driver_candidate_adapter.py @@ -201,14 +201,21 @@ def __exit__( exc: BaseException | None, traceback: object | None, ) -> bool | None: - """Close the DB-API cursor and never suppress an application exception. + """Close the DB-API cursor without replacing an active application error. Cursor exit owns resource cleanup only; transaction commit or rollback - remains a connection-level responsibility. Returning ``False`` preserves - any active exception while avoiding dependence on driver-specific context - manager behavior that pg8000's public DB-API contract does not require. + remains a connection-level responsibility. If cleanup fails while an + application exception is already in flight, the application exception + remains primary. A close-only failure still propagates. Returning + ``False`` preserves ordinary context-manager exception propagation while + avoiding a driver-specific cursor context-manager dependency. """ - self._cursor.close() + try: + self._cursor.close() + except BaseException: + if exc is not None: + raise exc from None + raise return False From c1705051bc06d72d9c8547716452ad1dbc9e520d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:50:12 +0900 Subject: [PATCH 147/338] test(postgres): expose connection cleanup error precedence --- ...g8000_driver_candidate_error_precedence.py | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/tests/test_pg8000_driver_candidate_error_precedence.py b/tests/test_pg8000_driver_candidate_error_precedence.py index 7672a1c1..9fbbb117 100644 --- a/tests/test_pg8000_driver_candidate_error_precedence.py +++ b/tests/test_pg8000_driver_candidate_error_precedence.py @@ -2,10 +2,12 @@ The pg8000 anti-corruption adapter must attempt connection cleanup after a transaction failure without letting a later close failure replace the earlier -commit or rollback failure. Direct connection execution must likewise close the -internally created cursor when execution fails, because the caller never receives -that cursor and therefore cannot release it. These tests keep that recovery -contract independent from the real-driver PostgreSQL smoke gate. +commit or rollback failure. An application exception also remains primary when +rollback succeeds but later connection cleanup fails. Direct connection +execution must likewise close the internally created cursor when execution +fails, because the caller never receives that cursor and therefore cannot +release it. These tests keep that recovery contract independent from the +real-driver PostgreSQL smoke gate. """ from __future__ import annotations @@ -41,6 +43,21 @@ def close(self) -> None: raise OSError("close failed") +class _RollbackSuccessCloseFailureConnection: + """Succeed rollback but fail cleanup after an application exception.""" + + def __init__(self) -> None: + self.rollback_count = 0 + self.close_count = 0 + + def rollback(self) -> None: + self.rollback_count += 1 + + def close(self) -> None: + self.close_count += 1 + raise OSError("close failed") + + class _ExecuteAndCloseFailureCursor: """Fail direct execution and optionally fail the required cursor cleanup.""" @@ -100,6 +117,21 @@ def test_candidate_context_preserves_rollback_failure_when_close_also_fails() -> assert adapter.is_closed() is False +def test_candidate_context_preserves_application_error_when_only_close_fails() -> None: + """Cleanup failure must not replace an application error after rollback.""" + raw = _RollbackSuccessCloseFailureConnection() + adapter = Pg8000CandidateConnectionAdapter(raw) + application_error = ValueError("application failed") + + with pytest.raises(ValueError) as caught: + adapter.__exit__(ValueError, application_error, None) + + assert caught.value is application_error + assert raw.rollback_count == 1 + assert raw.close_count == 1 + assert adapter.is_closed() is False + + @pytest.mark.parametrize("fail_close", [False, True]) def test_candidate_direct_execute_closes_cursor_and_preserves_primary_failure( fail_close: bool, From 84f4fe9ab02362363a34af98b09f8995e33f4b3f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:51:17 +0900 Subject: [PATCH 148/338] fix(postgres): preserve application error over close failure --- pg_llm_batch/pg8000_driver_candidate_adapter.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pg_llm_batch/pg8000_driver_candidate_adapter.py b/pg_llm_batch/pg8000_driver_candidate_adapter.py index 249e58c9..0cfb14dc 100644 --- a/pg_llm_batch/pg8000_driver_candidate_adapter.py +++ b/pg_llm_batch/pg8000_driver_candidate_adapter.py @@ -337,14 +337,16 @@ def __exit__( exc: BaseException | None, traceback: object | None, ) -> bool | None: - """Commit or roll back, attempt close, and preserve transaction failures. + """Commit or roll back, close, and preserve the highest-priority failure. pg8000's public DB-API contract documents ``commit``, ``rollback``, and ``close`` but does not require a connection context-manager extension. Owning the package policy here removes that undocumented dependency while retaining the transaction semantics required by candidate admission. A - commit or rollback failure remains the primary failure even if later - connection cleanup also fails; close-only failures still propagate. + commit or rollback failure remains primary over both an application error + and later cleanup failure. If rollback succeeds, the application error + remains primary over a later close failure. A close-only failure still + propagates on an otherwise successful exit. """ transaction_error: BaseException | None = None try: @@ -360,6 +362,8 @@ def __exit__( except BaseException: if transaction_error is not None: raise transaction_error from None + if exc is not None: + raise exc from None raise if transaction_error is not None: From 6dbcaaf7fb9014265c9e058aa17f02e3b0c8d01d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 01:09:16 +0900 Subject: [PATCH 149/338] test(ci): require immutable pg8000 dependency closure --- tests/test_workflow_contracts.py | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/test_workflow_contracts.py b/tests/test_workflow_contracts.py index b261a7bb..884ad7ce 100644 --- a/tests/test_workflow_contracts.py +++ b/tests/test_workflow_contracts.py @@ -180,6 +180,43 @@ def test_ci_pg8000_candidate_parity_is_immutable_and_queue_conservative() -> Non assert '"pg8000' not in project.casefold() +def test_ci_pg8000_candidate_pins_and_hashes_full_dependency_closure() -> None: + """Candidate proof must not resolve mutable transitive wheels at install time.""" + workflow = _read(".github/workflows/ci.yml") + + exact_artifacts = { + "pg8000==1.31.5": ( + "0af2c1926b153307639868d2ee5cef6cd3a7d07448e12736989b10e1d491e201" + ), + "python-dateutil==2.9.0.post0": ( + "a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" + ), + "scramp==1.4.17": ( + "a4e3fd2e8169461a28a13777a166d3da94274454f0714a7d3023fee124474ac8" + ), + "asn1crypto==1.5.1": ( + "db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67" + ), + "six==1.17.0": ( + "4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274" + ), + } + for requirement, digest in exact_artifacts.items(): + assert requirement in workflow + assert digest in workflow + + assert "pip download --no-deps --only-binary=:all:" in workflow + assert "uv pip install --python .venv/bin/python --no-deps" in workflow + assert "/tmp/pg8000-candidate/pg8000-1.31.5-py3-none-any.whl" in workflow + assert ( + "/tmp/pg8000-candidate/python_dateutil-2.9.0.post0-py2.py3-none-any.whl" + in workflow + ) + assert "/tmp/pg8000-candidate/scramp-1.4.17-py3-none-any.whl" in workflow + assert "/tmp/pg8000-candidate/asn1crypto-1.5.1-py2.py3-none-any.whl" in workflow + assert "/tmp/pg8000-candidate/six-1.17.0-py2.py3-none-any.whl" in workflow + + def test_workflow_step_field_matching_ignores_comments_and_unrelated_values() -> None: """Comment or nested text must not masquerade as workflow step fields.""" decoy = """ - name: Decoy From e2f67a78fd14defee7b47fa7087b35cd89eb48e8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 01:10:08 +0900 Subject: [PATCH 150/338] fix(ci): pin pg8000 candidate dependency closure --- .github/workflows/ci.yml | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c76b84e..64bd95ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -124,19 +124,35 @@ jobs: prune-cache: true - name: Install locked project dependencies run: uv sync --locked - - name: Download exact pg8000 candidate artifact + - name: Download exact pg8000 candidate dependency closure run: >- python -m pip download --no-deps --only-binary=:all: - --dest /tmp/pg8000-candidate pg8000==1.31.5 - - name: Verify pg8000 candidate artifact digest - run: >- - echo - "0af2c1926b153307639868d2ee5cef6cd3a7d07448e12736989b10e1d491e201 /tmp/pg8000-candidate/pg8000-1.31.5-py3-none-any.whl" - | sha256sum --check --strict - - name: Install candidate only into the CI environment + --dest /tmp/pg8000-candidate + pg8000==1.31.5 + python-dateutil==2.9.0.post0 + scramp==1.4.17 + asn1crypto==1.5.1 + six==1.17.0 + - name: Verify pg8000 candidate dependency digests + shell: bash + run: | + cat <<'EOF' | sha256sum --check --strict + 0af2c1926b153307639868d2ee5cef6cd3a7d07448e12736989b10e1d491e201 /tmp/pg8000-candidate/pg8000-1.31.5-py3-none-any.whl + a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 /tmp/pg8000-candidate/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + a4e3fd2e8169461a28a13777a166d3da94274454f0714a7d3023fee124474ac8 /tmp/pg8000-candidate/scramp-1.4.17-py3-none-any.whl + db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67 /tmp/pg8000-candidate/asn1crypto-1.5.1-py2.py3-none-any.whl + 4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 /tmp/pg8000-candidate/six-1.17.0-py2.py3-none-any.whl + EOF + - name: Install exact candidate closure into the CI environment run: >- - uv pip install --python .venv/bin/python + uv pip install --python .venv/bin/python --no-deps /tmp/pg8000-candidate/pg8000-1.31.5-py3-none-any.whl + /tmp/pg8000-candidate/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + /tmp/pg8000-candidate/scramp-1.4.17-py3-none-any.whl + /tmp/pg8000-candidate/asn1crypto-1.5.1-py2.py3-none-any.whl + /tmp/pg8000-candidate/six-1.17.0-py2.py3-none-any.whl + - name: Verify candidate environment dependency consistency + run: uv pip check --python .venv/bin/python - name: Start candidate PostgreSQL runtime shell: bash run: | From df54826ad6152ae72b95ec5a117c58c222da8f38 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 08:02:30 +0900 Subject: [PATCH 151/338] test(postgres): require file-only candidate credential handoff --- .../test_pg8000_candidate_secret_boundary.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/test_pg8000_candidate_secret_boundary.py diff --git a/tests/test_pg8000_candidate_secret_boundary.py b/tests/test_pg8000_candidate_secret_boundary.py new file mode 100644 index 00000000..f7651bb3 --- /dev/null +++ b/tests/test_pg8000_candidate_secret_boundary.py @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Protect the pg8000 candidate smoke credential from duplicate env propagation.""" + +from __future__ import annotations + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def _read(relative_path: str) -> str: + """Read one repository-owned candidate acceptance artifact.""" + return (ROOT / relative_path).read_text(encoding="utf-8") + + +def test_pg8000_candidate_credential_uses_only_ephemeral_password_file() -> None: + """Keep the candidate password in one masked file boundary, not GITHUB_ENV.""" + workflow = _read(".github/workflows/ci.yml") + smoke = _read("tests/smoke_pg8000_candidate_postgres.py") + + assert "PG8000_CANDIDATE_PASSWORD_FILE=$password_file" in workflow + assert "PG_LLM_BATCH_POSTGRES_PASSWORD=$candidate_password" not in workflow + assert 'os.environ.get("PG_LLM_BATCH_POSTGRES_PASSWORD")' not in smoke + assert 'os.environ.get("PG8000_CANDIDATE_PASSWORD_FILE")' in smoke + assert ".read_text(encoding=\"utf-8\")" in smoke From 3a6688ee9d910b86cc842a357151817a7a35abb9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 08:03:07 +0900 Subject: [PATCH 152/338] fix(postgres): read candidate credential from ephemeral file --- tests/smoke_pg8000_candidate_postgres.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/smoke_pg8000_candidate_postgres.py b/tests/smoke_pg8000_candidate_postgres.py index 07645128..adac241a 100644 --- a/tests/smoke_pg8000_candidate_postgres.py +++ b/tests/smoke_pg8000_candidate_postgres.py @@ -13,6 +13,7 @@ from datetime import datetime, timezone from importlib import metadata import os +from pathlib import Path import uuid from pg8000 import dbapi @@ -28,10 +29,16 @@ def _raw_connection() -> object: - """Open one finite local candidate connection using only CI-owned credentials.""" - password = os.environ.get("PG_LLM_BATCH_POSTGRES_PASSWORD") + """Open one finite local candidate connection using the CI-owned password file.""" + password_file = os.environ.get("PG8000_CANDIDATE_PASSWORD_FILE") + if not password_file: + raise RuntimeError("PG8000_CANDIDATE_PASSWORD_FILE is required") + try: + password = Path(password_file).read_text(encoding="utf-8") + except (OSError, UnicodeError): + raise RuntimeError("PG8000 candidate password file could not be read") from None if not password: - raise RuntimeError("PG_LLM_BATCH_POSTGRES_PASSWORD is required") + raise RuntimeError("PG8000 candidate password file is empty") return dbapi.connect( user=_EXPECTED_USER, password=password, From 7ce0666b28d6b37c4f9d9348baad0f3dfc8a5847 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 08:03:49 +0900 Subject: [PATCH 153/338] fix(postgres): minimize candidate credential propagation --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64bd95ae..1446e7b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,7 +162,6 @@ jobs: printf '%s' "$candidate_password" > "$password_file" chmod 600 "$password_file" container="pg-llm-batch-pg8000-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" - echo "PG_LLM_BATCH_POSTGRES_PASSWORD=$candidate_password" >> "$GITHUB_ENV" echo "PG8000_CANDIDATE_PASSWORD_FILE=$password_file" >> "$GITHUB_ENV" echo "PG8000_CANDIDATE_CONTAINER=$container" >> "$GITHUB_ENV" docker run --detach --name "$container" \ From 67a1e32695327270c19af2a33221211604586a52 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 08:04:56 +0900 Subject: [PATCH 154/338] test(postgres): align candidate secret contract --- tests/test_workflow_contracts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_workflow_contracts.py b/tests/test_workflow_contracts.py index 884ad7ce..445aee8d 100644 --- a/tests/test_workflow_contracts.py +++ b/tests/test_workflow_contracts.py @@ -174,7 +174,7 @@ def test_ci_pg8000_candidate_parity_is_immutable_and_queue_conservative() -> Non assert "pg8000-candidate-ci-password" not in workflow assert "secrets.token_urlsafe(32)" in workflow assert "::add-mask::$candidate_password" in workflow - assert "PG_LLM_BATCH_POSTGRES_PASSWORD=$candidate_password" in workflow + assert "PG_LLM_BATCH_POSTGRES_PASSWORD=$candidate_password" not in workflow assert "PG8000_CANDIDATE_PASSWORD_FILE" in workflow assert "Tear down candidate PostgreSQL runtime" in workflow assert '"pg8000' not in project.casefold() From a645f9893edb3a2c202c26f0cf54332878a34913 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 12:05:48 +0900 Subject: [PATCH 155/338] test(license): require candidate wheel license evidence --- .../test_candidate_wheel_license_verifier.py | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 tests/test_candidate_wheel_license_verifier.py diff --git a/tests/test_candidate_wheel_license_verifier.py b/tests/test_candidate_wheel_license_verifier.py new file mode 100644 index 00000000..e3ff8d86 --- /dev/null +++ b/tests/test_candidate_wheel_license_verifier.py @@ -0,0 +1,144 @@ +"""Regression tests for immutable candidate-wheel license admission. + +The pg8000 migration is intended to remove an LGPL-family production dependency. +Hash-pinning a candidate wheel closure proves artifact identity but does not prove +that every installed transitive dependency satisfies the repository's inbound +license policy. These tests keep that commercial-policy decision executable. +""" + +from __future__ import annotations + +import importlib.util +from pathlib import Path +import zipfile + +import pytest + + +_TOOL_PATH = Path(__file__).parents[1] / "tools" / "verify_candidate_wheel_licenses.py" + + +def _load_verifier(): + """Load the repository-owned verifier without turning tools into a package.""" + spec = importlib.util.spec_from_file_location("candidate_license_verifier", _TOOL_PATH) + if spec is None or spec.loader is None: + raise AssertionError("candidate license verifier could not be loaded") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _write_wheel(directory: Path, filename: str, *, name: str, version: str, license_lines: tuple[str, ...]) -> None: + """Write the smallest synthetic wheel metadata fixture needed by the verifier.""" + metadata = [ + "Metadata-Version: 2.4", + f"Name: {name}", + f"Version: {version}", + *license_lines, + "", + ] + dist_info = filename.split("-", 1)[0].replace("-", "_") + with zipfile.ZipFile(directory / filename, "w") as archive: + archive.writestr(f"{dist_info}-{version}.dist-info/METADATA", "\n".join(metadata)) + + +def _write_valid_closure(directory: Path) -> None: + """Create license metadata matching the exact pg8000 candidate closure.""" + _write_wheel( + directory, + "pg8000-1.31.5-py3-none-any.whl", + name="pg8000", + version="1.31.5", + license_lines=("License-Expression: BSD-3-Clause",), + ) + _write_wheel( + directory, + "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", + name="python-dateutil", + version="2.9.0.post0", + license_lines=( + "License: Dual License", + "Classifier: License :: OSI Approved :: Apache Software License", + "Classifier: License :: OSI Approved :: BSD License", + ), + ) + _write_wheel( + directory, + "scramp-1.4.17-py3-none-any.whl", + name="scramp", + version="1.4.17", + license_lines=("License-Expression: MIT-0",), + ) + _write_wheel( + directory, + "asn1crypto-1.5.1-py2.py3-none-any.whl", + name="asn1crypto", + version="1.5.1", + license_lines=("License: MIT",), + ) + _write_wheel( + directory, + "six-1.17.0-py2.py3-none-any.whl", + name="six", + version="1.17.0", + license_lines=("License: MIT",), + ) + + +def test_exact_candidate_closure_requires_permissive_license_evidence(tmp_path: Path) -> None: + verifier = _load_verifier() + _write_valid_closure(tmp_path) + + verifier.verify_candidate_wheel_licenses(tmp_path) + + +def test_candidate_closure_rejects_gpl_family_metadata_even_with_permissive_marker(tmp_path: Path) -> None: + verifier = _load_verifier() + _write_valid_closure(tmp_path) + wheel_path = tmp_path / "scramp-1.4.17-py3-none-any.whl" + wheel_path.unlink() + _write_wheel( + tmp_path, + wheel_path.name, + name="scramp", + version="1.4.17", + license_lines=( + "License-Expression: MIT-0", + "Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", + ), + ) + + with pytest.raises(verifier.CandidateWheelLicenseError, match="disallowed license"): + verifier.verify_candidate_wheel_licenses(tmp_path) + + +def test_candidate_closure_rejects_missing_positive_license_evidence(tmp_path: Path) -> None: + verifier = _load_verifier() + _write_valid_closure(tmp_path) + wheel_path = tmp_path / "six-1.17.0-py2.py3-none-any.whl" + wheel_path.unlink() + _write_wheel( + tmp_path, + wheel_path.name, + name="six", + version="1.17.0", + license_lines=("License: UNKNOWN",), + ) + + with pytest.raises(verifier.CandidateWheelLicenseError, match="approved license evidence"): + verifier.verify_candidate_wheel_licenses(tmp_path) + + +def test_candidate_closure_rejects_unexpected_wheel_set(tmp_path: Path) -> None: + verifier = _load_verifier() + _write_valid_closure(tmp_path) + _write_wheel( + tmp_path, + "unexpected-1.0-py3-none-any.whl", + name="unexpected", + version="1.0", + license_lines=("License: MIT",), + ) + + with pytest.raises(verifier.CandidateWheelLicenseError, match="wheel set is invalid"): + verifier.verify_candidate_wheel_licenses(tmp_path) From 7399ff6bb40859d705d54a0eabdd329fc8655fea Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 12:06:15 +0900 Subject: [PATCH 156/338] feat(license): verify candidate wheel closure metadata --- tools/verify_candidate_wheel_licenses.py | 189 +++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 tools/verify_candidate_wheel_licenses.py diff --git a/tools/verify_candidate_wheel_licenses.py b/tools/verify_candidate_wheel_licenses.py new file mode 100644 index 00000000..d026e3f1 --- /dev/null +++ b/tools/verify_candidate_wheel_licenses.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +"""Verify license metadata for the exact pg8000 candidate wheel closure. + +Artifact hashes prove which wheels were downloaded, but they do not prove that +every transitive package satisfies the repository's commercial inbound-license +policy. This verifier reads METADATA directly from the already hash-verified +wheels, requires the exact reviewed package/version set, rejects GPL-family +metadata, and requires positive permissive-license evidence for every wheel. +It never imports or executes candidate package code. +""" + +from __future__ import annotations + +from email.parser import Parser +from pathlib import Path +import re +import sys +import zipfile + + +_MAX_METADATA_BYTES = 524_288 +_CANONICAL_NAME_SEPARATOR = re.compile(r"[-_.]+") +_GPL_FAMILY = re.compile(r"(?:^|[^a-z])(agpl|lgpl|gpl)(?:[^a-z]|$)") + +_EXPECTED_WHEELS = { + "pg8000-1.31.5-py3-none-any.whl": ( + "pg8000", + "1.31.5", + ("bsd",), + ), + "python_dateutil-2.9.0.post0-py2.py3-none-any.whl": ( + "python-dateutil", + "2.9.0.post0", + ("apache software license", "bsd license"), + ), + "scramp-1.4.17-py3-none-any.whl": ( + "scramp", + "1.4.17", + ("mit-0", "mit no attribution", "mit no attribution license"), + ), + "asn1crypto-1.5.1-py2.py3-none-any.whl": ( + "asn1crypto", + "1.5.1", + ("mit",), + ), + "six-1.17.0-py2.py3-none-any.whl": ( + "six", + "1.17.0", + ("mit",), + ), +} + + +class CandidateWheelLicenseError(RuntimeError): + """Reject candidate metadata that cannot support commercial admission.""" + + +def _canonical_distribution_name(value: str) -> str: + """Apply the package-name normalization used for exact identity comparison.""" + return _CANONICAL_NAME_SEPARATOR.sub("-", value).casefold() + + +def _read_metadata(wheel_path: Path) -> str: + """Read one bounded wheel METADATA member without extracting package code.""" + try: + with zipfile.ZipFile(wheel_path) as archive: + metadata_members = [ + info + for info in archive.infolist() + if info.filename.endswith(".dist-info/METADATA") + ] + if len(metadata_members) != 1: + raise CandidateWheelLicenseError( + "candidate wheel metadata identity is invalid" + ) + metadata_member = metadata_members[0] + if metadata_member.file_size > _MAX_METADATA_BYTES: + raise CandidateWheelLicenseError( + "candidate wheel metadata exceeds the bounded evidence size" + ) + raw_metadata = archive.read(metadata_member) + except (OSError, zipfile.BadZipFile, RuntimeError): + raise CandidateWheelLicenseError("candidate wheel could not be inspected") from None + + if len(raw_metadata) > _MAX_METADATA_BYTES: + raise CandidateWheelLicenseError( + "candidate wheel metadata exceeds the bounded evidence size" + ) + try: + return raw_metadata.decode("utf-8") + except UnicodeDecodeError: + raise CandidateWheelLicenseError("candidate wheel metadata is not UTF-8") from None + + +def _license_evidence(metadata_text: str) -> str: + """Collect declared license fields and classifiers into bounded evidence text.""" + message = Parser().parsestr(metadata_text) + evidence_values: list[str] = [] + for header in ("License-Expression", "License"): + value = message.get(header) + if value: + evidence_values.append(value) + evidence_values.extend( + classifier + for classifier in message.get_all("Classifier", []) + if classifier.casefold().startswith("license ::") + ) + return "\n".join(evidence_values).casefold() + + +def _verify_one_wheel( + wheel_path: Path, + *, + expected_name: str, + expected_version: str, + approved_markers: tuple[str, ...], +) -> None: + """Validate exact package identity and positive/negative license evidence.""" + metadata_text = _read_metadata(wheel_path) + message = Parser().parsestr(metadata_text) + package_name = message.get("Name") + package_version = message.get("Version") + if ( + type(package_name) is not str + or _canonical_distribution_name(package_name) != expected_name + or type(package_version) is not str + or package_version != expected_version + ): + raise CandidateWheelLicenseError("candidate wheel package identity is invalid") + + license_evidence = _license_evidence(metadata_text) + if not license_evidence: + raise CandidateWheelLicenseError( + "candidate wheel lacks approved license evidence" + ) + if ( + _GPL_FAMILY.search(license_evidence) is not None + or "gnu general public license" in license_evidence + or "gnu lesser general public license" in license_evidence + or "gnu affero general public license" in license_evidence + ): + raise CandidateWheelLicenseError( + "candidate wheel contains a disallowed license" + ) + if not any(marker in license_evidence for marker in approved_markers): + raise CandidateWheelLicenseError( + "candidate wheel lacks approved license evidence" + ) + + +def verify_candidate_wheel_licenses(directory: Path) -> None: + """Verify the exact immutable pg8000 candidate closure in ``directory``. + + The directory is expected to contain only the five wheel artifacts that the + CI digest gate pins and later installs with ``--no-deps``. Requiring the same + exact set prevents an unreviewed extra artifact from entering license evidence + without a corresponding digest and policy decision. + """ + if type(directory) is not Path or not directory.is_dir(): + raise CandidateWheelLicenseError("candidate wheel directory is invalid") + wheel_names = {path.name for path in directory.glob("*.whl")} + if wheel_names != set(_EXPECTED_WHEELS): + raise CandidateWheelLicenseError("candidate wheel set is invalid") + + for filename, (package_name, package_version, approved_markers) in sorted( + _EXPECTED_WHEELS.items() + ): + _verify_one_wheel( + directory / filename, + expected_name=package_name, + expected_version=package_version, + approved_markers=approved_markers, + ) + + +def main(argv: list[str] | None = None) -> int: + """Run license admission against one exact candidate wheel directory.""" + arguments = sys.argv[1:] if argv is None else argv + if len(arguments) != 1: + raise SystemExit("usage: verify_candidate_wheel_licenses.py WHEEL_DIRECTORY") + try: + verify_candidate_wheel_licenses(Path(arguments[0])) + except CandidateWheelLicenseError as exc: + raise SystemExit(str(exc)) from None + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 730997426838e11c8258ef06de610e13385561a1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 12:06:46 +0900 Subject: [PATCH 157/338] test(ci): require candidate license gate before install --- tests/test_candidate_wheel_license_verifier.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/test_candidate_wheel_license_verifier.py b/tests/test_candidate_wheel_license_verifier.py index e3ff8d86..fcd4d43b 100644 --- a/tests/test_candidate_wheel_license_verifier.py +++ b/tests/test_candidate_wheel_license_verifier.py @@ -15,7 +15,8 @@ import pytest -_TOOL_PATH = Path(__file__).parents[1] / "tools" / "verify_candidate_wheel_licenses.py" +_REPOSITORY_ROOT = Path(__file__).parents[1] +_TOOL_PATH = _REPOSITORY_ROOT / "tools" / "verify_candidate_wheel_licenses.py" def _load_verifier(): @@ -142,3 +143,16 @@ def test_candidate_closure_rejects_unexpected_wheel_set(tmp_path: Path) -> None: with pytest.raises(verifier.CandidateWheelLicenseError, match="wheel set is invalid"): verifier.verify_candidate_wheel_licenses(tmp_path) + + +def test_candidate_license_gate_runs_before_candidate_install() -> None: + """CI must verify license metadata before any candidate wheel is installed.""" + workflow = (_REPOSITORY_ROOT / ".github" / "workflows" / "ci.yml").read_text( + encoding="utf-8" + ) + verification_step = "- name: Verify pg8000 candidate dependency licenses" + install_step = "- name: Install exact candidate closure into the CI environment" + + assert verification_step in workflow + assert "python tools/verify_candidate_wheel_licenses.py /tmp/pg8000-candidate" in workflow + assert workflow.index(verification_step) < workflow.index(install_step) From a315f5a6f8e31dd8e0a99d32bec6c82db24737ea Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 12:07:11 +0900 Subject: [PATCH 158/338] fix(ci): gate candidate install on license metadata --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1446e7b6..26087006 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,6 +143,8 @@ jobs: db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67 /tmp/pg8000-candidate/asn1crypto-1.5.1-py2.py3-none-any.whl 4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 /tmp/pg8000-candidate/six-1.17.0-py2.py3-none-any.whl EOF + - name: Verify pg8000 candidate dependency licenses + run: python tools/verify_candidate_wheel_licenses.py /tmp/pg8000-candidate - name: Install exact candidate closure into the CI environment run: >- uv pip install --python .venv/bin/python --no-deps From 3b786309f41b3df1dca5bb33d50393bbf046cb91 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 12:12:02 +0900 Subject: [PATCH 159/338] docs(gap): establish live commercial readiness baseline --- docs/product-technical-gap-baseline.md | 56 ++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 docs/product-technical-gap-baseline.md diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md new file mode 100644 index 00000000..d31753f2 --- /dev/null +++ b/docs/product-technical-gap-baseline.md @@ -0,0 +1,56 @@ +# Product and technical gap baseline + +This document records shipped truth separately from active-PR evidence. Exact PR heads, checks, reviews, rulesets, and release identities must always be read live before merge or release decisions; this file is not a substitute for GitHub evidence. + +## Product boundary + +pg-llm-batch owns durable PostgreSQL-backed asynchronous LLM batch preparation, token/size accounting, provider-neutral `BatchInferencePort` lifecycle state, tenant/RLS enforcement, result ingestion, audit/provenance, and recovery. Model/provider discovery and routing remain contextual-orchestrator authority. Foreign product truth is consumed through released versioned contracts and anti-corruption layers; source copying, mutable-branch production dependencies, and cross-service application-table SQL are out of bounds. + +## Protected-main truth + +The protected integration branch is `main`. At the latest refresh it was `bdff1273d3885dedc5187632e1c8838b470c9b6d`. The package remains version `0.1.0`, and its production dependency graph still includes `psycopg[binary]>=3.1`. Therefore issue #322, replacement of the LGPL-family Psycopg runtime dependency, remains an open commercial-policy defect. No public release should claim that the current `pip install .` runtime graph is commercially clean while that defect remains. + +The repository has no immutable GitHub release at the latest refresh. A release is not ready merely because a branch is green: one exact protected head must pass the repository's applicable CI, security, coverage/docstring, package, SBOM/provenance, reproducibility, migration/rollback/recovery, operability, and review gates before version/tag/publication evidence is promoted. + +## Active delivery lanes + +PR #233 remains the dependency-root delivery lane and must be judged from its live head and live base, not predecessor evidence. + +PR #323 is the active Draft migration lane for issue #322. It establishes a driver-neutral PostgreSQL anti-corruption port, retains Psycopg only as the current baseline adapter, and evaluates pg8000 1.31.5 as candidate evidence without promoting it into the production manifest. The lane already exercises parameter binding, tuple-row normalization, row-count semantics, transaction and cleanup precedence, forced-RLS tenant scope, JSONB/UUID/timestamp behavior, exact candidate dependency hashes, and real PostgreSQL candidate smoke tests. + +The current candidate supply-chain work also verifies license metadata for the exact five-wheel pg8000 candidate closure before installation. The verifier reads bounded wheel `METADATA` without importing candidate code, rejects GPL/LGPL/AGPL-family declarations, requires positive reviewed permissive-license evidence for every exact package/version, and rejects an unexpected wheel set. This strengthens candidate admission but does not itself approve a production driver replacement. + +## Highest-priority gaps + +| Gap | Current state | Required next evidence | +| --- | --- | --- | +| Commercial PostgreSQL runtime dependency | P0 / active | Complete issue #322: preserve shipped DB semantics while removing every disallowed GPL/LGPL/AGPL-family runtime package from the committed dependency graph. | +| Candidate driver contract parity | Active Draft | Close conninfo URI/keyword/service-selector compatibility, driver-level JSONB/error classification, concurrency/recovery, timeout/health, schema/restore, and package-installed behavior with realistic PostgreSQL evidence. | +| Candidate supply-chain admission | Active / strengthened | Exact wheel hashes and license metadata are now gated; complete vulnerability/SBOM/provenance and final runtime-graph evidence before promotion. | +| Exact-head CI execution | External control-plane dependency plus local continuation | Current required jobs must acquire a runner and check out the exact current head. Queued/pre-checkout evidence is non-passing; central `.github#712` owns the organization runner-admission diagnosis. | +| Immutable product release | Not yet published | After the production dependency replacement and all gates pass on one integrated protected head, perform version/CHANGELOG/tag/package/SBOM/provenance/reproducibility/rollback publication and verify artifact identity. | +| Context Graph / EA projection | Candidate-only until released authority exists | `context-graph-contracts` and `enterprise-architecture-core` currently expose no immutable GitHub release. Do not pin mutable producer heads. Continue pg-owned release-readiness seams and adopt only a verified released contract. | + +## Commercial acceptance for issue #322 + +Completion requires all of the following on the final production graph, not only candidate fixtures: + +- parameterized SQL and injection-safe bindings remain intact; +- commit, rollback, context-manager, cleanup-error precedence, cancellation/recovery, and connection lifecycle remain deterministic; +- tenant authority and transaction-local `set_config` behavior remain correct under forced RLS and restricted roles; +- JSON/JSONB, UUID, timestamp, row, row-count, and relevant PostgreSQL error semantics remain compatible; +- DSN parsing/rendering preserves the repository's supported URI, keyword, and service-selector contract without credential leakage into argv or logs; +- concurrency, idempotency, checkpoint, schema application, logical restore, health, and finite-connect behavior pass realistic PostgreSQL tests; +- the committed runtime graph and built artifacts contain no disallowed GPL/LGPL/AGPL-family package; +- package, license, vulnerability, SBOM, provenance, and reproducibility evidence bind the same immutable artifacts; +- the final unchanged head passes exact-source required checks and then-live review/ruleset requirements without self-approval or gate weakening. + +## Context Fabric boundary + +`context-graph-contracts` remains a contract-only Shared Kernel for canonical object/authority references, truth origin/status, bitemporal semantics, provenance, Context Assertion, and CloudEvents/schema/conformance/admission contracts. `enterprise-architecture-core` remains the EA Decision Plane. While their dedicated Context Fabric writer is active, pg-llm-batch treats both repositories as read-only source dependencies and advances their existing owner paths with exact consumer RED/GREEN criteria instead of creating competing writers. + +Prompt, response, batch-result, and user data remain pg/product-domain data and are not copied into EA authoritative architecture tables. Deployable service/API/worker/database/runtime/provider/version and lifecycle/risk/ownership/remediation changes may be projected only through a verified released Context Graph contract with provenance. + +## Evidence discipline + +Queued, pending, skipped-required, cancelled, absent, predecessor-head, model-only, and status-only evidence is non-passing. A current blocker is the next work item at its actual owner: pg-owned causes require a realistic RED, the smallest causal repair, focused/full GREEN, and exact-head refetch; foreign-owned causes require advancement of the existing owner path followed by independent pg work. A report, comment, handoff, or documentation-only change is never completion while executable code/test/release work remains. From cf7c3462cc3a91c6b9fe0d39a5535ca88f0d30a7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 12:14:29 +0900 Subject: [PATCH 160/338] test(license): reject permissive substring false positives --- tests/test_candidate_wheel_license_verifier.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_candidate_wheel_license_verifier.py b/tests/test_candidate_wheel_license_verifier.py index fcd4d43b..953aec87 100644 --- a/tests/test_candidate_wheel_license_verifier.py +++ b/tests/test_candidate_wheel_license_verifier.py @@ -113,6 +113,23 @@ def test_candidate_closure_rejects_gpl_family_metadata_even_with_permissive_mark verifier.verify_candidate_wheel_licenses(tmp_path) +def test_candidate_closure_rejects_permissive_token_inside_unapproved_word(tmp_path: Path) -> None: + verifier = _load_verifier() + _write_valid_closure(tmp_path) + wheel_path = tmp_path / "six-1.17.0-py2.py3-none-any.whl" + wheel_path.unlink() + _write_wheel( + tmp_path, + wheel_path.name, + name="six", + version="1.17.0", + license_lines=("License: Limited proprietary terms",), + ) + + with pytest.raises(verifier.CandidateWheelLicenseError, match="approved license evidence"): + verifier.verify_candidate_wheel_licenses(tmp_path) + + def test_candidate_closure_rejects_missing_positive_license_evidence(tmp_path: Path) -> None: verifier = _load_verifier() _write_valid_closure(tmp_path) From 2d3874bcd84fff540825225540233b3b5aef0a36 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 12:14:53 +0900 Subject: [PATCH 161/338] fix(license): require bounded permissive markers --- tools/verify_candidate_wheel_licenses.py | 37 +++++++++++++++++------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/tools/verify_candidate_wheel_licenses.py b/tools/verify_candidate_wheel_licenses.py index d026e3f1..dc5fbc1c 100644 --- a/tools/verify_candidate_wheel_licenses.py +++ b/tools/verify_candidate_wheel_licenses.py @@ -92,20 +92,34 @@ def _read_metadata(wheel_path: Path) -> str: raise CandidateWheelLicenseError("candidate wheel metadata is not UTF-8") from None -def _license_evidence(metadata_text: str) -> str: - """Collect declared license fields and classifiers into bounded evidence text.""" +def _license_evidence(metadata_text: str) -> tuple[str, ...]: + """Collect declared license fields and classifiers as separate evidence lines.""" message = Parser().parsestr(metadata_text) evidence_values: list[str] = [] for header in ("License-Expression", "License"): value = message.get(header) if value: - evidence_values.append(value) + evidence_values.append(value.casefold()) evidence_values.extend( - classifier + classifier.casefold() for classifier in message.get_all("Classifier", []) if classifier.casefold().startswith("license ::") ) - return "\n".join(evidence_values).casefold() + return tuple(evidence_values) + + +def _contains_marker(evidence_lines: tuple[str, ...], marker: str) -> bool: + """Match one approved marker on non-alphanumeric boundaries only. + + License metadata is untrusted decision input. Substring matching would accept + an unrelated word such as ``limited`` for the reviewed ``MIT`` marker. The + boundary check still accepts SPDX identifiers and classifier phrases while + preventing a permissive token from being smuggled inside another word. + """ + marker_pattern = re.compile( + rf"(? Date: Fri, 4 Sep 2026 12:33:00 +0900 Subject: [PATCH 162/338] test(postgres): reject driver fetch overdelivery --- tests/test_psycopg_driver_adapter.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/test_psycopg_driver_adapter.py b/tests/test_psycopg_driver_adapter.py index 76b2b5a1..45a64300 100644 --- a/tests/test_psycopg_driver_adapter.py +++ b/tests/test_psycopg_driver_adapter.py @@ -138,6 +138,20 @@ def test_cursor_adapter_rejects_non_positive_or_non_integer_fetch_budget( PsycopgCursorAdapter(raw).fetchmany(invalid_size) # type: ignore[arg-type] +def test_cursor_adapter_rejects_driver_overdelivery_beyond_fetch_budget() -> None: + """Fail closed if a concrete driver violates the port's finite fetch budget.""" + + class _OverdeliveringCursor(_RawCursor): + def fetchmany(self, size: int) -> list[object]: + assert size == 1 + return list(self.rows) + + raw = _OverdeliveringCursor() + + with pytest.raises(PsycopgDriverAdapterError, match="exceeds requested size"): + PsycopgCursorAdapter(raw).fetchmany(1) + + def test_cursor_adapter_rejects_non_integer_row_count() -> None: raw = _RawCursor() raw.rowcount = True @@ -278,4 +292,4 @@ def fake_connect(*args: object, **kwargs: object) -> None: connect_timeout_seconds=invalid, # type: ignore[arg-type] ) - assert called is False + assert called is False \ No newline at end of file From 10afce6b6a8b2cc836edac7bf24c6e2bb2e26c6a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 12:33:38 +0900 Subject: [PATCH 163/338] fix(postgres): enforce finite cursor fetch budgets --- pg_llm_batch/psycopg_driver_adapter.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/pg_llm_batch/psycopg_driver_adapter.py b/pg_llm_batch/psycopg_driver_adapter.py index 6c1277be..2762fd66 100644 --- a/pg_llm_batch/psycopg_driver_adapter.py +++ b/pg_llm_batch/psycopg_driver_adapter.py @@ -95,10 +95,21 @@ def fetchone(self) -> tuple[object, ...] | None: return self._normalize_result_row(row) def fetchmany(self, size: int) -> list[tuple[object, ...]]: - """Return a finite page while rejecting malformed materialized rows.""" + """Return at most the requested finite page and reject malformed results.""" if type(size) is not int or size <= 0: raise PsycopgDriverAdapterError("PostgreSQL driver fetch size is invalid") - return [self._normalize_result_row(row) for row in self._cursor.fetchmany(size)] + rows = self._cursor.fetchmany(size) + try: + returned_count = len(rows) + except (TypeError, ValueError, OverflowError): + raise PsycopgDriverAdapterError( + "PostgreSQL driver fetch result is invalid" + ) from None + if returned_count > size: + raise PsycopgDriverAdapterError( + "PostgreSQL driver fetch result exceeds requested size" + ) + return [self._normalize_result_row(row) for row in rows] def fetchall(self) -> list[tuple[object, ...]]: """Return bounded query results while rejecting malformed rows.""" @@ -244,4 +255,4 @@ def is_invalid_conninfo(self, error: BaseException) -> bool: def is_undefined_function(self, error: BaseException) -> bool: """Recognize only Psycopg's PostgreSQL undefined-function error category.""" - return isinstance(error, UndefinedFunction) + return isinstance(error, UndefinedFunction) \ No newline at end of file From 5e74d95494f34e706516c885f4087dd7ad124c64 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 13:03:49 +0900 Subject: [PATCH 164/338] test(postgres): preserve candidate closed state after close error --- ...est_pg8000_driver_candidate_close_state.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/test_pg8000_driver_candidate_close_state.py diff --git a/tests/test_pg8000_driver_candidate_close_state.py b/tests/test_pg8000_driver_candidate_close_state.py new file mode 100644 index 00000000..e0b440c6 --- /dev/null +++ b/tests/test_pg8000_driver_candidate_close_state.py @@ -0,0 +1,42 @@ +"""Recovery-state regressions for the candidate pg8000 connection adapter.""" + +from __future__ import annotations + +import pytest + +from pg_llm_batch.pg8000_driver_candidate_adapter import ( + Pg8000CandidateConnectionAdapter, +) + + +class _ProtocolCloseFailureConnection: + """Model pg8000 closing its socket while protocol-level close reports failure.""" + + def __init__(self) -> None: + self.closed = False + self.close_count = 0 + + def close(self) -> None: + """Release the underlying capability, then report the protocol failure.""" + self.close_count += 1 + self.closed = True + raise Runtime_Limit_Close_Error("protocol close failed") + + +class Runtime_Limit_Close_Error(RuntimeError): + """Distinguish the synthetic protocol-close failure from assertion failures.""" + + +def test_candidate_marks_connection_closed_when_protocol_close_reports_failure() -> None: + """A released pg8000 socket must not remain reusable after close raises.""" + raw = _ProtocolCloseFailureConnection() + adapter = Pg8000CandidateConnectionAdapter(raw) + + assert adapter.is_closed() is False + + with pytest.raises(RuntimeError, match="protocol close failed"): + adapter.close() + + assert raw.close_count == 1 + assert raw.closed is True + assert adapter.is_closed() is True From 545961dc0aeb614808770efb6feadd40b994ecbb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 13:04:27 +0900 Subject: [PATCH 165/338] test(postgres): keep close-state regression lint-clean --- tests/test_pg8000_driver_candidate_close_state.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/test_pg8000_driver_candidate_close_state.py b/tests/test_pg8000_driver_candidate_close_state.py index e0b440c6..b588f48d 100644 --- a/tests/test_pg8000_driver_candidate_close_state.py +++ b/tests/test_pg8000_driver_candidate_close_state.py @@ -20,11 +20,7 @@ def close(self) -> None: """Release the underlying capability, then report the protocol failure.""" self.close_count += 1 self.closed = True - raise Runtime_Limit_Close_Error("protocol close failed") - - -class Runtime_Limit_Close_Error(RuntimeError): - """Distinguish the synthetic protocol-close failure from assertion failures.""" + raise RuntimeError("protocol close failed") def test_candidate_marks_connection_closed_when_protocol_close_reports_failure() -> None: From b1753322bd379070b0179c06ccbe13e0aa707f7a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 13:05:01 +0900 Subject: [PATCH 166/338] fix(postgres): retain pg8000 terminal close state on failure --- pg_llm_batch/pg8000_driver_candidate_adapter.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/pg_llm_batch/pg8000_driver_candidate_adapter.py b/pg_llm_batch/pg8000_driver_candidate_adapter.py index 0cfb14dc..bff8e1ae 100644 --- a/pg_llm_batch/pg8000_driver_candidate_adapter.py +++ b/pg_llm_batch/pg8000_driver_candidate_adapter.py @@ -311,15 +311,18 @@ def is_closed(self) -> bool: return self._closed def close(self) -> None: - """Close the retained raw connection and record successful local cleanup. + """Release the raw connection while preserving pg8000's terminal-close state. - The state flips only after the raw close returns successfully. A close - failure therefore remains visible and cannot be misrepresented as a - released session authority; transport-failure recovery is still a later - candidate acceptance gate. + pg8000 1.31.5 documents that its underlying socket is closed even if the + PostgreSQL protocol-level close reports an error. The adapter therefore + records this capability as locally closed in ``finally`` while allowing + the original close failure to propagate. A failed close must never leave + a definitively released connection eligible for reuse. """ - self._connection.close() - self._closed = True + try: + self._connection.close() + finally: + self._closed = True def __enter__(self) -> Pg8000CandidateConnectionAdapter: """Enter the package transaction context without a driver-only extension. From f055a860dfbb1c748443f03f160bc8a9c083218c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:04:41 +0900 Subject: [PATCH 167/338] test(postgres): reject shaped candidate capability evidence --- ...tgres_driver_candidate_capability_shape.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/test_postgres_driver_candidate_capability_shape.py diff --git a/tests/test_postgres_driver_candidate_capability_shape.py b/tests/test_postgres_driver_candidate_capability_shape.py new file mode 100644 index 00000000..ecd6946b --- /dev/null +++ b/tests/test_postgres_driver_candidate_capability_shape.py @@ -0,0 +1,48 @@ +"""Capability-shape regressions for PostgreSQL driver candidate evidence.""" + +from __future__ import annotations + +from dataclasses import replace + +import pytest + +from pg_llm_batch.postgres_driver_candidate import ( + REQUIRED_POSTGRES_DRIVER_CAPABILITIES, + REQUIRED_POSTGRES_DRIVER_PYTHON_VERSIONS, + PostgresDriverCandidateEvidence, + PostgresDriverCandidateEvidenceError, +) + + +class _StringSubclass(str): + """Represent a shaped string that must not enter supply-chain evidence.""" + + +def _valid_evidence() -> PostgresDriverCandidateEvidence: + """Build one exact primitive candidate receipt for shape-validation tests.""" + return PostgresDriverCandidateEvidence( + package_name="pg8000", + package_version="1.31.5", + license_spdx="BSD-3-Clause", + license_report_sha256="1" * 64, + python_versions=tuple(sorted(REQUIRED_POSTGRES_DRIVER_PYTHON_VERSIONS)), + source_commit_sha="2" * 40, + artifact_sha256="3" * 64, + vulnerability_report_sha256="4" * 64, + capability_report_sha256="5" * 64, + known_vulnerability_ids=(), + capabilities=REQUIRED_POSTGRES_DRIVER_CAPABILITIES, + ) + + +def test_candidate_rejects_string_subclass_capability_before_set_comparison() -> None: + """Supply-chain capability evidence must contain exact built-in strings only.""" + capabilities = set(REQUIRED_POSTGRES_DRIVER_CAPABILITIES) + capabilities.remove("jsonb") + capabilities.add(_StringSubclass("jsonb")) + + with pytest.raises( + PostgresDriverCandidateEvidenceError, + match="PostgreSQL driver capability evidence is invalid", + ): + replace(_valid_evidence(), capabilities=frozenset(capabilities)) From e12bc01c561438b940ebef99a599e5e27766021f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 14:05:20 +0900 Subject: [PATCH 168/338] fix(postgres): reject shaped capability evidence --- pg_llm_batch/postgres_driver_candidate.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index 71ad5222..2fe06cd2 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -255,6 +255,10 @@ def __post_init__(self) -> None: raise PostgresDriverCandidateEvidenceError( "PostgreSQL driver capability evidence is invalid" ) + if any(type(capability) is not str for capability in self.capabilities): + raise PostgresDriverCandidateEvidenceError( + "PostgreSQL driver capability evidence is invalid" + ) if len(self.capabilities) > len(REQUIRED_POSTGRES_DRIVER_CAPABILITIES): raise PostgresDriverCandidateEvidenceError( "PostgreSQL driver capability evidence contains an unknown capability" From 2feb630a5e630d367cb9fbed85ac7ba00c03ca86 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 15:06:21 +0900 Subject: [PATCH 169/338] test(compose): require default driver conninfo boundary --- tests/test_compose_bootstrap_driver_port.py | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_compose_bootstrap_driver_port.py b/tests/test_compose_bootstrap_driver_port.py index eed150e2..c009bdc1 100644 --- a/tests/test_compose_bootstrap_driver_port.py +++ b/tests/test_compose_bootstrap_driver_port.py @@ -56,6 +56,31 @@ def test_build_private_dsn_uses_injected_driver_without_legacy_renderer() -> Non ] +def test_build_private_dsn_uses_default_driver_boundary_without_direct_renderer( + monkeypatch, +) -> None: + """Default secret assembly must select the driver port rather than Psycopg helpers.""" + driver = _BootstrapDriver() + monkeypatch.setattr(compose_bootstrap, "_default_postgres_driver", lambda: driver) + + private_dsn = compose_bootstrap._build_private_dsn( + "postgresql://pgllm@postgres:5432/pgllm", + "private-password", + ) + + assert private_dsn == "driver-private-dsn" + assert driver.parsed == ["postgresql://pgllm@postgres:5432/pgllm"] + assert driver.rendered == [ + { + "user": "pgllm", + "host": "postgres", + "port": "5432", + "dbname": "pgllm", + "password": "private-password", + } + ] + + def test_run_compose_health_forwards_one_driver_to_dsn_and_health_boundaries( tmp_path: Path, monkeypatch, From 78982a28139ceebba691ef2c5942edfaa5c812af Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 15:06:48 +0900 Subject: [PATCH 170/338] refactor(compose): route default conninfo through driver port --- pg_llm_batch/compose_bootstrap.py | 44 +++++++++++++++++++------------ 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/pg_llm_batch/compose_bootstrap.py b/pg_llm_batch/compose_bootstrap.py index 7b58fa6d..94301266 100644 --- a/pg_llm_batch/compose_bootstrap.py +++ b/pg_llm_batch/compose_bootstrap.py @@ -20,15 +20,23 @@ from .health import serve_healthz from .postgres_driver_port import PostgresDriverPort -try: # pragma: no cover - retained optional dependency during migration - from psycopg.conninfo import make_conninfo as _psycopg_make_conninfo -except ImportError: # pragma: no cover - _psycopg_make_conninfo = None - _DEFAULT_PASSWORD_FILE = Path("/run/secrets/postgres_password") _MAX_PASSWORD_BYTES = 65_536 +def _default_postgres_driver() -> PostgresDriverPort: + """Load the retained default through the same PostgreSQL anti-corruption port. + + Compose secret assembly must not import a concrete driver's conninfo helper + independently from the runtime database boundary. Keeping this lazy loader + behind ``PostgresDriverPort`` makes the retained Psycopg default replaceable + without creating a second connection-selector authority in the bootstrap. + """ + from .psycopg_driver_adapter import PsycopgDriverAdapter + + return PsycopgDriverAdapter() + + def _load_database_password(password_file: Path) -> str: """Read one bounded UTF-8 password from an explicitly mounted secret file.""" try: @@ -62,20 +70,22 @@ def _build_private_dsn( ) -> str: """Add the mounted password through the selected reviewed conninfo renderer. - An injected replacement driver parses the credential-free selector and then - renders a fresh parameter snapshot containing the mounted password. The - retained Psycopg renderer remains the default only while the commercial - migration is incomplete. Parser or renderer diagnostics are normalized so - secret material never escapes this bootstrap boundary. + The selected driver parses the credential-free selector and renders a fresh + parameter snapshot containing the mounted password. The retained Psycopg + implementation is loaded only through ``PostgresDriverPort`` while the + commercial migration is incomplete, so this module has no independent + concrete-driver conninfo authority. Parser or renderer diagnostics are + normalized so secret material never escapes this bootstrap boundary. """ + driver = ( + postgres_driver + if postgres_driver is not None + else _default_postgres_driver() + ) try: - if postgres_driver is not None: - parameters = dict(postgres_driver.parse_conninfo(base_dsn)) - parameters["password"] = password - return postgres_driver.make_conninfo(parameters) - if _psycopg_make_conninfo is None: - raise ConfigError("The PostgreSQL bootstrap driver is unavailable.") - return _psycopg_make_conninfo(base_dsn, password=password) + parameters = dict(driver.parse_conninfo(base_dsn)) + parameters["password"] = password + return driver.make_conninfo(parameters) except ConfigError: raise except Exception: From 49566dd3ff27dbe2bf09e974208f5f5279ac3d4d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 16:03:23 +0900 Subject: [PATCH 171/338] test(postgres): reject cross-thread pg8000 candidate reuse --- ...pg8000_driver_candidate_thread_affinity.py | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 tests/test_pg8000_driver_candidate_thread_affinity.py diff --git a/tests/test_pg8000_driver_candidate_thread_affinity.py b/tests/test_pg8000_driver_candidate_thread_affinity.py new file mode 100644 index 00000000..b9aea745 --- /dev/null +++ b/tests/test_pg8000_driver_candidate_thread_affinity.py @@ -0,0 +1,133 @@ +"""Concurrency regressions for the candidate pg8000 DB-API boundary. + +pg8000 1.31.5 declares DB-API ``threadsafety == 1``: threads may share the +module, but not connections. The candidate adapter must therefore fail before +raw driver access when a connection or cursor capability crosses its creating +thread. This is candidate-admission evidence only; it does not promote pg8000 +into the production dependency graph. +""" + +from __future__ import annotations + +from concurrent.futures import ThreadPoolExecutor +from typing import Callable + +import pytest + +from pg_llm_batch.pg8000_driver_candidate_adapter import ( + Pg8000CandidateAdapterError, + Pg8000CandidateConnectionAdapter, + Pg8000CandidateCursorAdapter, +) + + +class _RawCursor: + def __init__(self) -> None: + self.calls = 0 + self.rowcount = 1 + + def execute(self, _query: str, _params: object | None = None) -> None: + self.calls += 1 + + def executemany(self, _query: str, _params_seq: object) -> None: + self.calls += 1 + + def fetchone(self) -> tuple[int]: + self.calls += 1 + return (1,) + + def fetchmany(self, _size: int) -> list[tuple[int]]: + self.calls += 1 + return [(1,)] + + def fetchall(self) -> list[tuple[int]]: + self.calls += 1 + return [(1,)] + + def close(self) -> None: + self.calls += 1 + + +class _RawConnection: + def __init__(self) -> None: + self.calls = 0 + self._autocommit = False + + @property + def autocommit(self) -> bool: + return self._autocommit + + @autocommit.setter + def autocommit(self, value: bool) -> None: + self.calls += 1 + self._autocommit = value + + def cursor(self) -> _RawCursor: + self.calls += 1 + return _RawCursor() + + def commit(self) -> None: + self.calls += 1 + + def rollback(self) -> None: + self.calls += 1 + + def close(self) -> None: + self.calls += 1 + + +def _run_on_worker(operation: Callable[[], object]) -> object: + with ThreadPoolExecutor(max_workers=1) as executor: + return executor.submit(operation).result() + + +@pytest.mark.parametrize( + "operation", + ("cursor", "commit", "rollback", "set_autocommit", "close"), +) +def test_candidate_connection_rejects_cross_thread_driver_access(operation: str) -> None: + raw = _RawConnection() + adapter = Pg8000CandidateConnectionAdapter(raw) + + callbacks: dict[str, Callable[[], object]] = { + "cursor": adapter.cursor, + "commit": adapter.commit, + "rollback": adapter.rollback, + "set_autocommit": lambda: adapter.set_autocommit(True), + "close": adapter.close, + } + + with pytest.raises( + Pg8000CandidateAdapterError, + match="must not be shared across threads", + ): + _run_on_worker(callbacks[operation]) + + assert raw.calls == 0 + + +@pytest.mark.parametrize( + "operation", + ("execute", "executemany", "fetchone", "fetchmany", "fetchall", "row_count", "close"), +) +def test_candidate_cursor_rejects_cross_thread_driver_access(operation: str) -> None: + raw = _RawCursor() + adapter = Pg8000CandidateCursorAdapter(raw) + + callbacks: dict[str, Callable[[], object]] = { + "execute": lambda: adapter.execute("SELECT %s", (1,)), + "executemany": lambda: adapter.executemany("SELECT %s", [(1,)]), + "fetchone": adapter.fetchone, + "fetchmany": lambda: adapter.fetchmany(1), + "fetchall": adapter.fetchall, + "row_count": adapter.row_count, + "close": lambda: adapter.__exit__(None, None, None), + } + + with pytest.raises( + Pg8000CandidateAdapterError, + match="must not be shared across threads", + ): + _run_on_worker(callbacks[operation]) + + assert raw.calls == 0 From 8372ac41607c6a28309fe195d75610cc84640270 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 16:05:31 +0900 Subject: [PATCH 172/338] feat(postgres): add pg8000 thread-affinity candidate boundary --- .../pg8000_thread_affine_candidate_adapter.py | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 pg_llm_batch/pg8000_thread_affine_candidate_adapter.py diff --git a/pg_llm_batch/pg8000_thread_affine_candidate_adapter.py b/pg_llm_batch/pg8000_thread_affine_candidate_adapter.py new file mode 100644 index 00000000..520e5eb9 --- /dev/null +++ b/pg_llm_batch/pg8000_thread_affine_candidate_adapter.py @@ -0,0 +1,151 @@ +"""Thread-affine pg8000 candidate adapters for concurrency admission. + +pg8000 1.31.5 declares DB-API ``threadsafety == 1``: threads may share the +module, but not connections. The portable candidate adapters intentionally do +not invent a stronger guarantee. This layer binds each candidate connection and +cursor to the thread that created it and fails before raw driver access when a +capability crosses that boundary. + +The layer remains candidate-only. It is exercised by the exact-artifact +PostgreSQL smoke test and must not be treated as production dependency approval +until the remaining conninfo, error, recovery, package, SBOM, and provenance +gates pass on one immutable artifact. +""" + +from __future__ import annotations + +from threading import get_ident +from typing import Any + +from .pg8000_driver_candidate_adapter import ( + Pg8000CandidateAdapterError, + Pg8000CandidateConnectionAdapter, + Pg8000CandidateCursorAdapter, +) + + +_THREAD_AFFINITY_ERROR = "PostgreSQL driver connection must not be shared across threads" +_CURSOR_THREAD_AFFINITY_ERROR = "PostgreSQL driver cursor must not be shared across threads" + + +class Pg8000ThreadAffineCandidateCursorAdapter(Pg8000CandidateCursorAdapter): + """Bind one candidate cursor capability to its creating thread. + + The base adapter owns DB-API row, parameter, fetch-budget, and cleanup + normalization. This subclass adds only the concurrency invariant required by + pg8000's declared thread-safety level and performs the check before every raw + cursor access. + """ + + def __init__(self, cursor: Any) -> None: + super().__init__(cursor) + self._owner_thread_id = get_ident() + + def _require_owner_thread(self) -> None: + """Reject cross-thread cursor use before touching driver-owned state.""" + if get_ident() != self._owner_thread_id: + raise Pg8000CandidateAdapterError(_CURSOR_THREAD_AFFINITY_ERROR) + + def execute( + self, + query: str, + params: object | None = None, + ) -> Pg8000ThreadAffineCandidateCursorAdapter: + """Execute only on the thread that owns the raw candidate cursor.""" + self._require_owner_thread() + super().execute(query, params) + return self + + def executemany( + self, + query: str, + params_seq: object, + ) -> Pg8000ThreadAffineCandidateCursorAdapter: + """Execute a parameter sequence only on the cursor owner thread.""" + self._require_owner_thread() + super().executemany(query, params_seq) + return self + + def fetchone(self) -> tuple[object, ...] | None: + """Fetch one row only from the cursor owner thread.""" + self._require_owner_thread() + return super().fetchone() + + def fetchmany(self, size: int) -> list[tuple[object, ...]]: + """Fetch one bounded page only from the cursor owner thread.""" + self._require_owner_thread() + return super().fetchmany(size) + + def fetchall(self) -> list[tuple[object, ...]]: + """Fetch an already bounded result only from the cursor owner thread.""" + self._require_owner_thread() + return super().fetchall() + + def row_count(self) -> int | None: + """Read affected-row evidence only from the cursor owner thread.""" + self._require_owner_thread() + return super().row_count() + + def __enter__(self) -> Pg8000ThreadAffineCandidateCursorAdapter: + """Enter the cursor context only on the owner thread.""" + self._require_owner_thread() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + traceback: object | None, + ) -> bool | None: + """Release the raw cursor only on the thread that owns it.""" + self._require_owner_thread() + return super().__exit__(exc_type, exc, traceback) + + +class Pg8000ThreadAffineCandidateConnectionAdapter(Pg8000CandidateConnectionAdapter): + """Bind one candidate connection and its cursors to the creating thread. + + pg8000's DB-API metadata explicitly permits module sharing but not connection + sharing. Serializing a shared connection with a lock would still exceed that + contract, so this adapter rejects cross-thread connection access rather than + treating mutual exclusion as proof of portability. + """ + + def __init__(self, connection: Any) -> None: + super().__init__(connection) + self._owner_thread_id = get_ident() + + def _require_owner_thread(self) -> None: + """Reject cross-thread connection use before touching raw driver state.""" + if get_ident() != self._owner_thread_id: + raise Pg8000CandidateAdapterError(_THREAD_AFFINITY_ERROR) + + def cursor(self) -> Pg8000ThreadAffineCandidateCursorAdapter: + """Create a thread-affine cursor on the exact owned connection.""" + self._require_owner_thread() + return Pg8000ThreadAffineCandidateCursorAdapter(self._connection.cursor()) + + def commit(self) -> None: + """Commit only on the thread that owns the candidate connection.""" + self._require_owner_thread() + super().commit() + + def rollback(self) -> None: + """Roll back only on the thread that owns the candidate connection.""" + self._require_owner_thread() + super().rollback() + + def set_autocommit(self, enabled: bool) -> None: + """Change transaction mode only on the candidate connection owner thread.""" + self._require_owner_thread() + super().set_autocommit(enabled) + + def close(self) -> None: + """Close the raw candidate connection only from its owner thread.""" + self._require_owner_thread() + super().close() + + def __enter__(self) -> Pg8000ThreadAffineCandidateConnectionAdapter: + """Enter the candidate transaction context only on the owner thread.""" + self._require_owner_thread() + return self From 6c95886f6f45e75525ae4e02db9c1c8392cb2a4a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 16:05:54 +0900 Subject: [PATCH 173/338] test(postgres): bind pg8000 candidate to owner thread --- ...pg8000_driver_candidate_thread_affinity.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/test_pg8000_driver_candidate_thread_affinity.py b/tests/test_pg8000_driver_candidate_thread_affinity.py index b9aea745..0f121dec 100644 --- a/tests/test_pg8000_driver_candidate_thread_affinity.py +++ b/tests/test_pg8000_driver_candidate_thread_affinity.py @@ -1,10 +1,10 @@ """Concurrency regressions for the candidate pg8000 DB-API boundary. pg8000 1.31.5 declares DB-API ``threadsafety == 1``: threads may share the -module, but not connections. The candidate adapter must therefore fail before -raw driver access when a connection or cursor capability crosses its creating -thread. This is candidate-admission evidence only; it does not promote pg8000 -into the production dependency graph. +module, but not connections. The admitted candidate boundary must therefore fail +before raw driver access when a connection or cursor capability crosses its +creating thread. This is candidate-admission evidence only; it does not promote +pg8000 into the production dependency graph. """ from __future__ import annotations @@ -14,10 +14,10 @@ import pytest -from pg_llm_batch.pg8000_driver_candidate_adapter import ( - Pg8000CandidateAdapterError, - Pg8000CandidateConnectionAdapter, - Pg8000CandidateCursorAdapter, +from pg_llm_batch.pg8000_driver_candidate_adapter import Pg8000CandidateAdapterError +from pg_llm_batch.pg8000_thread_affine_candidate_adapter import ( + Pg8000ThreadAffineCandidateConnectionAdapter, + Pg8000ThreadAffineCandidateCursorAdapter, ) @@ -87,7 +87,7 @@ def _run_on_worker(operation: Callable[[], object]) -> object: ) def test_candidate_connection_rejects_cross_thread_driver_access(operation: str) -> None: raw = _RawConnection() - adapter = Pg8000CandidateConnectionAdapter(raw) + adapter = Pg8000ThreadAffineCandidateConnectionAdapter(raw) callbacks: dict[str, Callable[[], object]] = { "cursor": adapter.cursor, @@ -112,7 +112,7 @@ def test_candidate_connection_rejects_cross_thread_driver_access(operation: str) ) def test_candidate_cursor_rejects_cross_thread_driver_access(operation: str) -> None: raw = _RawCursor() - adapter = Pg8000CandidateCursorAdapter(raw) + adapter = Pg8000ThreadAffineCandidateCursorAdapter(raw) callbacks: dict[str, Callable[[], object]] = { "execute": lambda: adapter.execute("SELECT %s", (1,)), From 634f8e5ab84257284017ab8cdacf95b5754fffda Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 16:06:24 +0900 Subject: [PATCH 174/338] test(postgres): exercise thread-affine pg8000 candidate --- tests/smoke_pg8000_candidate_postgres.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/smoke_pg8000_candidate_postgres.py b/tests/smoke_pg8000_candidate_postgres.py index adac241a..3493361a 100644 --- a/tests/smoke_pg8000_candidate_postgres.py +++ b/tests/smoke_pg8000_candidate_postgres.py @@ -3,9 +3,9 @@ This script is intentionally outside pytest discovery. CI installs one immutable pg8000 candidate artifact and runs this smoke against the repository PostgreSQL image without adding the candidate to the production dependency graph. The -checks cover the portable connection/cursor ACL plus transaction, parameter, -JSONB, UUID/timestamp, affected-row, and transaction-local tenant semantics that -must be proven before candidate promotion. +checks cover the portable connection/cursor ACL, thread-affine connection use, +transaction, parameter, JSONB, UUID/timestamp, affected-row, and transaction-local +tenant semantics that must be proven before candidate promotion. """ from __future__ import annotations @@ -18,9 +18,9 @@ from pg8000 import dbapi -from pg_llm_batch.pg8000_driver_candidate_adapter import ( - Pg8000CandidateConnectionAdapter, - validate_pg8000_dbapi_module, +from pg_llm_batch.pg8000_driver_candidate_adapter import validate_pg8000_dbapi_module +from pg_llm_batch.pg8000_thread_affine_candidate_adapter import ( + Pg8000ThreadAffineCandidateConnectionAdapter, ) _EXPECTED_VERSION = "1.31.5" @@ -71,7 +71,7 @@ def _prepare_rls_fixture() -> tuple[uuid.UUID, datetime]: raw = _raw_connection() try: raw.autocommit = True - connection = Pg8000CandidateConnectionAdapter(raw) + connection = Pg8000ThreadAffineCandidateConnectionAdapter(raw) with connection.cursor() as cursor: cursor.execute("CREATE ROLE pg8000_candidate_reader NOLOGIN") cursor.execute( @@ -130,7 +130,7 @@ def _prepare_rls_fixture() -> tuple[uuid.UUID, datetime]: def _assert_transaction_rollback() -> None: """Prove the package connection context rolls an exceptional write back.""" raw = _raw_connection() - adapter = Pg8000CandidateConnectionAdapter(raw) + adapter = Pg8000ThreadAffineCandidateConnectionAdapter(raw) try: with adapter as connection: with connection.cursor() as cursor: @@ -156,7 +156,7 @@ def _assert_typed_rls_read( ) -> None: """Prove transaction-local tenant scope and typed result semantics together.""" raw = _raw_connection() - adapter = Pg8000CandidateConnectionAdapter(raw) + adapter = Pg8000ThreadAffineCandidateConnectionAdapter(raw) with adapter as connection: with connection.cursor() as cursor: cursor.execute("SET ROLE pg8000_candidate_reader") @@ -194,7 +194,7 @@ def main() -> None: validate_pg8000_dbapi_module(dbapi) raw = _raw_connection() - adapter = Pg8000CandidateConnectionAdapter(raw) + adapter = Pg8000ThreadAffineCandidateConnectionAdapter(raw) with adapter as connection: with connection.cursor() as cursor: cursor.execute("SELECT current_database(), current_user, %s::text", ("bound",)) From f68d75ce060bf95c30c2e0c5252eba3f7d43e43b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 16:10:42 +0900 Subject: [PATCH 175/338] test(postgres): require candidate thread-affinity evidence --- ...gres_driver_candidate_thread_capability.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tests/test_postgres_driver_candidate_thread_capability.py diff --git a/tests/test_postgres_driver_candidate_thread_capability.py b/tests/test_postgres_driver_candidate_thread_capability.py new file mode 100644 index 00000000..34e00d90 --- /dev/null +++ b/tests/test_postgres_driver_candidate_thread_capability.py @@ -0,0 +1,64 @@ +"""Commercial-candidate concurrency evidence must match DB-API connection ownership. + +A replacement driver can be permissively licensed and otherwise satisfy the SQL, +transaction, type, and conninfo contract while still declaring connections unsafe +to share across threads. Candidate admission therefore requires explicit evidence +for the connection thread-affinity policy instead of inferring concurrency safety +from module importability or successful single-thread PostgreSQL smokes. +""" + +from __future__ import annotations + +from pg_llm_batch.postgres_driver_candidate import ( + PostgresDriverCandidateEvidence, + evaluate_postgres_driver_candidate, +) + + +_LEGACY_CAPABILITIES_WITHOUT_THREAD_AFFINITY = frozenset( + { + "autocommit_state", + "connection_closed_state", + "connection_context", + "connection_context_commit_rollback", + "conninfo_keyword_parse_render", + "conninfo_service_selector", + "conninfo_uri_parse_render", + "cursor_context", + "finite_connect_timeout", + "invalid_conninfo_classification", + "jsonb", + "parameterized_sql", + "result_row_semantics", + "row_count", + "sql_parameter_style_adaptation", + "transaction_commit_rollback", + "undefined_function_classification", + "uuid_timestamp_adaptation", + } +) + + +def test_candidate_requires_explicit_connection_thread_affinity_evidence() -> None: + """Reject the former complete receipt when thread-ownership evidence is absent.""" + evidence = PostgresDriverCandidateEvidence( + package_name="candidate-driver", + package_version="1.2.3", + license_spdx="BSD-3-Clause", + license_report_sha256="d" * 64, + python_versions=("3.10", "3.11", "3.12", "3.13", "3.14"), + source_commit_sha="a" * 40, + artifact_sha256="b" * 64, + vulnerability_report_sha256="c" * 64, + capability_report_sha256="e" * 64, + known_vulnerability_ids=(), + capabilities=_LEGACY_CAPABILITIES_WITHOUT_THREAD_AFFINITY, + ) + + decision = evaluate_postgres_driver_candidate(evidence) + + assert decision.eligible_for_parity_validation is False + assert decision.production_approved is False + assert decision.reasons == ( + "missing_capability:connection_thread_affinity", + ) From 62126dc8a8caf24c51622bbc8f83569263878ba0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 16:11:42 +0900 Subject: [PATCH 176/338] fix(postgres): require candidate connection thread-affinity evidence --- pg_llm_batch/postgres_driver_candidate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pg_llm_batch/postgres_driver_candidate.py b/pg_llm_batch/postgres_driver_candidate.py index 2fe06cd2..dabea588 100644 --- a/pg_llm_batch/postgres_driver_candidate.py +++ b/pg_llm_batch/postgres_driver_candidate.py @@ -22,6 +22,7 @@ "connection_closed_state", "connection_context", "connection_context_commit_rollback", + "connection_thread_affinity", "conninfo_keyword_parse_render", "conninfo_service_selector", "conninfo_uri_parse_render", From 1a9008e0923b78b7872e820ae668ea8aa3e26bd5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 17:02:30 +0900 Subject: [PATCH 177/338] test(postgres): add RED pg8000 undefined-function classifier contract --- ...0_driver_candidate_error_classification.py | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 tests/test_pg8000_driver_candidate_error_classification.py diff --git a/tests/test_pg8000_driver_candidate_error_classification.py b/tests/test_pg8000_driver_candidate_error_classification.py new file mode 100644 index 00000000..dffdbb4b --- /dev/null +++ b/tests/test_pg8000_driver_candidate_error_classification.py @@ -0,0 +1,85 @@ +"""Candidate-only PostgreSQL error-classification contract tests. + +These tests keep pg8000 out of the production dependency graph. The exact +candidate artifact is injected as a DB-API module so the classifier can prove +one PostgreSQL SQLSTATE without trusting shaped exception objects or message +text. Real PostgreSQL acceptance remains a separate CI smoke before candidate +promotion. +""" + +from __future__ import annotations + +from types import ModuleType + +import pytest + +from pg_llm_batch.pg8000_driver_candidate_errors import ( + Pg8000CandidateErrorEvidenceError, + is_pg8000_candidate_undefined_function, +) + + +class _ProgrammingError(Exception): + """Stand in for the exact candidate DB-API ProgrammingError class.""" + + +def _dbapi_module() -> ModuleType: + """Build one exact module-shaped DB-API authority for classifier tests.""" + module = ModuleType("pg8000.dbapi") + module.ProgrammingError = _ProgrammingError + return module + + +def test_candidate_classifies_only_exact_undefined_function_sqlstate() -> None: + module = _dbapi_module() + + assert is_pg8000_candidate_undefined_function( + _ProgrammingError({"S": "ERROR", "C": "42883", "M": "hidden"}), + dbapi_module=module, + ) is True + assert is_pg8000_candidate_undefined_function( + _ProgrammingError({"S": "ERROR", "C": "42P01", "M": "hidden"}), + dbapi_module=module, + ) is False + + +def test_candidate_classifier_rejects_untrusted_module_authority() -> None: + with pytest.raises( + Pg8000CandidateErrorEvidenceError, + match="DB-API module authority is invalid", + ): + is_pg8000_candidate_undefined_function( + _ProgrammingError({"C": "42883"}), + dbapi_module=object(), + ) + + +def test_candidate_classifier_does_not_execute_or_trust_shaped_payloads() -> None: + module = _dbapi_module() + + class _MappingLike: + def get(self, key: object) -> object: + raise AssertionError("mapping-like payload was evaluated") + + assert is_pg8000_candidate_undefined_function( + _ProgrammingError(_MappingLike()), + dbapi_module=module, + ) is False + assert is_pg8000_candidate_undefined_function( + RuntimeError({"C": "42883"}), + dbapi_module=module, + ) is False + + +def test_candidate_classifier_rejects_malformed_programming_error_authority() -> None: + module = _dbapi_module() + module.ProgrammingError = "ProgrammingError" + + with pytest.raises( + Pg8000CandidateErrorEvidenceError, + match="ProgrammingError authority is invalid", + ): + is_pg8000_candidate_undefined_function( + _ProgrammingError({"C": "42883"}), + dbapi_module=module, + ) From 5c5466c2af4e6ba37a87cb044f00dfab7758b576 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 17:02:45 +0900 Subject: [PATCH 178/338] feat(postgres): classify pg8000 undefined-function SQLSTATE --- .../pg8000_driver_candidate_errors.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 pg_llm_batch/pg8000_driver_candidate_errors.py diff --git a/pg_llm_batch/pg8000_driver_candidate_errors.py b/pg_llm_batch/pg8000_driver_candidate_errors.py new file mode 100644 index 00000000..bf88c088 --- /dev/null +++ b/pg_llm_batch/pg8000_driver_candidate_errors.py @@ -0,0 +1,79 @@ +"""Candidate-only pg8000 PostgreSQL error classification. + +The production package still uses Psycopg while the commercial driver migration +is incomplete. This module proves only narrow pg8000 error semantics needed by +``PostgresDriverPort`` without importing pg8000 into the committed runtime +dependency graph. Callers must inject the exact admitted DB-API module from the +candidate environment; message text is never used as authority. +""" + +from __future__ import annotations + +from types import ModuleType + + +_UNDEFINED_FUNCTION_SQLSTATE = "42883" + + +class Pg8000CandidateErrorEvidenceError(RuntimeError): + """Reject malformed candidate exception authority before classification. + + Candidate metadata participates in a commercial dependency decision. An + invalid module or exception-class authority therefore fails closed instead + of being interpreted as a PostgreSQL server error or a successful parity + result. + """ + + +def _programming_error_type(dbapi_module: object) -> type[BaseException]: + """Return the exact DB-API ProgrammingError class from an admitted module. + + ``ModuleType`` identity is required so shaped objects cannot execute custom + attribute access while supplying security-relevant error metadata. The + exported class must be an actual ``BaseException`` subtype before any + candidate exception is inspected. + """ + if type(dbapi_module) is not ModuleType: + raise Pg8000CandidateErrorEvidenceError( + "PostgreSQL candidate DB-API module authority is invalid" + ) + programming_error = vars(dbapi_module).get("ProgrammingError") + if ( + type(programming_error) is not type + or not issubclass(programming_error, BaseException) + ): + raise Pg8000CandidateErrorEvidenceError( + "PostgreSQL candidate ProgrammingError authority is invalid" + ) + return programming_error + + +def is_pg8000_candidate_undefined_function( + error: BaseException, + *, + dbapi_module: object, +) -> bool: + """Recognize only PostgreSQL SQLSTATE 42883 from the exact candidate class. + + pg8000 server errors carry a PostgreSQL response mapping as the sole + ``ProgrammingError`` argument. Classification requires the exact injected + DB-API exception type, an exact built-in ``dict`` payload, and an exact + string SQLSTATE. Severity and message text are intentionally ignored, so + translated or attacker-controlled diagnostics cannot manufacture the + undefined-function fallback signal used by token-counting code. + + Raises: + Pg8000CandidateErrorEvidenceError: If the injected DB-API module does not + expose a trustworthy ``ProgrammingError`` class authority. + """ + programming_error = _programming_error_type(dbapi_module) + if type(error) is not programming_error: + return False + arguments = error.args + if type(arguments) is not tuple or len(arguments) != 1: + return False + payload = arguments[0] + if type(payload) is not dict: + return False + sqlstate = payload.get("C") + return type(sqlstate) is str and sqlstate == _UNDEFINED_FUNCTION_SQLSTATE From d8f1060abb61c473695072392ec6b07cac311a19 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 17:03:24 +0900 Subject: [PATCH 179/338] test(postgres): prove pg8000 SQLSTATE classification on real PostgreSQL --- tests/smoke_pg8000_candidate_postgres.py | 32 ++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/tests/smoke_pg8000_candidate_postgres.py b/tests/smoke_pg8000_candidate_postgres.py index 3493361a..c1974c0e 100644 --- a/tests/smoke_pg8000_candidate_postgres.py +++ b/tests/smoke_pg8000_candidate_postgres.py @@ -4,8 +4,9 @@ pg8000 candidate artifact and runs this smoke against the repository PostgreSQL image without adding the candidate to the production dependency graph. The checks cover the portable connection/cursor ACL, thread-affine connection use, -transaction, parameter, JSONB, UUID/timestamp, affected-row, and transaction-local -tenant semantics that must be proven before candidate promotion. +transaction, parameter, JSONB, UUID/timestamp, affected-row, narrow PostgreSQL +error classification, and transaction-local tenant semantics that must be proven +before candidate promotion. """ from __future__ import annotations @@ -19,6 +20,9 @@ from pg8000 import dbapi from pg_llm_batch.pg8000_driver_candidate_adapter import validate_pg8000_dbapi_module +from pg_llm_batch.pg8000_driver_candidate_errors import ( + is_pg8000_candidate_undefined_function, +) from pg_llm_batch.pg8000_thread_affine_candidate_adapter import ( Pg8000ThreadAffineCandidateConnectionAdapter, ) @@ -150,6 +154,29 @@ def _assert_transaction_rollback() -> None: raise +def _assert_undefined_function_classification() -> None: + """Prove SQLSTATE-based undefined-function classification on real PostgreSQL.""" + raw = _raw_connection() + adapter = Pg8000ThreadAffineCandidateConnectionAdapter(raw) + try: + with adapter.cursor() as cursor: + try: + cursor.execute("SELECT pg_llm_batch_candidate_missing_function()") + except BaseException as error: + if not is_pg8000_candidate_undefined_function( + error, + dbapi_module=dbapi, + ): + raise AssertionError( + "candidate undefined-function classification changed" + ) from error + else: + raise AssertionError("candidate undefined-function probe unexpectedly exists") + adapter.rollback() + finally: + adapter.close() + + def _assert_typed_rls_read( expected_uuid: uuid.UUID, expected_time: datetime, @@ -201,6 +228,7 @@ def main() -> None: if cursor.fetchone() != (_EXPECTED_DATABASE, _EXPECTED_USER, "bound"): raise AssertionError("candidate parameter/result semantics changed") + _assert_undefined_function_classification() _cleanup() try: evidence_uuid, evidence_time = _prepare_rls_fixture() From 85ba4df7fd22affbf9685d3ef894190c29c01218 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 17:09:10 +0900 Subject: [PATCH 180/338] test(recovery): add RED terminal pg_tiktoken session cleanup --- ...oken_counter_undefined_function_cleanup.py | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 tests/test_token_counter_undefined_function_cleanup.py diff --git a/tests/test_token_counter_undefined_function_cleanup.py b/tests/test_token_counter_undefined_function_cleanup.py new file mode 100644 index 00000000..a74f17c8 --- /dev/null +++ b/tests/test_token_counter_undefined_function_cleanup.py @@ -0,0 +1,115 @@ +"""Recovery regressions for terminal pg_tiktoken capability loss. + +A replacement PostgreSQL driver may classify both supported pg_tiktoken entry +points as undefined at runtime, for example after an extension rollback or a +misrouted connection. Once the counter marks the capability unavailable it +must also release the cached session; retaining an unusable connection would +leak a database resource that the counter intentionally never retries. +""" + +from __future__ import annotations + +from typing import Any + +import pytest + +import pg_llm_batch.token_counter as token_counter_module +from pg_llm_batch.token_counter import TokenCounter + + +class _UndefinedFunctionError(RuntimeError): + """Represent one driver-classified PostgreSQL undefined-function error.""" + + +class _Cursor: + """Expose a healthy capability probe followed by two missing functions.""" + + def __init__(self, driver: _Driver) -> None: + self._driver = driver + + def __enter__(self) -> _Cursor: + return self + + def __exit__(self, *_exc: Any) -> None: + return None + + def execute(self, query: str, params: object | None = None) -> _Cursor: + if "to_regprocedure" in query: + self._driver.rows.append((True, True, True)) + return self + if "tiktoken_count" in query or "tiktoken_encode" in query: + raise _UndefinedFunctionError("pg_tiktoken function unavailable") + raise AssertionError(f"unexpected SQL in cleanup regression: {query!r}") + + def fetchone(self) -> tuple[object, ...] | None: + if not self._driver.rows: + return None + return self._driver.rows.pop(0) + + +class _Connection: + """Track whether terminal capability loss releases the retained session.""" + + def __init__(self, driver: _Driver) -> None: + self._driver = driver + self.closed = False + + def cursor(self) -> _Cursor: + return _Cursor(self._driver) + + def set_autocommit(self, enabled: bool) -> None: + assert enabled is True + + def is_closed(self) -> bool: + return self.closed + + def close(self) -> None: + self.closed = True + + +class _Driver: + """Minimal driver port for terminal undefined-function recovery evidence.""" + + def __init__(self) -> None: + self.rows: list[tuple[object, ...]] = [] + self.connections: list[_Connection] = [] + + def connect( + self, + dsn: str, + *, + connect_timeout_seconds: int | None = None, + ) -> _Connection: + assert dsn == "postgresql://candidate" + assert connect_timeout_seconds is None + connection = _Connection(self) + self.connections.append(connection) + return connection + + def is_undefined_function(self, error: BaseException) -> bool: + return isinstance(error, _UndefinedFunctionError) + + +def test_terminal_undefined_function_failure_closes_cached_driver_session( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Both missing pg_tiktoken entry points must disable and release the session.""" + driver = _Driver() + monkeypatch.setattr(token_counter_module, "psycopg", None) + monkeypatch.setattr( + token_counter_module, + "get_model_metadata", + lambda _dsn, _model, *, postgres_driver=None: { + "tokenizer_model": "o200k_base" + }, + ) + + counter = TokenCounter("postgresql://candidate", postgres_driver=driver) + + with pytest.raises(RuntimeError, match="Token counting requires pg_tiktoken"): + counter.count_tokens("hello", "model-a") + + assert len(driver.connections) == 1 + assert driver.connections[0].closed is True + assert counter._pg_conn is None + assert counter._pg_available is False From ff0d9b07ac905ce4b98d3c5c70a7f6eb62e5b9ef Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 17:11:15 +0900 Subject: [PATCH 181/338] fix(recovery): release pg_tiktoken session after terminal capability loss --- pg_llm_batch/token_counter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pg_llm_batch/token_counter.py b/pg_llm_batch/token_counter.py index 93bd583c..271df36c 100644 --- a/pg_llm_batch/token_counter.py +++ b/pg_llm_batch/token_counter.py @@ -181,6 +181,7 @@ def count_tokens(self, text: str, model: str) -> int: except Exception as error: # pragma: no cover - runtime DB variance if self._is_undefined_function(error): self._pg_available = False + self.close() logger.warning("pg_tiktoken extension/functions unavailable") else: self.close() @@ -472,4 +473,4 @@ def is_empty(self) -> bool: def __len__(self) -> int: """Return the number of accumulated requests.""" - return self.record_count \ No newline at end of file + return self.record_count From 89344c75f26a128d11f46b9a22efea6fb589d7a6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 18:09:51 +0900 Subject: [PATCH 182/338] test(postgres): require pg8000 JSONB candidate adapter --- tests/test_pg8000_driver_candidate_jsonb.py | 50 +++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/test_pg8000_driver_candidate_jsonb.py diff --git a/tests/test_pg8000_driver_candidate_jsonb.py b/tests/test_pg8000_driver_candidate_jsonb.py new file mode 100644 index 00000000..611aedb5 --- /dev/null +++ b/tests/test_pg8000_driver_candidate_jsonb.py @@ -0,0 +1,50 @@ +"""Candidate JSONB adaptation contract for the permissive PostgreSQL driver lane. + +The pg8000 migration remains candidate-only until the exact artifact proves the +complete PostgreSQL, conninfo, RLS, recovery, packaging, SBOM, and provenance +contract. These tests pin only the JSONB parameter boundary needed by the +existing ``PostgresDriverPort.jsonb`` capability. +""" + +from __future__ import annotations + +import json +import math + +import pytest + +from pg_llm_batch.pg8000_driver_candidate_jsonb import ( + Pg8000CandidateJsonbError, + adapt_pg8000_jsonb, +) + + +def test_candidate_jsonb_serializes_exact_json_semantics_for_dbapi_binding() -> None: + """Return UTF-8 JSON text that pg8000 DB-API can bind to a JSONB cast.""" + payload = { + "request_id": "opaque-1", + "labels": ["한국어", "English", None], + "enabled": True, + "count": 3, + } + + adapted = adapt_pg8000_jsonb(payload) + + assert type(adapted) is str + assert adapted.encode("utf-8") + assert json.loads(adapted) == payload + assert payload["labels"] == ["한국어", "English", None] + + +@pytest.mark.parametrize("value", [math.nan, math.inf, -math.inf]) +def test_candidate_jsonb_rejects_non_finite_numbers(value: float) -> None: + """Reject values PostgreSQL JSONB cannot represent as standards-compliant JSON.""" + with pytest.raises(Pg8000CandidateJsonbError, match="JSONB value is invalid"): + adapt_pg8000_jsonb({"value": value}) + + +def test_candidate_jsonb_rejects_unencodable_or_non_json_values() -> None: + """Normalize invalid candidate payloads to one non-content-bearing error.""" + for value in ({"value": object()}, {"value": "\ud800"}): + with pytest.raises(Pg8000CandidateJsonbError, match="JSONB value is invalid"): + adapt_pg8000_jsonb(value) From 65489f05932295c8a128f798d395392f849e8814 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 18:10:18 +0900 Subject: [PATCH 183/338] feat(postgres): add pg8000 JSONB candidate adapter --- pg_llm_batch/pg8000_driver_candidate_jsonb.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 pg_llm_batch/pg8000_driver_candidate_jsonb.py diff --git a/pg_llm_batch/pg8000_driver_candidate_jsonb.py b/pg_llm_batch/pg8000_driver_candidate_jsonb.py new file mode 100644 index 00000000..f8128d72 --- /dev/null +++ b/pg_llm_batch/pg8000_driver_candidate_jsonb.py @@ -0,0 +1,58 @@ +"""Candidate-only JSONB adaptation for the pg8000 migration lane. + +pg8000 1.31.5's DB-API contract sends JSON as serialized text and returns JSON +values deserialized. The production runtime still uses Psycopg; this module only +proves the JSONB parameter adaptation needed before a permissively licensed +candidate can implement ``PostgresDriverPort.jsonb``. It does not promote +pg8000 into the runtime dependency graph or bypass the remaining conninfo, RLS, +recovery, package, SBOM, and provenance gates. +""" + +from __future__ import annotations + +import json + + +class Pg8000CandidateJsonbError(RuntimeError): + """Report an invalid candidate JSONB value without reflecting payload content. + + The error intentionally contains no serialized value because batch payloads + may contain purpose-bound user or provider content. Callers can classify the + candidate contract failure without turning diagnostics into a content leak. + """ + + +def adapt_pg8000_jsonb(value: object) -> str: + """Serialize one validated value for pg8000 DB-API JSONB parameter binding. + + PostgreSQL JSON/JSONB does not admit IEEE non-finite numeric literals, and an + isolated Unicode surrogate cannot be encoded as the UTF-8 client text sent to + PostgreSQL. The candidate therefore fails closed before database I/O for + either case and for objects that Python's JSON encoder cannot represent. + Non-ASCII text is retained as Unicode rather than escaped so the adapter can + exercise the same client-encoding boundary used by real multilingual batch + payloads. + + Args: + value: A caller-validated JSON-compatible Python value. + + Returns: + Compact UTF-8-encodable JSON text suitable for a pg8000 DB-API parameter. + + Raises: + Pg8000CandidateJsonbError: If the value is not finite JSON or cannot be + represented as UTF-8 JSON text. + """ + try: + serialized = json.dumps( + value, + ensure_ascii=False, + allow_nan=False, + separators=(",", ":"), + ) + serialized.encode("utf-8") + except (TypeError, ValueError, UnicodeEncodeError, RecursionError): + raise Pg8000CandidateJsonbError( + "PostgreSQL driver JSONB value is invalid" + ) from None + return serialized From 7007e219bdcfb821fd5ef5002b14f48154bd8881 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 18:11:12 +0900 Subject: [PATCH 184/338] test(postgres): exercise candidate JSONB adapter on real PostgreSQL --- tests/smoke_pg8000_candidate_postgres.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/smoke_pg8000_candidate_postgres.py b/tests/smoke_pg8000_candidate_postgres.py index c1974c0e..1f04952e 100644 --- a/tests/smoke_pg8000_candidate_postgres.py +++ b/tests/smoke_pg8000_candidate_postgres.py @@ -23,6 +23,7 @@ from pg_llm_batch.pg8000_driver_candidate_errors import ( is_pg8000_candidate_undefined_function, ) +from pg_llm_batch.pg8000_driver_candidate_jsonb import adapt_pg8000_jsonb from pg_llm_batch.pg8000_thread_affine_candidate_adapter import ( Pg8000ThreadAffineCandidateConnectionAdapter, ) @@ -117,11 +118,11 @@ def _prepare_rls_fixture() -> tuple[uuid.UUID, datetime]: "tenant-a", evidence_uuid, evidence_time, - {"candidate": "pg8000", "visible": True}, + adapt_pg8000_jsonb({"candidate": "pg8000", "visible": True}), "tenant-b", uuid.uuid4(), evidence_time, - {"candidate": "pg8000", "visible": False}, + adapt_pg8000_jsonb({"candidate": "pg8000", "visible": False}), ), ) if cursor.row_count() != 2: @@ -144,7 +145,7 @@ def _assert_transaction_rollback() -> None: SET evidence_json = %s WHERE tenant_scope = %s """, - ({"rolled_back": True}, "tenant-a"), + (adapt_pg8000_jsonb({"rolled_back": True}), "tenant-a"), ) if cursor.row_count() != 1: raise AssertionError("candidate rollback probe did not update one row") From 509a0f8643ee0a75b07b5538df46f92599c92db1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 18:37:42 +0900 Subject: [PATCH 185/338] test(postgres): pin candidate URI driver boundary --- tests/test_pg8000_candidate_driver_port.py | 160 +++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 tests/test_pg8000_candidate_driver_port.py diff --git a/tests/test_pg8000_candidate_driver_port.py b/tests/test_pg8000_candidate_driver_port.py new file mode 100644 index 00000000..05bfd020 --- /dev/null +++ b/tests/test_pg8000_candidate_driver_port.py @@ -0,0 +1,160 @@ +"""Candidate driver-port regressions for the permissive PostgreSQL migration. + +These tests pin the smallest URI-based connection-selector slice that pg8000 can +exercise without libpq. Keyword conninfo and service selectors remain explicit +fail-closed gaps; passing this suite must not be interpreted as full issue #322 +admission or production dependency approval. +""" + +from __future__ import annotations + +from types import ModuleType + +import pytest + +from pg_llm_batch.pg8000_candidate_driver_port import ( + Pg8000CandidateDriverAdapter, + Pg8000CandidateInvalidConninfoError, +) +from pg_llm_batch.pg8000_thread_affine_candidate_adapter import ( + Pg8000ThreadAffineCandidateConnectionAdapter, +) + + +class _ProgrammingError(Exception): + """Stand in for the exact candidate DB-API ProgrammingError authority.""" + + +def _candidate_module() -> tuple[ModuleType, dict[str, object]]: + """Build one DB-API-shaped module and capture exact connection arguments.""" + module = ModuleType("candidate_dbapi") + module.apilevel = "2.0" + module.paramstyle = "format" + module.threadsafety = 1 + module.ProgrammingError = _ProgrammingError + captured: dict[str, object] = {} + + def connect(**kwargs: object) -> object: + captured.update(kwargs) + return object() + + module.connect = connect + return module, captured + + +def test_candidate_uri_conninfo_round_trip_preserves_encoded_identity() -> None: + """Preserve URI identity while decoding values only at the driver boundary.""" + module, _ = _candidate_module() + driver = Pg8000CandidateDriverAdapter(module) + + params = driver.parse_conninfo( + "postgresql://batch%20user:p%40ss@db.example:6543/batch%2Fqueue" + ) + + assert params == { + "user": "batch user", + "password": "p@ss", + "host": "db.example", + "port": "6543", + "dbname": "batch/queue", + } + assert driver.parse_conninfo(driver.make_conninfo(params)) == params + + +def test_candidate_conninfo_rejects_unproved_keyword_service_and_query_options() -> None: + """Keep selectors outside the proved URI subset fail closed instead of guessing.""" + module, _ = _candidate_module() + driver = Pg8000CandidateDriverAdapter(module) + + for dsn in ( + "service=production", + "host=db.example dbname=batch user=batch", + "postgresql://batch@db.example/batch?sslmode=require", + "postgresql://batch@db.example/batch#fragment", + ): + with pytest.raises( + Pg8000CandidateInvalidConninfoError, + match="PostgreSQL connection selector is unsupported", + ): + driver.parse_conninfo(dsn) + + +def test_candidate_conninfo_rejects_malformed_percent_port_and_control_data() -> None: + """Reject malformed selectors through one non-content-bearing error contract.""" + module, _ = _candidate_module() + driver = Pg8000CandidateDriverAdapter(module) + + for dsn in ( + "postgresql://batch%ZZ@db.example/batch", + "postgresql://batch@db.example:70000/batch", + "postgresql://batch@db.example/batch\nservice=other", + ): + with pytest.raises(Pg8000CandidateInvalidConninfoError): + driver.parse_conninfo(dsn) + + +def test_candidate_make_conninfo_rejects_unknown_or_non_string_parameters() -> None: + """Prevent driver-specific or truthy values from entering the URI renderer.""" + module, _ = _candidate_module() + driver = Pg8000CandidateDriverAdapter(module) + baseline: dict[str, object] = { + "user": "batch", + "host": "db.example", + "port": "5432", + "dbname": "batch", + } + + with pytest.raises(Pg8000CandidateInvalidConninfoError): + driver.make_conninfo({**baseline, "service": "production"}) # type: ignore[arg-type] + with pytest.raises(Pg8000CandidateInvalidConninfoError): + driver.make_conninfo({**baseline, "port": 5432}) # type: ignore[arg-type] + + +def test_candidate_connect_maps_uri_and_finite_timeout_without_raw_dsn_forwarding() -> None: + """Translate the proved URI subset to pg8000 kwargs and retain thread affinity.""" + module, captured = _candidate_module() + driver = Pg8000CandidateDriverAdapter(module) + + connection = driver.connect( + "postgresql://batch:secret@127.0.0.1:5544/queue", + connect_timeout_seconds=5, + ) + + assert isinstance(connection, Pg8000ThreadAffineCandidateConnectionAdapter) + assert captured == { + "user": "batch", + "password": "secret", + "host": "127.0.0.1", + "port": 5544, + "database": "queue", + "timeout": 5, + } + assert "dsn" not in captured + + +@pytest.mark.parametrize("timeout", [True, False, 0, -1, 1.5, "5"]) +def test_candidate_connect_rejects_non_positive_or_non_integer_timeout(timeout: object) -> None: + """Do not coerce booleans, floats, text, or non-positive values into policy.""" + module, _ = _candidate_module() + driver = Pg8000CandidateDriverAdapter(module) + + with pytest.raises( + Pg8000CandidateInvalidConninfoError, + match="PostgreSQL driver timeout is invalid", + ): + driver.connect( + "postgresql://batch@127.0.0.1/queue", + connect_timeout_seconds=timeout, # type: ignore[arg-type] + ) + + +def test_candidate_invalid_conninfo_classifier_is_narrow() -> None: + """Classify only errors created by the candidate selector boundary.""" + module, _ = _candidate_module() + driver = Pg8000CandidateDriverAdapter(module) + + error = Pg8000CandidateInvalidConninfoError( + "PostgreSQL connection selector is invalid" + ) + assert driver.is_invalid_conninfo(error) is True + assert driver.is_invalid_conninfo(RuntimeError("database unavailable")) is False From ebafecb101d3492bfb7c20d8f1b8f0ccad93cafb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 18:38:18 +0900 Subject: [PATCH 186/338] feat(postgres): add candidate URI driver adapter --- pg_llm_batch/pg8000_candidate_driver_port.py | 282 +++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 pg_llm_batch/pg8000_candidate_driver_port.py diff --git a/pg_llm_batch/pg8000_candidate_driver_port.py b/pg_llm_batch/pg8000_candidate_driver_port.py new file mode 100644 index 00000000..b3f6579b --- /dev/null +++ b/pg_llm_batch/pg8000_candidate_driver_port.py @@ -0,0 +1,282 @@ +"""Candidate-only pg8000 implementation of the PostgreSQL driver port. + +The commercial migration needs a connection factory, not only cursor wrappers. +pg8000 1.31.5 accepts explicit DB-API connection keyword arguments but does not +provide libpq conninfo or service-file parsing. This adapter therefore proves a +small PostgreSQL URI subset and fails closed on keyword conninfo, service +selectors, query options, and fragments until those product contracts have their +own reviewed compatibility evidence. + +The module does not import pg8000. An exact candidate DB-API module must be +injected after artifact, license, integrity, and environment admission, keeping +pg8000 out of the committed production dependency graph while issue #322 remains +open. +""" + +from __future__ import annotations + +from collections.abc import Mapping +from types import ModuleType +from typing import cast +from urllib.parse import quote, unquote, urlsplit + +from .pg8000_driver_candidate_adapter import ( + Pg8000CandidateAdapterError, + validate_pg8000_dbapi_module, +) +from .pg8000_driver_candidate_errors import ( + is_pg8000_candidate_undefined_function, +) +from .pg8000_driver_candidate_jsonb import adapt_pg8000_jsonb +from .pg8000_thread_affine_candidate_adapter import ( + Pg8000ThreadAffineCandidateConnectionAdapter, +) +from .postgres_driver_port import PostgresConnectionPort, PostgresDriverPort + + +_HEX_DIGITS = frozenset("0123456789abcdefABCDEF") +_ALLOWED_PARAMETER_KEYS = frozenset({"user", "password", "host", "port", "dbname"}) +_DEFAULT_PORT = 5432 +_MIN_PORT = 1 +_MAX_PORT = 65_535 + + +class Pg8000CandidateInvalidConninfoError(Pg8000CandidateAdapterError): + """Identify only failures at the candidate connection-selector boundary. + + The error message never reflects the supplied selector or credentials. A + failure means the candidate cannot represent that PostgreSQL selector under + the currently proved URI contract; it does not mean the database rejected a + connection attempt. + """ + + +def _invalid_selector(*, unsupported: bool = False) -> Pg8000CandidateInvalidConninfoError: + """Create one non-content-bearing selector error for malformed or missing data.""" + if unsupported: + return Pg8000CandidateInvalidConninfoError( + "PostgreSQL connection selector is unsupported" + ) + return Pg8000CandidateInvalidConninfoError( + "PostgreSQL connection selector is invalid" + ) + + +def _contains_control(value: str) -> bool: + """Return whether text contains ASCII control or DEL framing characters.""" + return any(ord(character) < 0x20 or ord(character) == 0x7F for character in value) + + +def _validate_percent_encoding(value: str) -> None: + """Reject incomplete or non-hex percent escapes before URI decoding.""" + index = 0 + while index < len(value): + if value[index] != "%": + index += 1 + continue + if ( + index + 2 >= len(value) + or value[index + 1] not in _HEX_DIGITS + or value[index + 2] not in _HEX_DIGITS + ): + raise _invalid_selector() + index += 3 + + +def _decode_component(value: str, *, allow_empty: bool = False) -> str: + """Decode one UTF-8 URI component after strict percent and framing checks.""" + _validate_percent_encoding(value) + try: + decoded = unquote(value, encoding="utf-8", errors="strict") + except (UnicodeDecodeError, UnicodeError): + raise _invalid_selector() from None + if (not decoded and not allow_empty) or _contains_control(decoded) or "\x00" in decoded: + raise _invalid_selector() + return decoded + + +def _parse_port(value: object) -> int: + """Return one exact TCP port while rejecting bools and out-of-range values.""" + if type(value) is int: + port = value + elif type(value) is str and value.isascii() and value.isdigit(): + port = int(value) + else: + raise _invalid_selector() + if port < _MIN_PORT or port > _MAX_PORT: + raise _invalid_selector() + return port + + +def _validate_host(host: str) -> str: + """Keep a single TCP host and reject URI delimiters or framing characters.""" + if not host or _contains_control(host) or any(token in host for token in "/?#@[]"): + raise _invalid_selector() + return host + + +def _parse_postgresql_uri(dsn: str) -> dict[str, str]: + """Parse the candidate's reviewed single-host PostgreSQL URI subset. + + PostgreSQL keyword conninfo, service selectors, query parameters, fragments, + multi-host forms, Unix-socket selectors, and libpq-specific options remain + outside this bounded slice. They fail closed rather than being approximated + by pg8000 connection arguments. + """ + if type(dsn) is not str or not dsn or _contains_control(dsn): + raise _invalid_selector() + if not (dsn.startswith("postgresql://") or dsn.startswith("postgres://")): + raise _invalid_selector(unsupported=True) + + try: + parsed = urlsplit(dsn) + except ValueError: + raise _invalid_selector() from None + + if parsed.scheme not in {"postgresql", "postgres"}: + raise _invalid_selector(unsupported=True) + if parsed.query or parsed.fragment: + raise _invalid_selector(unsupported=True) + if not parsed.netloc or parsed.username is None or parsed.hostname is None: + raise _invalid_selector() + if not parsed.path.startswith("/") or len(parsed.path) <= 1: + raise _invalid_selector() + raw_database = parsed.path[1:] + if "/" in raw_database: + raise _invalid_selector(unsupported=True) + + user = _decode_component(parsed.username) + password = ( + _decode_component(parsed.password, allow_empty=True) + if parsed.password is not None + else None + ) + host = _validate_host(parsed.hostname) + database = _decode_component(raw_database) + try: + port = parsed.port if parsed.port is not None else _DEFAULT_PORT + except ValueError: + raise _invalid_selector() from None + port = _parse_port(port) + + result = { + "user": user, + "host": host, + "port": str(port), + "dbname": database, + } + if password is not None: + result["password"] = password + return result + + +def _validate_parameter_mapping(params: Mapping[str, str]) -> dict[str, str]: + """Copy exact built-in string values from the candidate URI parameter set.""" + if not isinstance(params, Mapping): + raise _invalid_selector() + copied: dict[str, str] = {} + for key, value in params.items(): + if type(key) is not str or key not in _ALLOWED_PARAMETER_KEYS: + raise _invalid_selector(unsupported=True) + if type(value) is not str or _contains_control(value) or "\x00" in value: + raise _invalid_selector() + copied[key] = value + if "user" not in copied or "host" not in copied or "dbname" not in copied: + raise _invalid_selector() + if not copied["user"] or not copied["dbname"]: + raise _invalid_selector() + copied["host"] = _validate_host(copied["host"]) + copied["port"] = str(_parse_port(copied.get("port", str(_DEFAULT_PORT)))) + return copied + + +def _render_host(host: str) -> str: + """Render a validated single host, adding URI brackets only for IPv6 form.""" + if ":" in host: + return f"[{host}]" + return host + + +class Pg8000CandidateDriverAdapter(PostgresDriverPort): + """Prove the pg8000 driver port on a strict single-host PostgreSQL URI subset. + + The injected module must already be the exact candidate artifact. This class + supplies no artifact discovery, dependency installation, or fallback. It + converts only reviewed URI fields to pg8000 DB-API keyword arguments and + wraps the resulting connection in the existing thread-affine candidate ACL. + """ + + def __init__(self, dbapi_module: ModuleType) -> None: + validate_pg8000_dbapi_module(dbapi_module) + connect = vars(dbapi_module).get("connect") + if not callable(connect): + raise Pg8000CandidateAdapterError( + "PostgreSQL driver connection factory is incompatible" + ) + self._dbapi_module = dbapi_module + self._connect = connect + + def connect( + self, + dsn: str, + *, + connect_timeout_seconds: int | None = None, + ) -> PostgresConnectionPort: + """Open one candidate connection from the proved URI and finite timeout. + + The original DSN is never forwarded to pg8000. Parsed values are supplied + as explicit DB-API keywords so unsupported libpq selector semantics cannot + be silently inherited or misrepresented. + """ + if connect_timeout_seconds is not None and ( + type(connect_timeout_seconds) is not int or connect_timeout_seconds <= 0 + ): + raise Pg8000CandidateInvalidConninfoError( + "PostgreSQL driver timeout is invalid" + ) + params = self.parse_conninfo(dsn) + kwargs: dict[str, object] = { + "user": params["user"], + "host": params["host"], + "port": _parse_port(params["port"]), + "database": params["dbname"], + } + if "password" in params: + kwargs["password"] = params["password"] + if connect_timeout_seconds is not None: + kwargs["timeout"] = connect_timeout_seconds + raw_connection = self._connect(**kwargs) + return Pg8000ThreadAffineCandidateConnectionAdapter(raw_connection) + + def parse_conninfo(self, dsn: str) -> Mapping[str, str]: + """Parse only the currently proved PostgreSQL URI selector subset.""" + return _parse_postgresql_uri(dsn) + + def make_conninfo(self, params: Mapping[str, str]) -> str: + """Render the proved parameter subset as a safely percent-encoded URI.""" + copied = _validate_parameter_mapping(params) + user = quote(copied["user"], safe="") + password = copied.get("password") + credentials = user + if password is not None: + credentials += f":{quote(password, safe='')}" + host = _render_host(copied["host"]) + database = quote(copied["dbname"], safe="") + return ( + f"postgresql://{credentials}@{host}:{copied['port']}/{database}" + ) + + def jsonb(self, value: object) -> object: + """Use the separately admitted candidate JSONB serialization boundary.""" + return adapt_pg8000_jsonb(value) + + def is_invalid_conninfo(self, error: BaseException) -> bool: + """Recognize only errors emitted by this candidate selector boundary.""" + return isinstance(error, Pg8000CandidateInvalidConninfoError) + + def is_undefined_function(self, error: BaseException) -> bool: + """Classify SQLSTATE 42883 through the exact injected DB-API authority.""" + return is_pg8000_candidate_undefined_function( + error, + dbapi_module=cast(object, self._dbapi_module), + ) From 47803c4b25b14decf698a0d468c49ca4f0fff260 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 18:39:04 +0900 Subject: [PATCH 187/338] test(postgres): exercise candidate driver factory on real DB --- tests/smoke_pg8000_candidate_postgres.py | 96 +++++++++++------------- 1 file changed, 42 insertions(+), 54 deletions(-) diff --git a/tests/smoke_pg8000_candidate_postgres.py b/tests/smoke_pg8000_candidate_postgres.py index 1f04952e..9aeffc40 100644 --- a/tests/smoke_pg8000_candidate_postgres.py +++ b/tests/smoke_pg8000_candidate_postgres.py @@ -3,10 +3,10 @@ This script is intentionally outside pytest discovery. CI installs one immutable pg8000 candidate artifact and runs this smoke against the repository PostgreSQL image without adding the candidate to the production dependency graph. The -checks cover the portable connection/cursor ACL, thread-affine connection use, -transaction, parameter, JSONB, UUID/timestamp, affected-row, narrow PostgreSQL -error classification, and transaction-local tenant semantics that must be proven -before candidate promotion. +checks cover the candidate URI connection factory, portable connection/cursor +ACL, thread-affine connection use, transaction, parameter, JSONB, UUID/timestamp, +affected-row, narrow PostgreSQL error classification, and transaction-local +tenant semantics that must be proven before candidate promotion. """ from __future__ import annotations @@ -19,22 +19,22 @@ from pg8000 import dbapi -from pg_llm_batch.pg8000_driver_candidate_adapter import validate_pg8000_dbapi_module -from pg_llm_batch.pg8000_driver_candidate_errors import ( - is_pg8000_candidate_undefined_function, -) +from pg_llm_batch.pg8000_candidate_driver_port import Pg8000CandidateDriverAdapter from pg_llm_batch.pg8000_driver_candidate_jsonb import adapt_pg8000_jsonb -from pg_llm_batch.pg8000_thread_affine_candidate_adapter import ( - Pg8000ThreadAffineCandidateConnectionAdapter, -) _EXPECTED_VERSION = "1.31.5" _EXPECTED_DATABASE = "pgllm" _EXPECTED_USER = "pgllm" +_CREDENTIAL_FREE_DSN = "postgresql://pgllm@127.0.0.1:5432/pgllm" -def _raw_connection() -> object: - """Open one finite local candidate connection using the CI-owned password file.""" +def _candidate_driver() -> Pg8000CandidateDriverAdapter: + """Bind the exact admitted pg8000 DB-API module to the candidate driver port.""" + return Pg8000CandidateDriverAdapter(dbapi) + + +def _connection() -> object: + """Open one finite candidate connection from a private in-memory URI selector.""" password_file = os.environ.get("PG8000_CANDIDATE_PASSWORD_FILE") if not password_file: raise RuntimeError("PG8000_CANDIDATE_PASSWORD_FILE is required") @@ -44,39 +44,33 @@ def _raw_connection() -> object: raise RuntimeError("PG8000 candidate password file could not be read") from None if not password: raise RuntimeError("PG8000 candidate password file is empty") - return dbapi.connect( - user=_EXPECTED_USER, - password=password, - host="127.0.0.1", - port=5432, - database=_EXPECTED_DATABASE, - timeout=5, - ) + + driver = _candidate_driver() + parameters = dict(driver.parse_conninfo(_CREDENTIAL_FREE_DSN)) + parameters["password"] = password + private_dsn = driver.make_conninfo(parameters) + return driver.connect(private_dsn, connect_timeout_seconds=5) def _cleanup() -> None: """Remove candidate-only database objects even after a prior interrupted smoke.""" - raw = _raw_connection() + connection = _connection() try: - raw.autocommit = True - cursor = raw.cursor() - try: + connection.set_autocommit(True) + with connection.cursor() as cursor: cursor.execute("DROP TABLE IF EXISTS pg8000_candidate_contract") cursor.execute("DROP ROLE IF EXISTS pg8000_candidate_reader") - finally: - cursor.close() finally: - raw.close() + connection.close() def _prepare_rls_fixture() -> tuple[uuid.UUID, datetime]: """Create an ephemeral RLS fixture and return exact typed evidence values.""" evidence_uuid = uuid.uuid4() evidence_time = datetime.now(timezone.utc).replace(microsecond=0) - raw = _raw_connection() + connection = _connection() try: - raw.autocommit = True - connection = Pg8000ThreadAffineCandidateConnectionAdapter(raw) + connection.set_autocommit(True) with connection.cursor() as cursor: cursor.execute("CREATE ROLE pg8000_candidate_reader NOLOGIN") cursor.execute( @@ -128,17 +122,16 @@ def _prepare_rls_fixture() -> tuple[uuid.UUID, datetime]: if cursor.row_count() != 2: raise AssertionError("pg8000 candidate row-count evidence is not exact") finally: - raw.close() + connection.close() return evidence_uuid, evidence_time def _assert_transaction_rollback() -> None: """Prove the package connection context rolls an exceptional write back.""" - raw = _raw_connection() - adapter = Pg8000ThreadAffineCandidateConnectionAdapter(raw) + connection = _connection() try: - with adapter as connection: - with connection.cursor() as cursor: + with connection as transaction: + with transaction.cursor() as cursor: cursor.execute( """ UPDATE pg8000_candidate_contract @@ -157,25 +150,22 @@ def _assert_transaction_rollback() -> None: def _assert_undefined_function_classification() -> None: """Prove SQLSTATE-based undefined-function classification on real PostgreSQL.""" - raw = _raw_connection() - adapter = Pg8000ThreadAffineCandidateConnectionAdapter(raw) + driver = _candidate_driver() + connection = _connection() try: - with adapter.cursor() as cursor: + with connection.cursor() as cursor: try: cursor.execute("SELECT pg_llm_batch_candidate_missing_function()") except BaseException as error: - if not is_pg8000_candidate_undefined_function( - error, - dbapi_module=dbapi, - ): + if not driver.is_undefined_function(error): raise AssertionError( "candidate undefined-function classification changed" ) from error else: raise AssertionError("candidate undefined-function probe unexpectedly exists") - adapter.rollback() + connection.rollback() finally: - adapter.close() + connection.close() def _assert_typed_rls_read( @@ -183,10 +173,9 @@ def _assert_typed_rls_read( expected_time: datetime, ) -> None: """Prove transaction-local tenant scope and typed result semantics together.""" - raw = _raw_connection() - adapter = Pg8000ThreadAffineCandidateConnectionAdapter(raw) - with adapter as connection: - with connection.cursor() as cursor: + connection = _connection() + with connection as transaction: + with transaction.cursor() as cursor: cursor.execute("SET ROLE pg8000_candidate_reader") cursor.execute( "SELECT set_config('pg_llm_batch.tenant_scope', %s, true)", @@ -219,12 +208,11 @@ def main() -> None: """Run exact-artifact and real-PostgreSQL candidate acceptance probes.""" if metadata.version("pg8000") != _EXPECTED_VERSION: raise AssertionError("unexpected pg8000 candidate version") - validate_pg8000_dbapi_module(dbapi) + _candidate_driver() - raw = _raw_connection() - adapter = Pg8000ThreadAffineCandidateConnectionAdapter(raw) - with adapter as connection: - with connection.cursor() as cursor: + connection = _connection() + with connection as transaction: + with transaction.cursor() as cursor: cursor.execute("SELECT current_database(), current_user, %s::text", ("bound",)) if cursor.fetchone() != (_EXPECTED_DATABASE, _EXPECTED_USER, "bound"): raise AssertionError("candidate parameter/result semantics changed") From 0f09b004398c1b0c2262f8ea77ee0e043444b85c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 18:43:53 +0900 Subject: [PATCH 188/338] test(postgres): reject ambiguous candidate hosts --- tests/test_pg8000_candidate_driver_port.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_pg8000_candidate_driver_port.py b/tests/test_pg8000_candidate_driver_port.py index 05bfd020..8434c9fd 100644 --- a/tests/test_pg8000_candidate_driver_port.py +++ b/tests/test_pg8000_candidate_driver_port.py @@ -79,6 +79,22 @@ def test_candidate_conninfo_rejects_unproved_keyword_service_and_query_options() driver.parse_conninfo(dsn) +def test_candidate_conninfo_rejects_ambiguous_or_unproved_host_forms() -> None: + """Reject multi-host, socket, zone-id, whitespace, and delimiter host forms.""" + module, _ = _candidate_module() + driver = Pg8000CandidateDriverAdapter(module) + + for dsn in ( + "postgresql://batch@db-a.example,db-b.example/batch", + "postgresql://batch@db%20name.example/batch", + "postgresql://batch@%2Fvar%2Frun%2Fpostgresql/batch", + "postgresql://batch@[fe80::1%25eth0]/batch", + "postgresql://batch@db\\name.example/batch", + ): + with pytest.raises(Pg8000CandidateInvalidConninfoError): + driver.parse_conninfo(dsn) + + def test_candidate_conninfo_rejects_malformed_percent_port_and_control_data() -> None: """Reject malformed selectors through one non-content-bearing error contract.""" module, _ = _candidate_module() From ad8515af59fc50c3e560a394ecdb8935e85ebfa2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 18:44:41 +0900 Subject: [PATCH 189/338] fix(postgres): fail closed on ambiguous candidate hosts --- pg_llm_batch/pg8000_candidate_driver_port.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/pg_llm_batch/pg8000_candidate_driver_port.py b/pg_llm_batch/pg8000_candidate_driver_port.py index b3f6579b..099520ad 100644 --- a/pg_llm_batch/pg8000_candidate_driver_port.py +++ b/pg_llm_batch/pg8000_candidate_driver_port.py @@ -39,6 +39,7 @@ _DEFAULT_PORT = 5432 _MIN_PORT = 1 _MAX_PORT = 65_535 +_AMBIGUOUS_HOST_TOKENS = frozenset("/?,#@[]\\%") class Pg8000CandidateInvalidConninfoError(Pg8000CandidateAdapterError): @@ -109,8 +110,20 @@ def _parse_port(value: object) -> int: def _validate_host(host: str) -> str: - """Keep a single TCP host and reject URI delimiters or framing characters.""" - if not host or _contains_control(host) or any(token in host for token in "/?#@[]"): + """Keep one TCP host while rejecting forms that imply unsupported selectors. + + Commas would turn PostgreSQL URI authority into a multi-host selector; + percent escapes can encode Unix-socket paths or IPv6 zone identifiers; and + whitespace/backslashes or URI delimiters make rendering ambiguous. Those + contracts require separate compatibility evidence and therefore fail closed + instead of being passed to pg8000 as a misleading single hostname. + """ + if ( + not host + or _contains_control(host) + or any(character.isspace() for character in host) + or any(token in host for token in _AMBIGUOUS_HOST_TOKENS) + ): raise _invalid_selector() return host From 6448aa38968609ff220e4a9e5069fb07c3853911 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 19:19:58 +0900 Subject: [PATCH 190/338] test(postgres): require centralized retained driver selection --- .../test_postgres_driver_runtime_selection.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tests/test_postgres_driver_runtime_selection.py diff --git a/tests/test_postgres_driver_runtime_selection.py b/tests/test_postgres_driver_runtime_selection.py new file mode 100644 index 00000000..4f68b703 --- /dev/null +++ b/tests/test_postgres_driver_runtime_selection.py @@ -0,0 +1,64 @@ +"""Regressions for the retained PostgreSQL driver selection boundary. + +The commercial driver migration must leave concrete Psycopg authority in one +infrastructure adapter rather than importing the package from each bounded +context. These tests exercise the default connection path through a lazy runtime +selector while preserving explicit driver injection. +""" + +from __future__ import annotations + +from typing import Any + +import pg_llm_batch.checkpoint_store as checkpoint_store +import pg_llm_batch.db as db +from pg_llm_batch.postgres_driver_runtime import retained_postgres_driver + + +class _Connection: + """Represent one exact connection returned by the selected fake driver.""" + + +class _Driver: + """Capture default-driver connection attempts without a concrete client.""" + + def __init__(self) -> None: + self.dsns: list[str] = [] + self.connection = _Connection() + + def connect(self, dsn: str, **_kwargs: Any) -> _Connection: + """Record the exact DSN and return the retained fake connection.""" + self.dsns.append(dsn) + return self.connection + + +def test_db_default_connection_uses_runtime_driver_selector(monkeypatch) -> None: + """Low-level DB helpers must not own a second concrete-driver import path.""" + driver = _Driver() + monkeypatch.setattr(db, "retained_postgres_driver", lambda: driver) + + connection = db._connect_database("postgresql://unit", None) + + assert connection is driver.connection + assert driver.dsns == ["postgresql://unit"] + + +def test_checkpoint_default_connection_uses_runtime_driver_selector(monkeypatch) -> None: + """Checkpoint persistence must share the same retained-driver authority.""" + driver = _Driver() + monkeypatch.setattr(checkpoint_store, "retained_postgres_driver", lambda: driver) + + connection = checkpoint_store._connect_postgres("postgresql://unit", None) + + assert connection is driver.connection + assert driver.dsns == ["postgresql://unit"] + + +def test_runtime_selector_returns_postgres_driver_port() -> None: + """The retained selector must expose only the provider-neutral driver port.""" + driver = retained_postgres_driver() + + assert callable(driver.connect) + assert callable(driver.parse_conninfo) + assert callable(driver.make_conninfo) + assert callable(driver.jsonb) From 2d641a26053cb1d071979b6bb0f1cb18d7201b40 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 19:20:23 +0900 Subject: [PATCH 191/338] refactor(postgres): centralize retained driver selection --- pg_llm_batch/postgres_driver_runtime.py | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 pg_llm_batch/postgres_driver_runtime.py diff --git a/pg_llm_batch/postgres_driver_runtime.py b/pg_llm_batch/postgres_driver_runtime.py new file mode 100644 index 00000000..586a7dcf --- /dev/null +++ b/pg_llm_batch/postgres_driver_runtime.py @@ -0,0 +1,40 @@ +"""Runtime selection for the retained PostgreSQL driver implementation. + +Concrete database-client authority belongs at one infrastructure boundary while +pg-llm-batch migrates away from Psycopg. Bounded contexts consume only +:class:`PostgresDriverPort`; this module lazily constructs the retained adapter +until a commercially admitted replacement is ready. Keeping the import lazy +also preserves explicit driver injection for candidate and degraded-mode tests. +""" + +from __future__ import annotations + +from .postgres_driver_port import PostgresDriverPort + + +class PostgresDriverUnavailableError(RuntimeError): + """Report that the retained PostgreSQL client cannot be constructed. + + The fixed diagnostic deliberately omits import paths, environment details, + DSNs, and credentials. Import failures unrelated to Psycopg are re-raised so + packaging defects are not misclassified as an optional-client absence. + """ + + +def retained_postgres_driver() -> PostgresDriverPort: + """Return the currently retained concrete driver behind the neutral port. + + Psycopg remains a temporary migration baseline only. The import lives here + so callers do not acquire a second concrete-driver dependency and the future + production replacement can be switched at one reviewed runtime boundary. + """ + try: + from .psycopg_driver_adapter import PsycopgDriverAdapter + except ModuleNotFoundError as exc: + missing_name = exc.name or "" + if missing_name != "psycopg" and not missing_name.startswith("psycopg."): + raise + raise PostgresDriverUnavailableError( + "Retained PostgreSQL driver is unavailable" + ) from None + return PsycopgDriverAdapter() From 4ab6c2dcc97122dd8184a4fb1a73fbd6c2f02dd3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 19:37:07 +0900 Subject: [PATCH 192/338] fix(postgres): route default persistence through runtime driver --- pg_llm_batch/checkpoint_store.py | 19 ++++++------- pg_llm_batch/db.py | 47 ++++++++++---------------------- 2 files changed, 22 insertions(+), 44 deletions(-) diff --git a/pg_llm_batch/checkpoint_store.py b/pg_llm_batch/checkpoint_store.py index 18ddddbc..398ec9cb 100644 --- a/pg_llm_batch/checkpoint_store.py +++ b/pg_llm_batch/checkpoint_store.py @@ -10,15 +10,14 @@ from .db import ( DEFAULT_TENANT_SCOPE, - _require_psycopg, _set_transaction_tenant_scope, - psycopg, validate_endpoint_alias, validate_remote_resource_id, validate_tenant_scope, ) from .exceptions import ConfigError, PgLlmBatchError, ValidationError from .postgres_driver_port import PostgresDriverPort +from .postgres_driver_runtime import retained_postgres_driver from .result_streaming import BatchResultCheckpoint MIGRATION_PATH = ( @@ -90,17 +89,15 @@ def _connect_postgres( postgres_dsn: str, postgres_driver: PostgresDriverPort | None, ) -> Any: - """Connect through an injected driver while preserving the legacy default. + """Connect through the selected PostgreSQL driver boundary. - The optional port lets one bounded persistence consumer migrate away from - Psycopg without changing its SQL, transaction, tenant, or checkpoint - semantics. Until the repository selects and validates a commercial - replacement, omitting the port retains the current Psycopg path explicitly. + Explicitly injected migration drivers remain available for parity and + degraded-mode tests. Without an injection, the centralized runtime selector + supplies the retained implementation so checkpoint persistence no longer + imports or constructs Psycopg directly. """ - if postgres_driver is not None: - return postgres_driver.connect(postgres_dsn) - _require_psycopg() - return psycopg.connect(postgres_dsn) + selected_driver = postgres_driver or retained_postgres_driver() + return selected_driver.connect(postgres_dsn) def _validated_checkpoint(value: Any, field: str) -> BatchResultCheckpoint: diff --git a/pg_llm_batch/db.py b/pg_llm_batch/db.py index 3f26037c..a483ba8f 100644 --- a/pg_llm_batch/db.py +++ b/pg_llm_batch/db.py @@ -20,11 +20,7 @@ from .exceptions import ValidationError from .postgres_driver_port import PostgresDriverPort - -try: # pragma: no cover - optional dependency - import psycopg # type: ignore -except ImportError: # pragma: no cover - psycopg = None # type: ignore +from .postgres_driver_runtime import retained_postgres_driver logger = logging.getLogger(__name__) @@ -91,27 +87,19 @@ def __init__(self) -> None: super().__init__("Stored virtual payload failed integrity validation") -def _require_psycopg() -> None: - """Raise a clear error when the optional psycopg dependency is unavailable.""" - if psycopg is None: # pragma: no cover - raise RuntimeError("psycopg is required for database access") - - def _connect_database( dsn: str, postgres_driver: PostgresDriverPort | None, ) -> Any: - """Open one connection through an injected migration driver when supplied. + """Open one connection through the selected PostgreSQL driver boundary. - The default remains Psycopg until a permissively licensed adapter has passed - the repository's parity and release gates. Injected candidates can therefore - exercise package SQL without making the current runtime dependency an - unavoidable prerequisite for every persistence consumer. + Explicitly injected migration drivers remain authoritative for candidate and + degraded-mode tests. When no driver is injected, one centralized runtime + selector supplies the retained implementation so bounded contexts no longer + import or construct Psycopg directly. """ - if postgres_driver is not None: - return postgres_driver.connect(dsn) - _require_psycopg() - return psycopg.connect(dsn) + selected_driver = postgres_driver or retained_postgres_driver() + return selected_driver.connect(dsn) def apply_schema( @@ -455,14 +443,11 @@ def _set_transaction_tenant_scope(cursor: Any, tenant_scope: str) -> None: def _cursor_row_count( cursor: Any, - postgres_driver: PostgresDriverPort | None, + _postgres_driver: PostgresDriverPort | None, ) -> int | None: - """Read an exact affected-row count, normalizing unknown driver evidence.""" - value = ( - cursor.row_count() - if postgres_driver is not None - else getattr(cursor, "rowcount", None) - ) + """Read an exact affected-row count through a driver-neutral cursor surface.""" + row_count = getattr(cursor, "row_count", None) + value = row_count() if callable(row_count) else getattr(cursor, "rowcount", None) if value is None or value == -1: return None if type(value) is not int or value < 0: @@ -888,11 +873,7 @@ def get_model_metadata( A dictionary containing normalized ``mode`` and ``tokenizer_model`` when found, otherwise ``None``. """ - if ( - not dsn - or not model_id - or (postgres_driver is None and psycopg is None) - ): + if not dsn or not model_id: return None try: with _connect_database(dsn, postgres_driver) as conn: @@ -917,4 +898,4 @@ def get_model_metadata( } except Exception as exc: # pragma: no cover - defensive logger.debug("model metadata lookup failed for %s: %s", model_id, exc) - return None \ No newline at end of file + return None From ea85971ab298a8e4696e825288b7bae8fdf82535 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 19:41:36 +0900 Subject: [PATCH 193/338] test(postgres): require centralized readiness driver selection --- .../test_postgres_driver_runtime_selection.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/test_postgres_driver_runtime_selection.py b/tests/test_postgres_driver_runtime_selection.py index 4f68b703..104e665a 100644 --- a/tests/test_postgres_driver_runtime_selection.py +++ b/tests/test_postgres_driver_runtime_selection.py @@ -12,6 +12,7 @@ import pg_llm_batch.checkpoint_store as checkpoint_store import pg_llm_batch.db as db +import pg_llm_batch.health as health from pg_llm_batch.postgres_driver_runtime import retained_postgres_driver @@ -24,11 +25,13 @@ class _Driver: def __init__(self) -> None: self.dsns: list[str] = [] + self.connection_kwargs: list[dict[str, Any]] = [] self.connection = _Connection() - def connect(self, dsn: str, **_kwargs: Any) -> _Connection: - """Record the exact DSN and return the retained fake connection.""" + def connect(self, dsn: str, **kwargs: Any) -> _Connection: + """Record the exact DSN and connection options before returning the fake.""" self.dsns.append(dsn) + self.connection_kwargs.append(dict(kwargs)) return self.connection @@ -41,6 +44,7 @@ def test_db_default_connection_uses_runtime_driver_selector(monkeypatch) -> None assert connection is driver.connection assert driver.dsns == ["postgresql://unit"] + assert driver.connection_kwargs == [{}] def test_checkpoint_default_connection_uses_runtime_driver_selector(monkeypatch) -> None: @@ -52,6 +56,19 @@ def test_checkpoint_default_connection_uses_runtime_driver_selector(monkeypatch) assert connection is driver.connection assert driver.dsns == ["postgresql://unit"] + assert driver.connection_kwargs == [{}] + + +def test_health_default_connection_uses_runtime_driver_selector(monkeypatch) -> None: + """Readiness must share the retained driver and preserve its finite timeout.""" + driver = _Driver() + monkeypatch.setattr(health, "retained_postgres_driver", lambda: driver) + + connection = health._connect_health_database("postgresql://unit", None) + + assert connection is driver.connection + assert driver.dsns == ["postgresql://unit"] + assert driver.connection_kwargs == [{"connect_timeout_seconds": 5}] def test_runtime_selector_returns_postgres_driver_port() -> None: From c6916941d4f0fdb5138f14fd78bf8025de77d863 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 19:43:17 +0900 Subject: [PATCH 194/338] fix(postgres): route readiness through runtime driver --- pg_llm_batch/health.py | 30 ++++++++++++------------ tests/test_health.py | 40 +++++++++++++++++++------------- tests/test_health_driver_port.py | 14 +++-------- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/pg_llm_batch/health.py b/pg_llm_batch/health.py index 3ba79a17..ab22e043 100644 --- a/pg_llm_batch/health.py +++ b/pg_llm_batch/health.py @@ -14,11 +14,10 @@ from typing import Any, Dict, List from .postgres_driver_port import PostgresDriverPort - -try: # pragma: no cover - optional dependency - import psycopg # type: ignore -except ImportError: # pragma: no cover - psycopg = None # type: ignore +from .postgres_driver_runtime import ( + PostgresDriverUnavailableError, + retained_postgres_driver, +) logger = logging.getLogger(__name__) @@ -30,18 +29,19 @@ def _connect_health_database( dsn: str, postgres_driver: PostgresDriverPort | None, ) -> Any: - """Open the bounded readiness connection through the selected database seam. + """Open a bounded readiness connection through the shared driver selector. - An explicitly injected driver is authoritative for this call and receives the - same five-second connection budget as the retained Psycopg path. Omitting the - port preserves the current optional-dependency behavior until a replacement - driver has passed the repository's commercial parity gates. + An explicitly injected driver remains authoritative for candidate and + degraded-mode checks. Otherwise the centralized runtime boundary selects the + retained concrete client. Both paths receive the same five-second connection + budget so migration cannot silently weaken readiness liveness semantics. """ - if postgres_driver is not None: - return postgres_driver.connect(dsn, connect_timeout_seconds=5) - if psycopg is None: - return None - return psycopg.connect(dsn, connect_timeout=5) + if postgres_driver is None: + try: + postgres_driver = retained_postgres_driver() + except PostgresDriverUnavailableError: + return None + return postgres_driver.connect(dsn, connect_timeout_seconds=5) def check_health( diff --git a/tests/test_health.py b/tests/test_health.py index d077f051..b245256c 100644 --- a/tests/test_health.py +++ b/tests/test_health.py @@ -43,24 +43,25 @@ def cursor(self): return _Cursor(self._rows) -class _Psycopg: - """Minimal psycopg facade returning fixed health rows.""" +class _Driver: + """Minimal retained-driver facade returning fixed health rows.""" def __init__(self, rows): self._rows = rows - def connect(self, _dsn, *, connect_timeout): - assert connect_timeout == 5 + def connect(self, _dsn, *, connect_timeout_seconds): + assert connect_timeout_seconds == 5 return _Connection(self._rows) +def _select_driver(monkeypatch, driver) -> None: + """Bind one retained readiness driver without exposing concrete-client imports.""" + monkeypatch.setattr(health, "retained_postgres_driver", lambda: driver) + + def test_missing_required_component_is_reported_not_ready(monkeypatch): """A partial health function result must never pass by vacuous truth.""" - monkeypatch.setattr( - health, - "psycopg", - _Psycopg([("database", True, "connected")]), - ) + _select_driver(monkeypatch, _Driver([("database", True, "connected")])) report = health.check_health("postgresql://example") @@ -78,7 +79,12 @@ def test_missing_required_component_is_reported_not_ready(monkeypatch): def test_health_dependency_and_database_failures_are_bounded(monkeypatch): """Dependency absence is explicit while runtime failures stay content-free.""" - monkeypatch.setattr(health, "psycopg", None) + def unavailable_driver(): + raise health.PostgresDriverUnavailableError( + "Retained PostgreSQL driver is unavailable" + ) + + monkeypatch.setattr(health, "retained_postgres_driver", unavailable_driver) report = health.check_health("postgresql://example") assert report == { "ready": False, @@ -87,12 +93,14 @@ def test_health_dependency_and_database_failures_are_bounded(monkeypatch): ], } - class BrokenPsycopg: + class BrokenDriver: @staticmethod - def connect(_dsn, *, connect_timeout): - raise OSError(f"private-dsn-sentinel after {connect_timeout}s") + def connect(_dsn, *, connect_timeout_seconds): + raise OSError( + f"private-dsn-sentinel after {connect_timeout_seconds}s" + ) - monkeypatch.setattr(health, "psycopg", BrokenPsycopg()) + _select_driver(monkeypatch, BrokenDriver()) report = health.check_health("postgresql://example") assert report == { "ready": False, @@ -115,11 +123,11 @@ def test_health_requires_every_required_component(monkeypatch): ("com_config", True, "ready"), ("optional_metrics", False, "disabled"), ] - monkeypatch.setattr(health, "psycopg", _Psycopg(rows)) + _select_driver(monkeypatch, _Driver(rows)) assert health.check_health("postgresql://example")["ready"] is True rows[1] = ("pg_tiktoken", False, "extension unavailable") - monkeypatch.setattr(health, "psycopg", _Psycopg(rows)) + _select_driver(monkeypatch, _Driver(rows)) assert health.check_health("postgresql://example")["ready"] is False diff --git a/tests/test_health_driver_port.py b/tests/test_health_driver_port.py index 4cda7131..1f465736 100644 --- a/tests/test_health_driver_port.py +++ b/tests/test_health_driver_port.py @@ -5,8 +5,6 @@ from typing import Any -import pytest - from pg_llm_batch import health @@ -75,11 +73,8 @@ def connect( return self.connection -def test_check_health_uses_injected_driver_without_psycopg( - monkeypatch: pytest.MonkeyPatch, -) -> None: - """Readiness must work through the replacement seam when Psycopg is unavailable.""" - monkeypatch.setattr(health, "psycopg", None) +def test_check_health_uses_injected_driver_without_concrete_import() -> None: + """Readiness must work entirely through an explicitly injected driver seam.""" driver = _HealthDriver() report = health.check_health( @@ -94,11 +89,8 @@ def test_check_health_uses_injected_driver_without_psycopg( ] -def test_check_health_bounds_injected_driver_failures_without_psycopg( - monkeypatch: pytest.MonkeyPatch, -) -> None: +def test_check_health_bounds_injected_driver_failures() -> None: """Replacement-driver failures remain bounded without reflecting connection data.""" - monkeypatch.setattr(health, "psycopg", None) secret_sentinel = "postgresql://user:private-password@db.example/batch" class _BrokenDriver: From 9c8a9ca83375a4ae634430c917567a458c738930 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 19:45:39 +0900 Subject: [PATCH 195/338] test(postgres): require centralized config driver selection --- tests/test_postgres_driver_runtime_selection.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_postgres_driver_runtime_selection.py b/tests/test_postgres_driver_runtime_selection.py index 104e665a..e4e0dbbf 100644 --- a/tests/test_postgres_driver_runtime_selection.py +++ b/tests/test_postgres_driver_runtime_selection.py @@ -11,6 +11,7 @@ from typing import Any import pg_llm_batch.checkpoint_store as checkpoint_store +import pg_llm_batch.config as config import pg_llm_batch.db as db import pg_llm_batch.health as health from pg_llm_batch.postgres_driver_runtime import retained_postgres_driver @@ -71,6 +72,22 @@ def test_health_default_connection_uses_runtime_driver_selector(monkeypatch) -> assert driver.connection_kwargs == [{"connect_timeout_seconds": 5}] +def test_config_default_connection_uses_runtime_driver_selector(monkeypatch) -> None: + """Configuration persistence must not retain a second concrete-client authority.""" + driver = _Driver() + monkeypatch.setattr(config, "retained_postgres_driver", lambda: driver) + + connection = config._connect_store_database( + "postgresql://unit", + None, + missing_dependency_message="driver unavailable", + ) + + assert connection is driver.connection + assert driver.dsns == ["postgresql://unit"] + assert driver.connection_kwargs == [{}] + + def test_runtime_selector_returns_postgres_driver_port() -> None: """The retained selector must expose only the provider-neutral driver port.""" driver = retained_postgres_driver() From 2ca4a46c7c3289e0efe953b635ce8842320a3af9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 19:48:24 +0900 Subject: [PATCH 196/338] fix(postgres): route config stores through runtime driver --- pg_llm_batch/config.py | 52 ++++++++++++---------------- tests/test_config.py | 58 +++++++++++++++++++++++++++----- tests/test_config_driver_port.py | 21 +++--------- 3 files changed, 75 insertions(+), 56 deletions(-) diff --git a/pg_llm_batch/config.py b/pg_llm_batch/config.py index d16c4bd2..e81837e6 100644 --- a/pg_llm_batch/config.py +++ b/pg_llm_batch/config.py @@ -22,11 +22,10 @@ from .exceptions import ConfigError from .postgres_driver_port import PostgresDriverPort - -try: # pragma: no cover - optional dependency - import psycopg # type: ignore -except ImportError: # pragma: no cover - psycopg = None # type: ignore +from .postgres_driver_runtime import ( + PostgresDriverUnavailableError, + retained_postgres_driver, +) try: # pragma: no cover - optional dependency from cryptography.fernet import Fernet # type: ignore @@ -162,29 +161,24 @@ def _connect_store_database( *, missing_dependency_message: str, ) -> Any: - """Open one config-store connection through the selected driver boundary. + """Open one config-store connection through the shared driver selector. - Explicit driver injection lets these durable stores migrate independently of - the retained Psycopg runtime while preserving the same connection identity - for table setup, reads, and writes. Autocommit setup stays outside this helper - so a constructor can close the already-opened connection if setup fails. + Explicit driver injection remains authoritative for candidate or host-owned + adapters. Otherwise the centralized runtime boundary selects the retained + concrete implementation. Missing retained-driver support is translated to + the store's existing bounded ``ConfigError`` contract. """ - if postgres_driver is not None: - return postgres_driver.connect(dsn) - if psycopg is None: - raise ConfigError(missing_dependency_message) - return psycopg.connect(dsn) + if postgres_driver is None: + try: + postgres_driver = retained_postgres_driver() + except PostgresDriverUnavailableError as exc: + raise ConfigError(missing_dependency_message) from exc + return postgres_driver.connect(dsn) -def _set_store_autocommit( - connection: Any, - postgres_driver: PostgresDriverPort | None, -) -> None: - """Enable explicit store autocommit through the selected connection contract.""" - if postgres_driver is not None: - connection.set_autocommit(True) - return - connection.autocommit = True +def _set_store_autocommit(connection: Any) -> None: + """Enable explicit store autocommit through the driver-neutral connection.""" + connection.set_autocommit(True) class PostgresConfigStore: @@ -199,8 +193,6 @@ def __init__( postgres_driver: PostgresDriverPort | None = None, ) -> None: """Connect through the selected driver and initialize the config cache.""" - if postgres_driver is None and psycopg is None: - raise ConfigError("psycopg is required for PostgresConfigStore") if not dsn: raise ConfigError( "A Postgres DSN must be provided explicitly (no os.getenv for config)" @@ -212,7 +204,7 @@ def __init__( missing_dependency_message="psycopg is required for PostgresConfigStore", ) try: - _set_store_autocommit(self._conn, postgres_driver) + _set_store_autocommit(self._conn) self.cache: Dict[str, Dict[str, Any]] = {} self._ensure_table() self._ensure_defaults() @@ -345,8 +337,6 @@ def __init__( postgres_driver: PostgresDriverPort | None = None, ) -> None: """Connect through the selected driver with the requested secret policy.""" - if postgres_driver is None and psycopg is None: - raise ConfigError("psycopg is required for SecretStore") if not dsn: raise ConfigError("A Postgres DSN must be provided explicitly") if require_encryption and not fernet_key: @@ -364,7 +354,7 @@ def __init__( missing_dependency_message="psycopg is required for SecretStore", ) try: - _set_store_autocommit(self._conn, postgres_driver) + _set_store_autocommit(self._conn) self._fernet = None if fernet_key and Fernet is not None: self._fernet = Fernet(fernet_key.encode("utf-8")) @@ -459,4 +449,4 @@ def close(self) -> None: def get_config_store(dsn: str) -> PostgresConfigStore: """Construct a config store. DSN must be passed explicitly (no getenv).""" - return PostgresConfigStore(dsn) \ No newline at end of file + return PostgresConfigStore(dsn) diff --git a/tests/test_config.py b/tests/test_config.py index 859c5541..d1fc15a5 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -3,6 +3,8 @@ from __future__ import annotations +from typing import Any + import pytest from pg_llm_batch import config as cfg_mod @@ -11,10 +13,45 @@ from tests.conftest import FakePsycopg +class _FakeDriverConnection: + """Adapt the legacy unit-test connection to the driver-neutral store surface.""" + + def __init__(self, connection: Any) -> None: + self._connection = connection + + @property + def closed(self) -> bool: + """Expose the underlying deterministic closed state for cleanup assertions.""" + return self._connection.closed + + def cursor(self): + """Return the exact cursor backed by the shared fake KV state.""" + return self._connection.cursor() + + def set_autocommit(self, enabled: bool) -> None: + """Apply the store's explicit autocommit policy to the legacy fake.""" + self._connection.autocommit = enabled + + def close(self) -> None: + """Close the underlying fake connection.""" + self._connection.close() + + +class _FakeDriver: + """Provide the shared FakePsycopg state through the runtime driver contract.""" + + def __init__(self, client: FakePsycopg) -> None: + self.client = client + + def connect(self, *args: Any, **kwargs: Any) -> _FakeDriverConnection: + """Return one driver-neutral wrapper around the existing deterministic fake.""" + return _FakeDriverConnection(self.client.connect(*args, **kwargs)) + + @pytest.fixture() def fake_pg(monkeypatch): fake = FakePsycopg() - monkeypatch.setattr(cfg_mod, "psycopg", fake) + monkeypatch.setattr(cfg_mod, "retained_postgres_driver", lambda: _FakeDriver(fake)) return fake @@ -25,16 +62,13 @@ def test_config_requires_dsn(fake_pg): def test_config_defaults_seeded_and_typed(fake_pg): store = PostgresConfigStore("postgresql://x") - # int coercion from stored string assert store.get("token_limits", "per_batch") == 5_000_000_000 - # bool coercion assert store.get("optimization", "smart_batching") is True def test_config_set_get_roundtrip(fake_pg): store = PostgresConfigStore("postgresql://x") store.set("gateway", "base_url", "https://gw.example/v1") - # bypass cache to prove it persisted to the backing table store.cache.clear() assert store.get("gateway", "base_url") == "https://gw.example/v1" @@ -42,7 +76,6 @@ def test_config_set_get_roundtrip(fake_pg): def test_secret_store_base64_without_key(fake_pg, caplog): store = SecretStore("postgresql://x", fernet_key=None) store.set_secret("gateway_api_key.default", "sk-secret-123") - # stored obfuscated, not plaintext stored_value = fake_pg.store.secrets["gateway_api_key.default"][0] assert stored_value != "sk-secret-123" assert store.get_secret("gateway_api_key.default") == "sk-secret-123" @@ -57,11 +90,9 @@ def test_secret_store_fernet_encrypts_at_rest(fake_pg): value, is_encrypted = fake_pg.store.secrets["gateway_api_key.default"] assert is_encrypted is True assert value != "sk-abc" - # a wrong key cannot decrypt other = SecretStore("postgresql://x", fernet_key=Fernet.generate_key().decode()) with pytest.raises(Exception): other.get_secret("gateway_api_key.default") - # the right key can assert store.get_secret("gateway_api_key.default") == "sk-abc" @@ -121,13 +152,22 @@ def test_config_missing_lookup_show_and_factory(fake_pg): def test_store_constructor_requires_dependency_and_dsn(monkeypatch, fake_pg): - monkeypatch.setattr(cfg_mod, "psycopg", None) + def unavailable_driver(): + raise cfg_mod.PostgresDriverUnavailableError( + "Retained PostgreSQL driver is unavailable" + ) + + monkeypatch.setattr(cfg_mod, "retained_postgres_driver", unavailable_driver) with pytest.raises(ConfigError, match="psycopg is required"): PostgresConfigStore("postgresql://x") with pytest.raises(ConfigError, match="psycopg is required"): SecretStore("postgresql://x") - monkeypatch.setattr(cfg_mod, "psycopg", fake_pg) + monkeypatch.setattr( + cfg_mod, + "retained_postgres_driver", + lambda: _FakeDriver(fake_pg), + ) with pytest.raises(ConfigError, match="DSN"): PostgresConfigStore("") with pytest.raises(ConfigError, match="DSN"): diff --git a/tests/test_config_driver_port.py b/tests/test_config_driver_port.py index d1bbefaf..9aedb7ea 100644 --- a/tests/test_config_driver_port.py +++ b/tests/test_config_driver_port.py @@ -7,7 +7,6 @@ import pytest -from pg_llm_batch import config from pg_llm_batch.config import PostgresConfigStore, SecretStore from tests.conftest import FakeCursor, FakeKVStore @@ -49,11 +48,8 @@ def connect(self, dsn: str, **_kwargs: Any) -> _ConfigConnection: return connection -def test_config_store_uses_injected_driver_without_psycopg( - monkeypatch: pytest.MonkeyPatch, -) -> None: - """Configuration CRUD must not require Psycopg when a replacement port is supplied.""" - monkeypatch.setattr(config, "psycopg", None) +def test_config_store_uses_injected_driver_without_concrete_import() -> None: + """Configuration CRUD must not require the retained concrete client when injected.""" driver = _ConfigDriver() store = PostgresConfigStore( @@ -71,11 +67,8 @@ def test_config_store_uses_injected_driver_without_psycopg( assert driver.connections[0].closed is True -def test_secret_store_uses_injected_driver_without_psycopg( - monkeypatch: pytest.MonkeyPatch, -) -> None: - """Secret persistence must retain the same DB seam without a concrete driver import.""" - monkeypatch.setattr(config, "psycopg", None) +def test_secret_store_uses_injected_driver_without_concrete_import() -> None: + """Secret persistence must retain the same DB seam without concrete imports.""" driver = _ConfigDriver() store = SecretStore( @@ -93,12 +86,8 @@ def test_secret_store_uses_injected_driver_without_psycopg( assert driver.connections[0].closed is True -def test_config_store_closes_connection_when_autocommit_setup_fails( - monkeypatch: pytest.MonkeyPatch, -) -> None: +def test_config_store_closes_connection_when_autocommit_setup_fails() -> None: """A replacement-driver setup failure must not leak the opened DB connection.""" - monkeypatch.setattr(config, "psycopg", None) - class _BrokenConnection(_ConfigConnection): def set_autocommit(self, enabled: bool) -> None: """Fail after connection creation to exercise constructor cleanup.""" From d743e845c0142e509d70b3c506c5a4247b2bc5d7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 19:51:40 +0900 Subject: [PATCH 197/338] test(postgres): require centralized token driver selection --- tests/test_postgres_driver_runtime_selection.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_postgres_driver_runtime_selection.py b/tests/test_postgres_driver_runtime_selection.py index e4e0dbbf..6f84f4c0 100644 --- a/tests/test_postgres_driver_runtime_selection.py +++ b/tests/test_postgres_driver_runtime_selection.py @@ -14,6 +14,7 @@ import pg_llm_batch.config as config import pg_llm_batch.db as db import pg_llm_batch.health as health +import pg_llm_batch.token_counter as token_counter from pg_llm_batch.postgres_driver_runtime import retained_postgres_driver @@ -88,6 +89,21 @@ def test_config_default_connection_uses_runtime_driver_selector(monkeypatch) -> assert driver.connection_kwargs == [{}] +def test_token_counter_default_driver_uses_runtime_selector(monkeypatch) -> None: + """Token counting must acquire its retained database capability centrally.""" + driver = _Driver() + monkeypatch.setattr(token_counter, "retained_postgres_driver", lambda: driver) + monkeypatch.setattr( + token_counter.TokenCounter, + "_ensure_pg_tiktoken", + lambda self: False, + ) + + counter = token_counter.TokenCounter("postgresql://unit") + + assert counter._postgres_driver is driver + + def test_runtime_selector_returns_postgres_driver_port() -> None: """The retained selector must expose only the provider-neutral driver port.""" driver = retained_postgres_driver() From c1a31c0d63a4a60af003e36b77b937a13b4d0ee7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:05:56 +0900 Subject: [PATCH 198/338] fix(postgres): centralize token driver selection --- pg_llm_batch/token_counter.py | 59 +++++++++++++---------------------- 1 file changed, 21 insertions(+), 38 deletions(-) diff --git a/pg_llm_batch/token_counter.py b/pg_llm_batch/token_counter.py index 271df36c..e083a41b 100644 --- a/pg_llm_batch/token_counter.py +++ b/pg_llm_batch/token_counter.py @@ -23,16 +23,10 @@ from .exceptions import TokenLimitExceededError, ValidationError from .models import BatchRequest from .postgres_driver_port import PostgresDriverPort +from .postgres_driver_runtime import retained_postgres_driver logger = logging.getLogger(__name__) -try: # pragma: no cover - optional dependency - import psycopg # type: ignore - from psycopg.errors import UndefinedFunction # type: ignore -except ImportError: # pragma: no cover - psycopg = None # type: ignore - UndefinedFunction = Exception # type: ignore - @dataclass(frozen=True) class _EncoderInfo: @@ -61,7 +55,13 @@ def __init__( buffer_percentage: Optional[int] = None, postgres_driver: PostgresDriverPort | None = None, ) -> None: - """Initialize token counting with an optional PostgreSQL migration driver.""" + """Initialize token counting through the centralized PostgreSQL driver boundary. + + Explicitly injected drivers remain authoritative for candidate and test + paths. Ordinary runtime construction acquires the retained implementation + from :mod:`postgres_driver_runtime`, so this bounded context no longer + owns a second concrete Psycopg import or connection fallback. + """ if not postgres_dsn: raise ValidationError( field="postgres_dsn", @@ -70,7 +70,9 @@ def __init__( ) self.postgres_dsn = postgres_dsn self.config = config - self._postgres_driver = postgres_driver + self._postgres_driver = ( + postgres_driver if postgres_driver is not None else retained_postgres_driver() + ) self._pg_conn: Optional[Any] = None self._pg_connection_lock = RLock() self._pg_available: bool = False @@ -124,8 +126,7 @@ def __init__( ), ) - if self._postgres_driver is not None or psycopg is not None: - self._pg_available = self._ensure_pg_tiktoken() + self._pg_available = self._ensure_pg_tiktoken() @staticmethod def _require_positive_limit(field: str, value: Any) -> int: @@ -301,8 +302,6 @@ def _resolve_config_value(self, category: str, key: str, default: Any) -> Any: def _ensure_pg_tiktoken(self) -> bool: """Verify the pre-provisioned pg_tiktoken extension and functions read-only.""" - if self._postgres_driver is None and psycopg is None: - return False with self._pg_connection_lock: try: conn = self._get_pg_conn() @@ -328,31 +327,18 @@ def _ensure_pg_tiktoken(self) -> bool: def _get_pg_conn(self) -> Any: """Return a cached autocommit connection under the session reuse lock.""" with self._pg_connection_lock: - if self._pg_conn is not None: - if self._postgres_driver is not None: - if not self._pg_conn.is_closed(): - return self._pg_conn - elif not self._pg_conn.closed: - return self._pg_conn - if self._postgres_driver is not None: - self._pg_conn = self._postgres_driver.connect(self.postgres_dsn) - self._pg_conn.set_autocommit(True) + if self._pg_conn is not None and not self._pg_conn.is_closed(): return self._pg_conn - assert psycopg is not None - self._pg_conn = psycopg.connect(self.postgres_dsn) - self._pg_conn.autocommit = True + self._pg_conn = self._postgres_driver.connect(self.postgres_dsn) + self._pg_conn.set_autocommit(True) return self._pg_conn def _is_undefined_function(self, error: BaseException) -> bool: """Classify undefined-function failures through the selected driver boundary.""" - if self._postgres_driver is not None: - return self._postgres_driver.is_undefined_function(error) - return isinstance(error, UndefinedFunction) + return self._postgres_driver.is_undefined_function(error) def _count_tokens_postgres(self, text: str, model: str) -> int: """Count tokens while retaining one non-concurrent PostgreSQL session.""" - if self._postgres_driver is None and psycopg is None: - raise RuntimeError("PostgreSQL integration is unavailable") with self._pg_connection_lock: conn = self._get_pg_conn() tiktoken_name = self.get_encoder(model).tokenizer_name @@ -377,14 +363,11 @@ def _count_tokens_postgres(self, text: str, model: str) -> int: def _get_tokenizer_from_db(self, model: str) -> Optional[str]: """Return the tokenizer model recorded in model metadata, or None if unset.""" - if self._postgres_driver is None: - metadata = get_model_metadata(self.postgres_dsn, model) - else: - metadata = get_model_metadata( - self.postgres_dsn, - model, - postgres_driver=self._postgres_driver, - ) + metadata = get_model_metadata( + self.postgres_dsn, + model, + postgres_driver=self._postgres_driver, + ) if metadata and metadata.get("tokenizer_model"): return str(metadata["tokenizer_model"]) return None From 654e8d8149a7a963d7d8f9461b9d3ef9f2a9e670 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:07:14 +0900 Subject: [PATCH 199/338] test(postgres): add driver-port fake adapter --- tests/fake_postgres_driver_port.py | 133 +++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 tests/fake_postgres_driver_port.py diff --git a/tests/fake_postgres_driver_port.py b/tests/fake_postgres_driver_port.py new file mode 100644 index 00000000..980efed8 --- /dev/null +++ b/tests/fake_postgres_driver_port.py @@ -0,0 +1,133 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Driver-port adapter for legacy in-memory PostgreSQL unit-test fakes. + +Production bounded contexts no longer import Psycopg directly while the +commercial driver migration is in progress. These wrappers let the existing +in-memory SQL fake exercise the same ``PostgresDriverPort`` contract without +reintroducing concrete-client authority into product code. +""" + +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any + +from pg_llm_batch.postgres_driver_port import PostgresConnectionPort + + +class _FakeCursorPort: + """Adapt one legacy fake cursor to the driver-neutral cursor contract.""" + + def __init__(self, cursor: Any) -> None: + self._cursor = cursor + + def execute(self, query: str, params: object | None = None) -> _FakeCursorPort: + self._cursor.execute(query, params) + return self + + def executemany(self, query: str, params_seq: object) -> _FakeCursorPort: + self._cursor.executemany(query, params_seq) + return self + + def fetchone(self) -> tuple[object, ...] | None: + row = self._cursor.fetchone() + return None if row is None else tuple(row) + + def fetchmany(self, size: int) -> list[tuple[object, ...]]: + fetchmany = getattr(self._cursor, "fetchmany", None) + if not callable(fetchmany): + return self.fetchall()[:size] + return [tuple(row) for row in fetchmany(size)] + + def fetchall(self) -> list[tuple[object, ...]]: + return [tuple(row) for row in self._cursor.fetchall()] + + def row_count(self) -> int | None: + value = getattr(self._cursor, "rowcount", None) + if value is None or value == -1: + return None + return int(value) + + def __enter__(self) -> _FakeCursorPort: + self._cursor.__enter__() + return self + + def __exit__(self, *exc: object) -> object: + return self._cursor.__exit__(*exc) + + +class _FakeConnectionPort: + """Adapt one legacy fake connection while preserving exact session identity.""" + + def __init__(self, connection: Any) -> None: + self._connection = connection + + def cursor(self) -> _FakeCursorPort: + return _FakeCursorPort(self._connection.cursor()) + + def execute(self, query: str, params: object | None = None) -> _FakeCursorPort: + cursor = self.cursor() + return cursor.execute(query, params) + + def commit(self) -> None: + self._connection.commit() + + def rollback(self) -> None: + rollback = getattr(self._connection, "rollback", None) + if callable(rollback): + rollback() + + def set_autocommit(self, enabled: bool) -> None: + self._connection.autocommit = enabled + + def is_closed(self) -> bool: + return bool(self._connection.closed) + + def close(self) -> None: + self._connection.close() + + def __enter__(self) -> _FakeConnectionPort: + self._connection.__enter__() + return self + + def __exit__(self, *exc: object) -> object: + return self._connection.__exit__(*exc) + + +class FakePsycopgDriverPort: + """Expose ``tests.conftest.FakePsycopg`` through ``PostgresDriverPort``. + + The fake intentionally implements only deterministic unit-test semantics. + Real PostgreSQL compatibility, RLS, recovery, and candidate admission remain + covered by their dedicated integration lanes rather than being inferred from + this in-memory adapter. + """ + + def __init__(self, psycopg_fake: Any) -> None: + self._psycopg_fake = psycopg_fake + + def connect( + self, + dsn: str, + *, + connect_timeout_seconds: int | None = None, + ) -> PostgresConnectionPort: + kwargs: dict[str, object] = {} + if connect_timeout_seconds is not None: + kwargs["connect_timeout"] = connect_timeout_seconds + return _FakeConnectionPort(self._psycopg_fake.connect(dsn, **kwargs)) + + def parse_conninfo(self, dsn: str) -> Mapping[str, str]: + return {"dsn": dsn} + + def make_conninfo(self, params: Mapping[str, str]) -> str: + return str(params.get("dsn", "")) + + def jsonb(self, value: object) -> object: + return value + + def is_invalid_conninfo(self, error: BaseException) -> bool: + return False + + def is_undefined_function(self, error: BaseException) -> bool: + return isinstance(error, self._psycopg_fake.errors.UndefinedFunction) From 04cb350b8dcf31ac783ec4046c5146f9845c6111 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:08:20 +0900 Subject: [PATCH 200/338] test(postgres): migrate token counter fakes to driver port --- tests/test_token_counter.py | 51 ++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/tests/test_token_counter.py b/tests/test_token_counter.py index a8774a76..406a4d98 100644 --- a/tests/test_token_counter.py +++ b/tests/test_token_counter.py @@ -3,22 +3,27 @@ from __future__ import annotations +from threading import RLock + import pytest -from pg_llm_batch import token_counter as tc_mod from pg_llm_batch import db as db_mod +from pg_llm_batch import token_counter as tc_mod from pg_llm_batch.exceptions import TokenLimitExceededError, ValidationError from pg_llm_batch.models import BatchRequest +from pg_llm_batch.postgres_driver_runtime import PostgresDriverUnavailableError from pg_llm_batch.token_counter import BatchAccumulator, TokenCounter from tests.conftest import FakePsycopg +from tests.fake_postgres_driver_port import FakePsycopgDriverPort @pytest.fixture() def fake_pg(monkeypatch): + """Bind the legacy in-memory SQL fake through the production driver port.""" fake = FakePsycopg() - monkeypatch.setattr(tc_mod, "psycopg", fake) - monkeypatch.setattr(tc_mod, "UndefinedFunction", fake.errors.UndefinedFunction) - monkeypatch.setattr(db_mod, "psycopg", fake) + driver = FakePsycopgDriverPort(fake) + monkeypatch.setattr(tc_mod, "retained_postgres_driver", lambda: driver) + monkeypatch.setattr(db_mod, "retained_postgres_driver", lambda: driver) return fake @@ -45,7 +50,10 @@ def test_db_tokenizer_lookup_prefers_mapping(fake_pg, monkeypatch): monkeypatch.setattr( tc_mod, "get_model_metadata", - lambda dsn, model: {"mode": "chat", "tokenizer_model": "o200k_base"}, + lambda dsn, model, *, postgres_driver=None: { + "mode": "chat", + "tokenizer_model": "o200k_base", + }, ) counter = TokenCounter("postgresql://x") assert counter.get_tiktoken_name("some-deployment") == "o200k_base" @@ -79,7 +87,7 @@ def test_split_oversized_batch(fake_pg): requests = [ BatchRequest(user_prompt="a b", model="m"), # 2 BatchRequest(user_prompt="c d", model="m"), # 2 -> new batch - BatchRequest(user_prompt="e", model="m"), # 1 -> fits with prev + BatchRequest(user_prompt="e", model="m"), # 1 -> fits with prev ] batches = counter.split_oversized_batch(requests) assert len(batches) == 2 @@ -185,12 +193,12 @@ def test_count_tokens_fails_closed_when_extension_disappears(fake_pg, monkeypatc counter.count_tokens("hello", "gpt-4o") assert counter._pg_available is False - monkeypatch.setattr(tc_mod, "psycopg", None) - unavailable = TokenCounter("postgresql://x") - with pytest.raises(RuntimeError, match="requires pg_tiktoken"): - unavailable.count_tokens("hello", "gpt-4o") - with pytest.raises(RuntimeError, match="integration is unavailable"): - unavailable._count_tokens_postgres("hello", "gpt-4o") + def unavailable_driver(): + raise PostgresDriverUnavailableError("Retained PostgreSQL driver is unavailable") + + monkeypatch.setattr(tc_mod, "retained_postgres_driver", unavailable_driver) + with pytest.raises(PostgresDriverUnavailableError, match="driver is unavailable"): + TokenCounter("postgresql://x") def test_pg_tiktoken_probe_failure_closes_connection(monkeypatch): @@ -202,7 +210,8 @@ def close(self): counter = object.__new__(TokenCounter) counter._pg_conn = Connection() - monkeypatch.setattr(tc_mod, "psycopg", object()) + counter._pg_connection_lock = RLock() + counter._postgres_driver = object() monkeypatch.setattr( counter, "_get_pg_conn", @@ -210,12 +219,6 @@ def close(self): ) assert counter._ensure_pg_tiktoken() is False assert counter._pg_conn is None - monkeypatch.setattr(tc_mod, "psycopg", None) - assert counter._ensure_pg_tiktoken() is False - - monkeypatch.setattr(tc_mod, "psycopg", object()) - counter._pg_conn = None - assert counter._ensure_pg_tiktoken() is False def test_postgres_count_falls_back_to_encode(fake_pg): @@ -240,11 +243,12 @@ def fetchone(self): cursor = Cursor() class Connection: - closed = False - def cursor(self): return cursor + def is_closed(self): + return False + counter = TokenCounter("postgresql://x") counter._pg_conn = Connection() assert counter._count_tokens_postgres("one two three four", "gpt-4o") == 4 @@ -270,14 +274,15 @@ def fetchone(self): return None class Connection: - closed = False - def __init__(self, cursor): self._cursor = cursor def cursor(self): return self._cursor + def is_closed(self): + return False + counter = TokenCounter("postgresql://x") counter._pg_conn = Connection(Cursor(fallback=False)) assert counter._count_tokens_postgres("text", "model") == 0 From a872e67271ad8ebda8c39051983a50958e70d728 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:09:50 +0900 Subject: [PATCH 201/338] test(postgres): route batch token fakes through driver port --- tests/test_batch_assembly.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_batch_assembly.py b/tests/test_batch_assembly.py index fc814d8a..557e09f4 100644 --- a/tests/test_batch_assembly.py +++ b/tests/test_batch_assembly.py @@ -14,14 +14,15 @@ from pg_llm_batch.orchestrator import BatchPayload, PostgresBatchOrchestrator from pg_llm_batch.token_counter import TokenCounter from tests.conftest import FakePsycopg +from tests.fake_postgres_driver_port import FakePsycopgDriverPort @pytest.fixture() def fake_pg(monkeypatch): fake = FakePsycopg() - monkeypatch.setattr(tc_mod, "psycopg", fake) - monkeypatch.setattr(tc_mod, "UndefinedFunction", fake.errors.UndefinedFunction) - monkeypatch.setattr(db_mod, "psycopg", fake) + driver = FakePsycopgDriverPort(fake) + monkeypatch.setattr(tc_mod, "retained_postgres_driver", lambda: driver) + monkeypatch.setattr(db_mod, "retained_postgres_driver", lambda: driver) monkeypatch.setattr(orch_mod, "psycopg", fake) monkeypatch.setattr(db_mod, "get_model_metadata", lambda dsn, model: None) return fake From 291191bf4ec2d26bcc43f057c409012b9e642d25 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:10:13 +0900 Subject: [PATCH 202/338] test(postgres): migrate pg_tiktoken probe to driver port --- tests/test_pg_tiktoken_runtime_authority.py | 29 +++++++++++++++------ 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/tests/test_pg_tiktoken_runtime_authority.py b/tests/test_pg_tiktoken_runtime_authority.py index ab7b131b..bf182ccf 100644 --- a/tests/test_pg_tiktoken_runtime_authority.py +++ b/tests/test_pg_tiktoken_runtime_authority.py @@ -3,7 +3,6 @@ from __future__ import annotations -from pg_llm_batch import token_counter as tc_mod from pg_llm_batch.token_counter import TokenCounter @@ -27,7 +26,7 @@ def fetchone(self) -> tuple[bool, bool, bool]: class _ProbeConnection: - """Minimal Psycopg connection double for a runtime capability probe.""" + """Minimal driver-port connection double for a runtime capability probe.""" def __init__(self) -> None: self.closed = False @@ -39,6 +38,12 @@ def __init__(self) -> None: def cursor(self) -> _ProbeCursor: return _ProbeCursor(self) + def set_autocommit(self, enabled: bool) -> None: + self.autocommit = enabled + + def is_closed(self) -> bool: + return self.closed + def commit(self) -> None: self.commit_calls += 1 @@ -47,24 +52,32 @@ def close(self) -> None: self.closed = True -class _ProbePsycopg: +class _ProbeDriver: """Return one inspectable connection without granting installation authority.""" def __init__(self) -> None: self.connection = _ProbeConnection() self.connect_calls = 0 - def connect(self, _dsn: str) -> _ProbeConnection: + def connect( + self, + _dsn: str, + *, + connect_timeout_seconds: int | None = None, + ) -> _ProbeConnection: + assert connect_timeout_seconds is None self.connect_calls += 1 return self.connection + def is_undefined_function(self, _error: BaseException) -> bool: + return False + -def test_runtime_pg_tiktoken_readiness_never_installs_extensions(monkeypatch) -> None: +def test_runtime_pg_tiktoken_readiness_never_installs_extensions() -> None: """Ordinary token counting must inspect capability without executing DDL.""" - driver = _ProbePsycopg() - monkeypatch.setattr(tc_mod, "psycopg", driver) + driver = _ProbeDriver() - counter = TokenCounter("postgresql://database") + counter = TokenCounter("postgresql://database", postgres_driver=driver) assert counter._pg_available is True assert driver.connect_calls == 1 From ec6983e46ea84847c9746d088f48927a1d40fadb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:13:51 +0900 Subject: [PATCH 203/338] test(postgres): require centralized orchestrator driver selection --- tests/test_postgres_driver_runtime_selection.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_postgres_driver_runtime_selection.py b/tests/test_postgres_driver_runtime_selection.py index 6f84f4c0..b7240c0a 100644 --- a/tests/test_postgres_driver_runtime_selection.py +++ b/tests/test_postgres_driver_runtime_selection.py @@ -14,6 +14,7 @@ import pg_llm_batch.config as config import pg_llm_batch.db as db import pg_llm_batch.health as health +import pg_llm_batch.orchestrator as orchestrator import pg_llm_batch.token_counter as token_counter from pg_llm_batch.postgres_driver_runtime import retained_postgres_driver @@ -104,6 +105,16 @@ def test_token_counter_default_driver_uses_runtime_selector(monkeypatch) -> None assert counter._postgres_driver is driver +def test_orchestrator_default_driver_uses_runtime_selector(monkeypatch) -> None: + """Batch assembly must not retain a direct concrete Psycopg authority path.""" + driver = _Driver() + monkeypatch.setattr(orchestrator, "retained_postgres_driver", lambda: driver) + + service = orchestrator.PostgresBatchOrchestrator("postgresql://unit") + + assert service._postgres_driver is driver + + def test_runtime_selector_returns_postgres_driver_port() -> None: """The retained selector must expose only the provider-neutral driver port.""" driver = retained_postgres_driver() From e5bf5d0b390050e13aba1f0ece87b91b4cb546e0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:15:15 +0900 Subject: [PATCH 204/338] fix(postgres): centralize orchestrator driver authority --- pg_llm_batch/orchestrator.py | 78 ++++++++++++++---------------------- 1 file changed, 29 insertions(+), 49 deletions(-) diff --git a/pg_llm_batch/orchestrator.py b/pg_llm_batch/orchestrator.py index bd89af4d..318e32bf 100644 --- a/pg_llm_batch/orchestrator.py +++ b/pg_llm_batch/orchestrator.py @@ -21,15 +21,9 @@ from .config import PostgresConfigStore from .exceptions import ValidationError from .postgres_driver_port import PostgresDriverPort +from .postgres_driver_runtime import retained_postgres_driver from .token_counter import BatchAccumulator, TokenCounter -try: # pragma: no cover - optional dependency - import psycopg # type: ignore - from psycopg.types.json import Jsonb # type: ignore -except ImportError: # pragma: no cover - psycopg = None # type: ignore - Jsonb = None # type: ignore - def _validate_effective_token_limit(value: Optional[int]) -> Optional[int]: """Validate an optional stricter runtime token limit.""" @@ -67,39 +61,34 @@ def __init__( *, postgres_driver: PostgresDriverPort | None = None, ) -> None: - """Initialize with an explicit DSN and optional migration driver.""" - if not dsn or (postgres_driver is None and psycopg is None): - raise RuntimeError("A Postgres DSN and psycopg are required") + """Initialize with an explicit DSN through the centralized driver boundary. + + Candidate and host adapters may be injected explicitly. Ordinary runtime + construction uses the retained selector so this bounded context does not + keep a second concrete Psycopg import, JSONB adapter, or connection path. + """ + if not dsn: + raise RuntimeError("A Postgres DSN is required") self.dsn = dsn - self._postgres_driver = postgres_driver + self._postgres_driver = ( + postgres_driver if postgres_driver is not None else retained_postgres_driver() + ) def _connect_database(self) -> Any: """Open one orchestrator connection through the selected driver boundary.""" - if self._postgres_driver is not None: - return self._postgres_driver.connect(self.dsn) - assert psycopg is not None - return psycopg.connect(self.dsn) + return self._postgres_driver.connect(self.dsn) def _set_autocommit(self, connection: Any, enabled: bool) -> None: - """Set transaction mode without exposing a candidate driver's raw API.""" - if self._postgres_driver is not None: - connection.set_autocommit(enabled) - return - connection.autocommit = enabled + """Set transaction mode through the driver-neutral connection contract.""" + connection.set_autocommit(enabled) def _adapt_jsonb(self, value: object) -> object: """Adapt JSONB through the selected driver while preserving exact payload data.""" - if self._postgres_driver is not None: - return self._postgres_driver.jsonb(value) - if Jsonb is not None: - return Jsonb(value) - return json.dumps(value) + return self._postgres_driver.jsonb(value) def _cursor_row_count(self, cursor: Any) -> int | None: """Read affected-row evidence through the selected cursor contract.""" - if self._postgres_driver is not None: - return cursor.row_count() - return getattr(cursor, "rowcount", None) + return cursor.row_count() def _resolve_batch_uuid(self, batch_key: str) -> Optional[str]: """Resolve an exact string batch UUID or input-file-path selector.""" @@ -165,22 +154,16 @@ def prepare_batches( ) rows: List[Tuple] = cur.fetchall() - if self._postgres_driver is None: - config = PostgresConfigStore(self.dsn) - else: - config = PostgresConfigStore( + config = PostgresConfigStore( + self.dsn, + postgres_driver=self._postgres_driver, + ) + try: + counter = TokenCounter( self.dsn, + config=config, postgres_driver=self._postgres_driver, ) - try: - if self._postgres_driver is None: - counter = TokenCounter(self.dsn, config=config) - else: - counter = TokenCounter( - self.dsn, - config=config, - postgres_driver=self._postgres_driver, - ) try: if validated_token_limit is not None: counter.effective_limit = min( @@ -204,14 +187,11 @@ def _assemble_payloads( payloads: List[Dict[str, Any]] = [] for (request_uuid, system_prompt, user_prompt, model_name) in rows: - if self._postgres_driver is None: - metadata = db.get_model_metadata(self.dsn, model_name) - else: - metadata = db.get_model_metadata( - self.dsn, - model_name, - postgres_driver=self._postgres_driver, - ) + metadata = db.get_model_metadata( + self.dsn, + model_name, + postgres_driver=self._postgres_driver, + ) mode = str((metadata or {}).get("mode") or "").lower() system_for_tokens = system_prompt if mode != "embedding" else "" From c70d7f7824ac7c4b92e4808e1096d2aa86137221 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:16:57 +0900 Subject: [PATCH 205/338] test(postgres): migrate orchestrator fakes to driver port --- tests/test_batch_assembly.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/tests/test_batch_assembly.py b/tests/test_batch_assembly.py index 557e09f4..b631ed8d 100644 --- a/tests/test_batch_assembly.py +++ b/tests/test_batch_assembly.py @@ -19,12 +19,18 @@ @pytest.fixture() def fake_pg(monkeypatch): + """Route assembly tests through one shared driver-port fake.""" fake = FakePsycopg() driver = FakePsycopgDriverPort(fake) + fake.driver = driver monkeypatch.setattr(tc_mod, "retained_postgres_driver", lambda: driver) monkeypatch.setattr(db_mod, "retained_postgres_driver", lambda: driver) - monkeypatch.setattr(orch_mod, "psycopg", fake) - monkeypatch.setattr(db_mod, "get_model_metadata", lambda dsn, model: None) + monkeypatch.setattr(orch_mod, "retained_postgres_driver", lambda: driver) + monkeypatch.setattr( + db_mod, + "get_model_metadata", + lambda dsn, model, *, postgres_driver=None: None, + ) return fake @@ -79,12 +85,9 @@ def test_assemble_payloads_splits_on_token_limit(fake_pg): assert all(p["record_count"] == 1 for p in payloads) -def test_orchestrator_requires_dsn_and_driver(monkeypatch, fake_pg): - with pytest.raises(RuntimeError, match="DSN and psycopg"): +def test_orchestrator_requires_dsn(fake_pg): + with pytest.raises(RuntimeError, match="Postgres DSN"): PostgresBatchOrchestrator("") - monkeypatch.setattr(orch_mod, "psycopg", None) - with pytest.raises(RuntimeError, match="DSN and psycopg"): - PostgresBatchOrchestrator("postgresql://x") def test_resolve_batch_uuid_direct_and_lookup(monkeypatch, fake_pg): @@ -176,8 +179,9 @@ def close(self): config = Config() class Counter: - def __init__(self, dsn, config): + def __init__(self, dsn, config, *, postgres_driver=None): assert (dsn, config) == ("postgresql://x", globals_config) + assert postgres_driver is fake_pg.driver self.effective_limit = 100 def close(self): @@ -186,7 +190,11 @@ def close(self): globals_config = config orch = PostgresBatchOrchestrator("postgresql://x") monkeypatch.setattr(fake_pg, "connect", lambda _dsn: Connection()) - monkeypatch.setattr(orch_mod, "PostgresConfigStore", lambda _dsn: globals_config) + monkeypatch.setattr( + orch_mod, + "PostgresConfigStore", + lambda _dsn, *, postgres_driver=None: globals_config, + ) monkeypatch.setattr(orch_mod, "TokenCounter", Counter) monkeypatch.setattr(orch, "_resolve_batch_uuid", lambda _key: "resolved") monkeypatch.setattr( @@ -220,7 +228,9 @@ def test_assemble_payloads_handles_empty_model_switch_and_null_user(fake_pg, mon monkeypatch.setattr( db_mod, "get_model_metadata", - lambda _dsn, model: {"mode": "embedding"} if model == "embed" else None, + lambda _dsn, model, *, postgres_driver=None: ( + {"mode": "embedding"} if model == "embed" else None + ), ) rows = [ ("r1", "ignored system", "vector", "embed"), @@ -316,7 +326,7 @@ def commit(self): def test_persist_payloads_separates_ready_and_overflow(monkeypatch, fake_pg): connection, executions, many = _persistence_connection() monkeypatch.setattr(fake_pg, "connect", lambda _dsn: connection) - monkeypatch.setattr(orch_mod, "Jsonb", lambda value: ("jsonb", value)) + monkeypatch.setattr(fake_pg.driver, "jsonb", lambda value: ("jsonb", value)) counter = TokenCounter("postgresql://x") counter.azure_max_files_per_job = 1 batch_uuid = "11111111-1111-1111-1111-111111111111" From b65f0edee34c42141fc234539e26a3a2547f9cfc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:17:30 +0900 Subject: [PATCH 206/338] test(postgres): migrate persistence failures to driver port --- tests/test_batch_persistence_failures.py | 76 +++++++++++++++++++++--- 1 file changed, 69 insertions(+), 7 deletions(-) diff --git a/tests/test_batch_persistence_failures.py b/tests/test_batch_persistence_failures.py index bf91aa17..791d62bc 100644 --- a/tests/test_batch_persistence_failures.py +++ b/tests/test_batch_persistence_failures.py @@ -3,8 +3,6 @@ from __future__ import annotations -from types import SimpleNamespace - import pytest from pg_llm_batch import orchestrator as orch_mod @@ -81,13 +79,77 @@ def commit(self): self.commits += 1 +class _CursorPort: + """Expose failure-mode row-count evidence through the driver cursor contract.""" + + def __init__(self, cursor: Cursor) -> None: + self._cursor = cursor + + def __enter__(self): + self._cursor.__enter__() + return self + + def __exit__(self, *exc): + return self._cursor.__exit__(*exc) + + def execute(self, sql, params=None): + self._cursor.execute(sql, params) + return self + + def executemany(self, sql, params): + self._cursor.executemany(sql, params) + return self + + def fetchone(self): + return self._cursor.fetchone() + + def fetchall(self): + return self._cursor.fetchall() + + def row_count(self): + return self._cursor.rowcount + + +class _ConnectionPort: + """Preserve one failure-mode transaction while satisfying the driver port.""" + + def __init__(self, connection: Connection) -> None: + self._connection = connection + + def __enter__(self): + self._connection.__enter__() + return self + + def __exit__(self, *exc): + return self._connection.__exit__(*exc) + + def cursor(self): + return _CursorPort(self._connection.cursor()) + + def set_autocommit(self, enabled): + self._connection.autocommit = enabled + + def commit(self): + self._connection.commit() + + +class _Driver: + """Return one deterministic failure connection through the driver boundary.""" + + def __init__(self, connection: Connection) -> None: + self.connection = connection + + def connect(self, _dsn, **_kwargs): + return _ConnectionPort(self.connection) + + def jsonb(self, value): + return value + + def _orchestrator(monkeypatch, mode: str): connection = Connection(mode) - monkeypatch.setattr( - orch_mod, - "psycopg", - SimpleNamespace(connect=lambda _dsn: connection), - ) + driver = _Driver(connection) + monkeypatch.setattr(orch_mod, "retained_postgres_driver", lambda: driver) return PostgresBatchOrchestrator("postgresql://x"), connection From c7369ae845729b98e1f7cd968e5fafe4cd6247dc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:18:07 +0900 Subject: [PATCH 207/338] test(postgres): inject driver in batch key authority tests --- .../test_orchestrator_batch_key_authority.py | 45 +++++++------------ 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/tests/test_orchestrator_batch_key_authority.py b/tests/test_orchestrator_batch_key_authority.py index 8258177d..cc5e95c1 100644 --- a/tests/test_orchestrator_batch_key_authority.py +++ b/tests/test_orchestrator_batch_key_authority.py @@ -8,7 +8,6 @@ import pytest -from pg_llm_batch import orchestrator as orchestrator_module from pg_llm_batch.exceptions import ValidationError from pg_llm_batch.orchestrator import PostgresBatchOrchestrator @@ -33,7 +32,6 @@ def __str__(self) -> str: ], ) def test_non_string_batch_key_fails_before_database_io( - monkeypatch: pytest.MonkeyPatch, invalid_key: object, ) -> None: """Only exact strings may choose UUID/path authority before PostgreSQL access.""" @@ -43,12 +41,11 @@ def forbidden_connect(_dsn: str) -> Any: database_calls.append("connect") raise AssertionError("database I/O must not occur for a non-string selector") - monkeypatch.setattr( - orchestrator_module, - "psycopg", - SimpleNamespace(connect=forbidden_connect), + driver = SimpleNamespace(connect=forbidden_connect) + orchestrator = PostgresBatchOrchestrator( + "postgresql://example", + postgres_driver=driver, ) - orchestrator = PostgresBatchOrchestrator("postgresql://example") with pytest.raises(ValidationError) as caught: orchestrator._resolve_batch_uuid(invalid_key) # type: ignore[arg-type] @@ -58,20 +55,17 @@ def forbidden_connect(_dsn: str) -> Any: assert database_calls == [] -def test_valid_uuid_string_preserves_exact_selector_without_database_io( - monkeypatch: pytest.MonkeyPatch, -) -> None: +def test_valid_uuid_string_preserves_exact_selector_without_database_io() -> None: """A valid exact UUID string must remain the direct authoritative selector.""" database_calls: list[str] = [] - monkeypatch.setattr( - orchestrator_module, - "psycopg", - SimpleNamespace( - connect=lambda _dsn: database_calls.append("connect") - or (_ for _ in ()).throw(AssertionError("unexpected database lookup")) - ), + driver = SimpleNamespace( + connect=lambda _dsn: database_calls.append("connect") + or (_ for _ in ()).throw(AssertionError("unexpected database lookup")) + ) + orchestrator = PostgresBatchOrchestrator( + "postgresql://example", + postgres_driver=driver, ) - orchestrator = PostgresBatchOrchestrator("postgresql://example") batch_key = "00000000-0000-0000-0000-000000000123" assert orchestrator._resolve_batch_uuid(batch_key) == batch_key @@ -120,19 +114,14 @@ def cursor(self) -> _LookupCursor: return _LookupCursor(self.lookup_values) -def test_exact_non_uuid_string_is_used_as_path_key_without_rewriting( - monkeypatch: pytest.MonkeyPatch, -) -> None: +def test_exact_non_uuid_string_is_used_as_path_key_without_rewriting() -> None: """An exact non-UUID string must retain byte-for-byte path-key identity.""" lookup_values: list[str] = [] - monkeypatch.setattr( - orchestrator_module, - "psycopg", - SimpleNamespace( - connect=lambda _dsn: _LookupConnection(lookup_values), - ), + driver = SimpleNamespace(connect=lambda _dsn: _LookupConnection(lookup_values)) + orchestrator = PostgresBatchOrchestrator( + "postgresql://example", + postgres_driver=driver, ) - orchestrator = PostgresBatchOrchestrator("postgresql://example") batch_key = "memory://batch/Case-Sensitive-Key" assert orchestrator._resolve_batch_uuid(batch_key) == ( From 5be8bd50ff2d937e32be6e90411132c50f346951 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:18:58 +0900 Subject: [PATCH 208/338] test(postgres): migrate lifecycle ownership tests to driver port --- tests/test_connection_lifecycle.py | 71 +++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/tests/test_connection_lifecycle.py b/tests/test_connection_lifecycle.py index 7f51474e..8a4fabd8 100644 --- a/tests/test_connection_lifecycle.py +++ b/tests/test_connection_lifecycle.py @@ -3,6 +3,7 @@ from __future__ import annotations +from threading import RLock from types import SimpleNamespace from typing import Any @@ -54,7 +55,7 @@ class _InitializationConnection: """Expose whether a partially initialized store releases its connection.""" def __init__(self) -> None: - """Start open with autocommit disabled like a new psycopg connection.""" + """Start open with autocommit disabled like a new database connection.""" self.autocommit = False self.closed = False @@ -63,14 +64,43 @@ def close(self) -> None: self.closed = True +class _InitializationConnectionPort: + """Expose initialization cleanup through the driver-neutral connection API.""" + + def __init__(self, connection: _InitializationConnection) -> None: + self._connection = connection + + def set_autocommit(self, enabled: bool) -> None: + self._connection.autocommit = enabled + + def close(self) -> None: + self._connection.close() + + +class _InitializationDriver: + """Return one observable initialization connection through the runtime port.""" + + def __init__(self, connection: _InitializationConnection) -> None: + self._connection = connection + + def connect(self, _dsn: str, **_kwargs: Any) -> _InitializationConnectionPort: + return _InitializationConnectionPort(self._connection) + + class _OwnedConfig: """Record whether the orchestrator closes its owned config store.""" instances: list["_OwnedConfig"] = [] - def __init__(self, dsn: str) -> None: - """Record the explicit DSN and register this owned store.""" + def __init__( + self, + dsn: str, + *, + postgres_driver: object | None = None, + ) -> None: + """Record the explicit DSN, shared driver, and owned store lifecycle.""" self.dsn = dsn + self.postgres_driver = postgres_driver self.closed = False self.instances.append(self) @@ -84,10 +114,17 @@ class _OwnedCounter: instances: list["_OwnedCounter"] = [] - def __init__(self, dsn: str, *, config: _OwnedConfig) -> None: + def __init__( + self, + dsn: str, + *, + config: _OwnedConfig, + postgres_driver: object | None = None, + ) -> None: """Record constructor ownership and expose the runtime token limit.""" self.dsn = dsn self.config = config + self.postgres_driver = postgres_driver self.effective_limit = 100 self.closed = False self.instances.append(self) @@ -102,7 +139,7 @@ def _prepare_owned_orchestrator(monkeypatch: Any) -> PostgresBatchOrchestrator: _OwnedConfig.instances = [] _OwnedCounter.instances = [] driver = SimpleNamespace(connect=lambda _dsn: _QueryConnection()) - monkeypatch.setattr(orchestrator_module, "psycopg", driver) + monkeypatch.setattr(orchestrator_module, "retained_postgres_driver", lambda: driver) monkeypatch.setattr(orchestrator_module, "PostgresConfigStore", _OwnedConfig) monkeypatch.setattr(orchestrator_module, "TokenCounter", _OwnedCounter) orchestrator = PostgresBatchOrchestrator("postgresql://example") @@ -154,8 +191,14 @@ def test_prepare_batches_closes_config_when_counter_construction_fails( """Config ownership must be released even when token setup cannot finish.""" orchestrator = _prepare_owned_orchestrator(monkeypatch) - def fail_counter(_dsn: str, *, config: _OwnedConfig) -> _OwnedCounter: + def fail_counter( + _dsn: str, + *, + config: _OwnedConfig, + postgres_driver: object | None = None, + ) -> _OwnedCounter: assert config is _OwnedConfig.instances[0] + assert postgres_driver is orchestrator._postgres_driver raise RuntimeError("counter construction failed") monkeypatch.setattr(orchestrator_module, "TokenCounter", fail_counter) @@ -172,11 +215,8 @@ def test_config_store_constructor_closes_connection_after_setup_failure( ) -> None: """A failed config-store setup must release the connection it already acquired.""" connection = _InitializationConnection() - monkeypatch.setattr( - config_module, - "psycopg", - SimpleNamespace(connect=lambda _dsn: connection), - ) + driver = _InitializationDriver(connection) + monkeypatch.setattr(config_module, "retained_postgres_driver", lambda: driver) def fail_table_setup(_store: PostgresConfigStore) -> None: raise RuntimeError("config setup failed") @@ -194,11 +234,8 @@ def test_secret_store_constructor_closes_connection_after_setup_failure( ) -> None: """A failed secret-store setup must release the connection it already acquired.""" connection = _InitializationConnection() - monkeypatch.setattr( - config_module, - "psycopg", - SimpleNamespace(connect=lambda _dsn: connection), - ) + driver = _InitializationDriver(connection) + monkeypatch.setattr(config_module, "retained_postgres_driver", lambda: driver) def fail_table_setup(_store: SecretStore) -> None: raise RuntimeError("secret setup failed") @@ -215,6 +252,7 @@ def test_token_counter_close_releases_cached_connection() -> None: """Closing a counter must release and clear its cached PostgreSQL connection.""" closed: list[str] = [] counter = object.__new__(TokenCounter) + counter._pg_connection_lock = RLock() counter._pg_conn = SimpleNamespace(close=lambda: closed.append("closed")) counter.close() @@ -227,6 +265,7 @@ def test_token_counter_close_releases_cached_connection() -> None: def test_token_counter_close_clears_connection_after_driver_failure() -> None: """Driver cleanup failure must not retain the unusable cached connection.""" counter = object.__new__(TokenCounter) + counter._pg_connection_lock = RLock() def fail_close() -> None: raise RuntimeError("driver close failed") From c554ee68a31c8b1559d094b0b02ee8f546c77c71 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:32:55 +0900 Subject: [PATCH 209/338] test(postgres): require bounded keyword conninfo candidate support --- tests/test_pg8000_candidate_driver_port.py | 34 +++++++++++++++++----- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/tests/test_pg8000_candidate_driver_port.py b/tests/test_pg8000_candidate_driver_port.py index 8434c9fd..ec73309d 100644 --- a/tests/test_pg8000_candidate_driver_port.py +++ b/tests/test_pg8000_candidate_driver_port.py @@ -1,9 +1,9 @@ """Candidate driver-port regressions for the permissive PostgreSQL migration. -These tests pin the smallest URI-based connection-selector slice that pg8000 can -exercise without libpq. Keyword conninfo and service selectors remain explicit -fail-closed gaps; passing this suite must not be interpreted as full issue #322 -admission or production dependency approval. +These tests pin the bounded URI and keyword-conninfo selector slices that pg8000 +can exercise without libpq. Service selectors and libpq-only options remain +explicit fail-closed gaps; passing this suite must not be interpreted as full +issue #322 admission or production dependency approval. """ from __future__ import annotations @@ -61,14 +61,34 @@ def test_candidate_uri_conninfo_round_trip_preserves_encoded_identity() -> None: assert driver.parse_conninfo(driver.make_conninfo(params)) == params -def test_candidate_conninfo_rejects_unproved_keyword_service_and_query_options() -> None: - """Keep selectors outside the proved URI subset fail closed instead of guessing.""" +def test_candidate_keyword_conninfo_round_trip_preserves_quoted_identity() -> None: + """Accept the bounded libpq keyword form needed by existing CLI deployments.""" + module, _ = _candidate_module() + driver = Pg8000CandidateDriverAdapter(module) + + params = driver.parse_conninfo( + "host=db.example port=6543 dbname='batch queue' " + "user='batch user' password='p@ss word'" + ) + + assert params == { + "host": "db.example", + "port": "6543", + "dbname": "batch queue", + "user": "batch user", + "password": "p@ss word", + } + assert driver.parse_conninfo(driver.make_conninfo(params)) == params + + +def test_candidate_conninfo_rejects_unproved_service_and_libpq_options() -> None: + """Keep selectors outside the proved portable subset fail closed instead of guessing.""" module, _ = _candidate_module() driver = Pg8000CandidateDriverAdapter(module) for dsn in ( "service=production", - "host=db.example dbname=batch user=batch", + "host=db.example dbname=batch user=batch sslmode=require", "postgresql://batch@db.example/batch?sslmode=require", "postgresql://batch@db.example/batch#fragment", ): From d5fdeaadcb58a3c1c3f8eb4c9e9dd4406b20f79b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:34:04 +0900 Subject: [PATCH 210/338] feat(postgres): parse bounded keyword conninfo for pg8000 candidate --- pg_llm_batch/pg8000_candidate_driver_port.py | 135 ++++++++++++++++--- 1 file changed, 115 insertions(+), 20 deletions(-) diff --git a/pg_llm_batch/pg8000_candidate_driver_port.py b/pg_llm_batch/pg8000_candidate_driver_port.py index 099520ad..2a28b6b0 100644 --- a/pg_llm_batch/pg8000_candidate_driver_port.py +++ b/pg_llm_batch/pg8000_candidate_driver_port.py @@ -2,10 +2,11 @@ The commercial migration needs a connection factory, not only cursor wrappers. pg8000 1.31.5 accepts explicit DB-API connection keyword arguments but does not -provide libpq conninfo or service-file parsing. This adapter therefore proves a -small PostgreSQL URI subset and fails closed on keyword conninfo, service -selectors, query options, and fragments until those product contracts have their -own reviewed compatibility evidence. +provide libpq conninfo or service-file parsing. This adapter therefore owns a +bounded anti-corruption parser for the single-host PostgreSQL URI and keyword +conninfo forms already needed by pg-llm-batch. Service selectors, query options, +multi-host/socket forms, and other libpq-only semantics remain fail closed until +they have separate compatibility evidence. The module does not import pg8000. An exact candidate DB-API module must be injected after artifact, license, integrity, and environment admission, keeping @@ -40,6 +41,7 @@ _MIN_PORT = 1 _MAX_PORT = 65_535 _AMBIGUOUS_HOST_TOKENS = frozenset("/?,#@[]\\%") +_KEYWORD_SEPARATOR = " " class Pg8000CandidateInvalidConninfoError(Pg8000CandidateAdapterError): @@ -47,8 +49,8 @@ class Pg8000CandidateInvalidConninfoError(Pg8000CandidateAdapterError): The error message never reflects the supplied selector or credentials. A failure means the candidate cannot represent that PostgreSQL selector under - the currently proved URI contract; it does not mean the database rejected a - connection attempt. + the currently proved URI/keyword contract; it does not mean the database + rejected a connection attempt. """ @@ -131,10 +133,9 @@ def _validate_host(host: str) -> str: def _parse_postgresql_uri(dsn: str) -> dict[str, str]: """Parse the candidate's reviewed single-host PostgreSQL URI subset. - PostgreSQL keyword conninfo, service selectors, query parameters, fragments, - multi-host forms, Unix-socket selectors, and libpq-specific options remain - outside this bounded slice. They fail closed rather than being approximated - by pg8000 connection arguments. + Service selectors, query parameters, fragments, multi-host forms, Unix-socket + selectors, and libpq-specific options remain outside this bounded slice. They + fail closed rather than being approximated by pg8000 connection arguments. """ if type(dsn) is not str or not dsn or _contains_control(dsn): raise _invalid_selector() @@ -183,8 +184,99 @@ def _parse_postgresql_uri(dsn: str) -> dict[str, str]: return result +def _read_keyword_value(dsn: str, start: int) -> tuple[str, int]: + """Read one bounded libpq-style keyword value and return the next offset. + + The candidate deliberately accepts only ASCII-space separators. Single-quoted + values and backslash escaping follow the portable conninfo forms needed by + current deployments, while tabs/newlines and unterminated escapes fail closed + instead of acquiring implicit libpq parser semantics. + """ + if start >= len(dsn): + return "", start + + quoted = dsn[start] == "'" + index = start + 1 if quoted else start + characters: list[str] = [] + while index < len(dsn): + character = dsn[index] + if quoted and character == "'": + index += 1 + if index < len(dsn) and dsn[index] != _KEYWORD_SEPARATOR: + raise _invalid_selector() + return "".join(characters), index + if not quoted and character == _KEYWORD_SEPARATOR: + return "".join(characters), index + if character == "\\": + index += 1 + if index >= len(dsn): + raise _invalid_selector() + escaped = dsn[index] + if _contains_control(escaped): + raise _invalid_selector() + characters.append(escaped) + index += 1 + continue + if character == "'" and not quoted: + raise _invalid_selector() + if _contains_control(character) or character == "\x00": + raise _invalid_selector() + characters.append(character) + index += 1 + + if quoted: + raise _invalid_selector() + return "".join(characters), index + + +def _parse_keyword_conninfo(dsn: str) -> dict[str, str]: + """Parse the reviewed single-host subset of PostgreSQL keyword conninfo. + + Only ``user``, ``password``, ``host``, ``port``, and ``dbname`` are admitted. + Duplicate keys are rejected rather than relying on libpq's last-value-wins + behavior because duplicated authority is ambiguous at the migration boundary. + Service-file selectors and transport options remain explicit unsupported gaps. + """ + if type(dsn) is not str or not dsn or _contains_control(dsn): + raise _invalid_selector() + if any(character.isspace() and character != _KEYWORD_SEPARATOR for character in dsn): + raise _invalid_selector() + + index = 0 + params: dict[str, str] = {} + while index < len(dsn): + while index < len(dsn) and dsn[index] == _KEYWORD_SEPARATOR: + index += 1 + if index >= len(dsn): + break + + key_start = index + while index < len(dsn) and dsn[index] not in {_KEYWORD_SEPARATOR, "="}: + index += 1 + key = dsn[key_start:index] + if not key: + raise _invalid_selector() + while index < len(dsn) and dsn[index] == _KEYWORD_SEPARATOR: + index += 1 + if index >= len(dsn) or dsn[index] != "=": + raise _invalid_selector() + index += 1 + while index < len(dsn) and dsn[index] == _KEYWORD_SEPARATOR: + index += 1 + + if key not in _ALLOWED_PARAMETER_KEYS: + raise _invalid_selector(unsupported=True) + if key in params: + raise _invalid_selector() + + value, index = _read_keyword_value(dsn, index) + params[key] = value + + return _validate_parameter_mapping(params) + + def _validate_parameter_mapping(params: Mapping[str, str]) -> dict[str, str]: - """Copy exact built-in string values from the candidate URI parameter set.""" + """Copy exact built-in string values from the candidate parameter set.""" if not isinstance(params, Mapping): raise _invalid_selector() copied: dict[str, str] = {} @@ -211,12 +303,13 @@ def _render_host(host: str) -> str: class Pg8000CandidateDriverAdapter(PostgresDriverPort): - """Prove the pg8000 driver port on a strict single-host PostgreSQL URI subset. + """Prove the pg8000 driver port on bounded single-host PostgreSQL selectors. The injected module must already be the exact candidate artifact. This class supplies no artifact discovery, dependency installation, or fallback. It - converts only reviewed URI fields to pg8000 DB-API keyword arguments and - wraps the resulting connection in the existing thread-affine candidate ACL. + converts only reviewed URI/keyword fields to pg8000 DB-API keyword arguments + and wraps the resulting connection in the existing thread-affine candidate + ACL. """ def __init__(self, dbapi_module: ModuleType) -> None: @@ -235,7 +328,7 @@ def connect( *, connect_timeout_seconds: int | None = None, ) -> PostgresConnectionPort: - """Open one candidate connection from the proved URI and finite timeout. + """Open one candidate connection from a proved selector and finite timeout. The original DSN is never forwarded to pg8000. Parsed values are supplied as explicit DB-API keywords so unsupported libpq selector semantics cannot @@ -262,8 +355,12 @@ def connect( return Pg8000ThreadAffineCandidateConnectionAdapter(raw_connection) def parse_conninfo(self, dsn: str) -> Mapping[str, str]: - """Parse only the currently proved PostgreSQL URI selector subset.""" - return _parse_postgresql_uri(dsn) + """Parse only the currently proved URI or keyword selector subsets.""" + if type(dsn) is not str or not dsn: + raise _invalid_selector() + if dsn.startswith("postgresql://") or dsn.startswith("postgres://"): + return _parse_postgresql_uri(dsn) + return _parse_keyword_conninfo(dsn) def make_conninfo(self, params: Mapping[str, str]) -> str: """Render the proved parameter subset as a safely percent-encoded URI.""" @@ -275,9 +372,7 @@ def make_conninfo(self, params: Mapping[str, str]) -> str: credentials += f":{quote(password, safe='')}" host = _render_host(copied["host"]) database = quote(copied["dbname"], safe="") - return ( - f"postgresql://{credentials}@{host}:{copied['port']}/{database}" - ) + return f"postgresql://{credentials}@{host}:{copied['port']}/{database}" def jsonb(self, value: object) -> object: """Use the separately admitted candidate JSONB serialization boundary.""" From 3e5fddcc07a0af640be8c05e6d20af7a7fa5d003 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:36:04 +0900 Subject: [PATCH 211/338] test(postgres): cover keyword conninfo fail-closed edges --- tests/test_pg8000_candidate_driver_port.py | 60 ++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/test_pg8000_candidate_driver_port.py b/tests/test_pg8000_candidate_driver_port.py index ec73309d..a5278577 100644 --- a/tests/test_pg8000_candidate_driver_port.py +++ b/tests/test_pg8000_candidate_driver_port.py @@ -81,6 +81,25 @@ def test_candidate_keyword_conninfo_round_trip_preserves_quoted_identity() -> No assert driver.parse_conninfo(driver.make_conninfo(params)) == params +def test_candidate_keyword_conninfo_supports_bare_escaped_and_empty_values() -> None: + """Preserve bounded libpq escaping without delegating parsing back to libpq.""" + module, _ = _candidate_module() + driver = Pg8000CandidateDriverAdapter(module) + + params = driver.parse_conninfo( + r" host = db.example user=batch dbname=batch\ queue password= " + ) + + assert params == { + "host": "db.example", + "user": "batch", + "dbname": "batch queue", + "password": "", + "port": "5432", + } + assert driver.parse_conninfo(driver.make_conninfo(params)) == params + + def test_candidate_conninfo_rejects_unproved_service_and_libpq_options() -> None: """Keep selectors outside the proved portable subset fail closed instead of guessing.""" module, _ = _candidate_module() @@ -99,6 +118,30 @@ def test_candidate_conninfo_rejects_unproved_service_and_libpq_options() -> None driver.parse_conninfo(dsn) +def test_candidate_keyword_conninfo_rejects_ambiguous_or_malformed_grammar() -> None: + """Reject duplicated authority, malformed quoting, and unproved separators.""" + module, _ = _candidate_module() + driver = Pg8000CandidateDriverAdapter(module) + + for dsn in ( + "host=db.example user=batch dbname=queue host=other.example", + "host=db.example user=batch dbname=queue password='unterminated", + "host=db.example user=batch dbname=queue password=trailing\\", + "host=db.example user=batch dbname=que'ue", + "host=db.example user=batch dbname='queue'x", + "host db.example user=batch dbname=queue", + "=db.example user=batch dbname=queue", + "host=db.example\u00a0user=batch dbname=queue", + ): + with pytest.raises(Pg8000CandidateInvalidConninfoError): + driver.parse_conninfo(dsn) + + with pytest.raises(Pg8000CandidateInvalidConninfoError): + driver.parse_conninfo(123) # type: ignore[arg-type] + with pytest.raises(Pg8000CandidateInvalidConninfoError): + driver.parse_conninfo("") + + def test_candidate_conninfo_rejects_ambiguous_or_unproved_host_forms() -> None: """Reject multi-host, socket, zone-id, whitespace, and delimiter host forms.""" module, _ = _candidate_module() @@ -168,6 +211,23 @@ def test_candidate_connect_maps_uri_and_finite_timeout_without_raw_dsn_forwardin assert "dsn" not in captured +def test_candidate_connect_maps_keyword_conninfo_without_raw_dsn_forwarding() -> None: + """Translate keyword conninfo through the same explicit pg8000 argument boundary.""" + module, captured = _candidate_module() + driver = Pg8000CandidateDriverAdapter(module) + + connection = driver.connect("host=127.0.0.1 user=batch dbname=queue") + + assert isinstance(connection, Pg8000ThreadAffineCandidateConnectionAdapter) + assert captured == { + "user": "batch", + "host": "127.0.0.1", + "port": 5432, + "database": "queue", + } + assert "dsn" not in captured + + @pytest.mark.parametrize("timeout", [True, False, 0, -1, 1.5, "5"]) def test_candidate_connect_rejects_non_positive_or_non_integer_timeout(timeout: object) -> None: """Do not coerce booleans, floats, text, or non-positive values into policy.""" From 2ffe05afffb10c760d9796cad3d5b29a4bd94437 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:45:14 +0900 Subject: [PATCH 212/338] test(postgres): require injected service-selector resolution --- tests/test_pg8000_candidate_driver_port.py | 91 +++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/tests/test_pg8000_candidate_driver_port.py b/tests/test_pg8000_candidate_driver_port.py index a5278577..44942d6c 100644 --- a/tests/test_pg8000_candidate_driver_port.py +++ b/tests/test_pg8000_candidate_driver_port.py @@ -1,7 +1,7 @@ """Candidate driver-port regressions for the permissive PostgreSQL migration. These tests pin the bounded URI and keyword-conninfo selector slices that pg8000 -can exercise without libpq. Service selectors and libpq-only options remain +can exercise without libpq. Service-file I/O and libpq-only options remain explicit fail-closed gaps; passing this suite must not be interpreted as full issue #322 admission or production dependency approval. """ @@ -100,6 +100,65 @@ def test_candidate_keyword_conninfo_supports_bare_escaped_and_empty_values() -> assert driver.parse_conninfo(driver.make_conninfo(params)) == params +def test_candidate_service_selector_uses_injected_resolver_and_direct_overrides() -> None: + """Resolve service authority outside the driver and preserve direct overrides.""" + module, _ = _candidate_module() + resolved_names: list[str] = [] + + def resolve_service(service_name: str) -> dict[str, str]: + resolved_names.append(service_name) + return { + "host": "service.example", + "port": "5432", + "dbname": "service_db", + "user": "service_user", + "password": "service-secret", + } + + driver = Pg8000CandidateDriverAdapter(module, service_resolver=resolve_service) + + params = driver.parse_conninfo( + "service=analytics port=6543 dbname='override db'" + ) + + assert resolved_names == ["analytics"] + assert params == { + "host": "service.example", + "port": "6543", + "dbname": "override db", + "user": "service_user", + "password": "service-secret", + } + + +def test_candidate_service_connect_uses_resolved_parameters_not_raw_selector() -> None: + """Keep service-file transport outside pg8000 while connecting with resolved values.""" + module, captured = _candidate_module() + + def resolve_service(service_name: str) -> dict[str, str]: + assert service_name == "analytics" + return { + "host": "127.0.0.1", + "port": "5544", + "dbname": "queue", + "user": "batch", + } + + driver = Pg8000CandidateDriverAdapter(module, service_resolver=resolve_service) + connection = driver.connect("service=analytics", connect_timeout_seconds=5) + + assert isinstance(connection, Pg8000ThreadAffineCandidateConnectionAdapter) + assert captured == { + "user": "batch", + "host": "127.0.0.1", + "port": 5544, + "database": "queue", + "timeout": 5, + } + assert "service" not in captured + assert "dsn" not in captured + + def test_candidate_conninfo_rejects_unproved_service_and_libpq_options() -> None: """Keep selectors outside the proved portable subset fail closed instead of guessing.""" module, _ = _candidate_module() @@ -118,6 +177,36 @@ def test_candidate_conninfo_rejects_unproved_service_and_libpq_options() -> None driver.parse_conninfo(dsn) +def test_candidate_service_resolution_rejects_empty_or_unproved_parameters() -> None: + """Do not let a service resolver expand the candidate beyond admitted fields.""" + module, _ = _candidate_module() + + with pytest.raises(Pg8000CandidateInvalidConninfoError): + Pg8000CandidateDriverAdapter( + module, + service_resolver=lambda _: { + "host": "db.example", + "dbname": "batch", + "user": "batch", + }, + ).parse_conninfo("service=''") + + driver = Pg8000CandidateDriverAdapter( + module, + service_resolver=lambda _: { + "host": "db.example", + "dbname": "batch", + "user": "batch", + "sslmode": "verify-full", + }, + ) + with pytest.raises( + Pg8000CandidateInvalidConninfoError, + match="PostgreSQL connection selector is unsupported", + ): + driver.parse_conninfo("service=production") + + def test_candidate_keyword_conninfo_rejects_ambiguous_or_malformed_grammar() -> None: """Reject duplicated authority, malformed quoting, and unproved separators.""" module, _ = _candidate_module() From e04bdb0f2cca876e1bd229b03010d192e5a6feb2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:46:37 +0900 Subject: [PATCH 213/338] feat(postgres): resolve service selectors through candidate ACL --- pg_llm_batch/pg8000_candidate_driver_port.py | 85 ++++++++++++++------ 1 file changed, 59 insertions(+), 26 deletions(-) diff --git a/pg_llm_batch/pg8000_candidate_driver_port.py b/pg_llm_batch/pg8000_candidate_driver_port.py index 2a28b6b0..55f5d43b 100644 --- a/pg_llm_batch/pg8000_candidate_driver_port.py +++ b/pg_llm_batch/pg8000_candidate_driver_port.py @@ -4,9 +4,11 @@ pg8000 1.31.5 accepts explicit DB-API connection keyword arguments but does not provide libpq conninfo or service-file parsing. This adapter therefore owns a bounded anti-corruption parser for the single-host PostgreSQL URI and keyword -conninfo forms already needed by pg-llm-batch. Service selectors, query options, -multi-host/socket forms, and other libpq-only semantics remain fail closed until -they have separate compatibility evidence. +conninfo forms already needed by pg-llm-batch. A caller may inject a service +resolver so service-file I/O and precedence remain outside the concrete driver; +only the resolved parameter subset already admitted by this candidate reaches +pg8000. Query options, multi-host/socket forms, and other libpq-only semantics +remain fail closed until they have separate compatibility evidence. The module does not import pg8000. An exact candidate DB-API module must be injected after artifact, license, integrity, and environment admission, keeping @@ -16,7 +18,7 @@ from __future__ import annotations -from collections.abc import Mapping +from collections.abc import Callable, Mapping from types import ModuleType from typing import cast from urllib.parse import quote, unquote, urlsplit @@ -37,12 +39,15 @@ _HEX_DIGITS = frozenset("0123456789abcdefABCDEF") _ALLOWED_PARAMETER_KEYS = frozenset({"user", "password", "host", "port", "dbname"}) +_KEYWORD_PARAMETER_KEYS = _ALLOWED_PARAMETER_KEYS | frozenset({"service"}) _DEFAULT_PORT = 5432 _MIN_PORT = 1 _MAX_PORT = 65_535 _AMBIGUOUS_HOST_TOKENS = frozenset("/?,#@[]\\%") _KEYWORD_SEPARATOR = " " +ServiceResolver = Callable[[str], Mapping[str, str]] + class Pg8000CandidateInvalidConninfoError(Pg8000CandidateAdapterError): """Identify only failures at the candidate connection-selector boundary. @@ -211,16 +216,11 @@ def _read_keyword_value(dsn: str, start: int) -> tuple[str, int]: index += 1 if index >= len(dsn): raise _invalid_selector() - escaped = dsn[index] - if _contains_control(escaped): - raise _invalid_selector() - characters.append(escaped) + characters.append(dsn[index]) index += 1 continue if character == "'" and not quoted: raise _invalid_selector() - if _contains_control(character) or character == "\x00": - raise _invalid_selector() characters.append(character) index += 1 @@ -229,13 +229,13 @@ def _read_keyword_value(dsn: str, start: int) -> tuple[str, int]: return "".join(characters), index -def _parse_keyword_conninfo(dsn: str) -> dict[str, str]: - """Parse the reviewed single-host subset of PostgreSQL keyword conninfo. +def _parse_keyword_fields(dsn: str) -> dict[str, str]: + """Parse the reviewed keyword grammar without yet resolving service authority. - Only ``user``, ``password``, ``host``, ``port``, and ``dbname`` are admitted. - Duplicate keys are rejected rather than relying on libpq's last-value-wins + ``service`` is grammar-recognized so a separately injected resolver can own + service-file I/O and precedence. Other unknown parameters remain unsupported; + duplicate keys are rejected instead of relying on libpq's last-value-wins behavior because duplicated authority is ambiguous at the migration boundary. - Service-file selectors and transport options remain explicit unsupported gaps. """ if type(dsn) is not str or not dsn or _contains_control(dsn): raise _invalid_selector() @@ -264,7 +264,7 @@ def _parse_keyword_conninfo(dsn: str) -> dict[str, str]: while index < len(dsn) and dsn[index] == _KEYWORD_SEPARATOR: index += 1 - if key not in _ALLOWED_PARAMETER_KEYS: + if key not in _KEYWORD_PARAMETER_KEYS: raise _invalid_selector(unsupported=True) if key in params: raise _invalid_selector() @@ -272,11 +272,11 @@ def _parse_keyword_conninfo(dsn: str) -> dict[str, str]: value, index = _read_keyword_value(dsn, index) params[key] = value - return _validate_parameter_mapping(params) + return params -def _validate_parameter_mapping(params: Mapping[str, str]) -> dict[str, str]: - """Copy exact built-in string values from the candidate parameter set.""" +def _copy_parameter_mapping(params: Mapping[str, str]) -> dict[str, str]: + """Copy exact built-in values while rejecting non-candidate parameters.""" if not isinstance(params, Mapping): raise _invalid_selector() copied: dict[str, str] = {} @@ -286,6 +286,12 @@ def _validate_parameter_mapping(params: Mapping[str, str]) -> dict[str, str]: if type(value) is not str or _contains_control(value) or "\x00" in value: raise _invalid_selector() copied[key] = value + return copied + + +def _validate_parameter_mapping(params: Mapping[str, str]) -> dict[str, str]: + """Normalize the complete candidate connection parameter set.""" + copied = _copy_parameter_mapping(params) if "user" not in copied or "host" not in copied or "dbname" not in copied: raise _invalid_selector() if not copied["user"] or not copied["dbname"]: @@ -303,16 +309,23 @@ def _render_host(host: str) -> str: class Pg8000CandidateDriverAdapter(PostgresDriverPort): - """Prove the pg8000 driver port on bounded single-host PostgreSQL selectors. + """Prove pg8000 on bounded single-host PostgreSQL connection selectors. The injected module must already be the exact candidate artifact. This class supplies no artifact discovery, dependency installation, or fallback. It - converts only reviewed URI/keyword fields to pg8000 DB-API keyword arguments - and wraps the resulting connection in the existing thread-affine candidate - ACL. + converts only reviewed URI/keyword fields to pg8000 DB-API keyword arguments. + Service-file lookup is deliberately an injected anti-corruption boundary: the + driver never reads process environment variables or filesystem service files + itself, and direct conninfo values override resolver-provided values before + the merged parameter set is validated. """ - def __init__(self, dbapi_module: ModuleType) -> None: + def __init__( + self, + dbapi_module: ModuleType, + *, + service_resolver: ServiceResolver | None = None, + ) -> None: validate_pg8000_dbapi_module(dbapi_module) connect = vars(dbapi_module).get("connect") if not callable(connect): @@ -321,6 +334,7 @@ def __init__(self, dbapi_module: ModuleType) -> None: ) self._dbapi_module = dbapi_module self._connect = connect + self._service_resolver = service_resolver def connect( self, @@ -355,12 +369,31 @@ def connect( return Pg8000ThreadAffineCandidateConnectionAdapter(raw_connection) def parse_conninfo(self, dsn: str) -> Mapping[str, str]: - """Parse only the currently proved URI or keyword selector subsets.""" + """Parse admitted URI/keyword selectors and resolve service authority. + + A service selector is accepted only when a resolver was explicitly + injected. The resolver returns ordinary connection parameters; this + adapter then applies any direct conninfo overrides and validates the final + merged subset. This mirrors PostgreSQL's service-then-direct precedence + without silently acquiring service-file or environment authority. + """ if type(dsn) is not str or not dsn: raise _invalid_selector() if dsn.startswith("postgresql://") or dsn.startswith("postgres://"): return _parse_postgresql_uri(dsn) - return _parse_keyword_conninfo(dsn) + + parsed = _parse_keyword_fields(dsn) + service_name = parsed.pop("service", None) + if service_name is None: + return _validate_parameter_mapping(parsed) + if not service_name: + raise _invalid_selector() + if self._service_resolver is None: + raise _invalid_selector(unsupported=True) + + resolved = _copy_parameter_mapping(self._service_resolver(service_name)) + resolved.update(parsed) + return _validate_parameter_mapping(resolved) def make_conninfo(self, params: Mapping[str, str]) -> str: """Render the proved parameter subset as a safely percent-encoded URI.""" From fcb464452fd04e3c0e1c8bc66dbb71c5d13a3477 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:49:37 +0900 Subject: [PATCH 214/338] test(postgres): require bounded pg_service resolver --- tests/test_pg8000_candidate_service_file.py | 127 ++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 tests/test_pg8000_candidate_service_file.py diff --git a/tests/test_pg8000_candidate_service_file.py b/tests/test_pg8000_candidate_service_file.py new file mode 100644 index 00000000..e041cc79 --- /dev/null +++ b/tests/test_pg8000_candidate_service_file.py @@ -0,0 +1,127 @@ +"""Candidate service-file resolver regressions for the PostgreSQL migration.""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from pg_llm_batch.pg8000_candidate_driver_port import Pg8000CandidateInvalidConninfoError +from pg_llm_batch.pg8000_candidate_service_file import Pg8000CandidateServiceFileResolver + + +def test_candidate_service_file_resolves_exact_section_without_ambient_state( + tmp_path: Path, +) -> None: + """Read only caller-selected service-file authority and preserve exact values.""" + service_file = tmp_path / "pg_service.conf" + service_file.write_text( + "# unrelated service\n" + "[other]\n" + "host=other.example\n" + "\n" + "[analytics]\n" + "host=db.example\n" + "port=6543\n" + "dbname=batch queue\n" + "user=batch user\n" + "password=service-secret\n", + encoding="utf-8", + ) + + resolver = Pg8000CandidateServiceFileResolver(service_file) + + assert resolver("analytics") == { + "host": "db.example", + "port": "6543", + "dbname": "batch queue", + "user": "batch user", + "password": "service-secret", + } + + +def test_candidate_service_file_rejects_missing_duplicate_or_empty_target( + tmp_path: Path, +) -> None: + """Fail closed when a service identity has no single authoritative stanza.""" + service_file = tmp_path / "pg_service.conf" + service_file.write_text( + "[analytics]\nhost=db.example\n[analytics]\nhost=other.example\n", + encoding="utf-8", + ) + resolver = Pg8000CandidateServiceFileResolver(service_file) + + with pytest.raises(Pg8000CandidateInvalidConninfoError): + resolver("analytics") + with pytest.raises(Pg8000CandidateInvalidConninfoError): + resolver("") + with pytest.raises(Pg8000CandidateInvalidConninfoError): + resolver("missing") + + +def test_candidate_service_file_rejects_duplicate_keys_and_malformed_lines( + tmp_path: Path, +) -> None: + """Do not invent last-value-wins or permissive grammar for target authority.""" + duplicate_key = tmp_path / "duplicate.conf" + duplicate_key.write_text( + "[analytics]\nhost=db.example\nhost=other.example\n", + encoding="utf-8", + ) + with pytest.raises(Pg8000CandidateInvalidConninfoError): + Pg8000CandidateServiceFileResolver(duplicate_key)("analytics") + + malformed = tmp_path / "malformed.conf" + malformed.write_text("[analytics]\nhost db.example\n", encoding="utf-8") + with pytest.raises(Pg8000CandidateInvalidConninfoError): + Pg8000CandidateServiceFileResolver(malformed)("analytics") + + +def test_candidate_service_file_rejects_ldap_and_control_data( + tmp_path: Path, +) -> None: + """Keep network lookup and framed data outside the local candidate resolver.""" + ldap_file = tmp_path / "ldap.conf" + ldap_file.write_text( + "[analytics]\nldap://directory.example/dc=example?description?one?(cn=db)\n", + encoding="utf-8", + ) + with pytest.raises( + Pg8000CandidateInvalidConninfoError, + match="PostgreSQL connection selector is unsupported", + ): + Pg8000CandidateServiceFileResolver(ldap_file)("analytics") + + control_file = tmp_path / "control.conf" + control_file.write_bytes(b"[analytics]\nhost=db.example\x00evil\n") + with pytest.raises(Pg8000CandidateInvalidConninfoError): + Pg8000CandidateServiceFileResolver(control_file)("analytics") + + +def test_candidate_service_file_rejects_non_utf8_or_oversized_input( + tmp_path: Path, +) -> None: + """Bound service metadata before decoding or parsing it.""" + invalid_utf8 = tmp_path / "invalid-utf8.conf" + invalid_utf8.write_bytes(b"[analytics]\nhost=\xff\n") + with pytest.raises(Pg8000CandidateInvalidConninfoError): + Pg8000CandidateServiceFileResolver(invalid_utf8)("analytics") + + oversized = tmp_path / "oversized.conf" + oversized.write_bytes(b"#" * 65_537) + with pytest.raises(Pg8000CandidateInvalidConninfoError): + Pg8000CandidateServiceFileResolver(oversized)("analytics") + + +def test_candidate_service_file_rejects_non_file_and_non_string_service( + tmp_path: Path, +) -> None: + """Normalize missing-file and invalid service identities at the candidate boundary.""" + with pytest.raises(Pg8000CandidateInvalidConninfoError): + Pg8000CandidateServiceFileResolver(tmp_path / "missing.conf")("analytics") + + service_file = tmp_path / "pg_service.conf" + service_file.write_text("[analytics]\nhost=db.example\n", encoding="utf-8") + resolver = Pg8000CandidateServiceFileResolver(service_file) + with pytest.raises(Pg8000CandidateInvalidConninfoError): + resolver(7) # type: ignore[arg-type] From ab94a1314f476c2b4860b88ea6d55e860f3c08a5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:50:14 +0900 Subject: [PATCH 215/338] feat(postgres): add bounded candidate pg_service resolver --- pg_llm_batch/pg8000_candidate_service_file.py | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 pg_llm_batch/pg8000_candidate_service_file.py diff --git a/pg_llm_batch/pg8000_candidate_service_file.py b/pg_llm_batch/pg8000_candidate_service_file.py new file mode 100644 index 00000000..2efd8cdf --- /dev/null +++ b/pg_llm_batch/pg8000_candidate_service_file.py @@ -0,0 +1,145 @@ +"""Bounded caller-selected ``pg_service.conf`` resolver for the pg8000 candidate. + +PostgreSQL service files are an INI-like indirection from a service name to +connection parameters. pg8000 does not implement libpq's service-file lookup, +and the database driver must not silently acquire process-environment or +filesystem-discovery authority while pg-llm-batch evaluates a replacement for +Psycopg. This candidate resolver therefore reads exactly one caller-selected +file, applies a finite byte budget, and returns only the exact target stanza. + +The parser intentionally does not implement libpq LDAP lookup or ambient +``PGSERVICEFILE``/user/system search precedence. Those capabilities require +separate security and compatibility evidence. The returned mapping is validated +again by :class:`Pg8000CandidateDriverAdapter`, so unsupported PostgreSQL +connection parameters remain fail closed at the driver boundary. +""" + +from __future__ import annotations + +from pathlib import Path + +from .pg8000_candidate_driver_port import Pg8000CandidateInvalidConninfoError + + +_MAX_SERVICE_FILE_BYTES = 64 * 1024 + + +def _invalid_service_file(*, unsupported: bool = False) -> Pg8000CandidateInvalidConninfoError: + """Return one non-content-bearing error for service-file resolution failures.""" + if unsupported: + return Pg8000CandidateInvalidConninfoError( + "PostgreSQL connection selector is unsupported" + ) + return Pg8000CandidateInvalidConninfoError( + "PostgreSQL connection selector is invalid" + ) + + +def _has_disallowed_control(value: str) -> bool: + """Reject framing controls while allowing ordinary horizontal whitespace.""" + return any( + (ord(character) < 0x20 and character != "\t") or ord(character) == 0x7F + for character in value + ) + + +def _validate_service_name(service_name: object) -> str: + """Validate one exact service identity without normalizing caller authority.""" + if ( + type(service_name) is not str + or not service_name + or service_name != service_name.strip() + or _has_disallowed_control(service_name) + or "[" in service_name + or "]" in service_name + ): + raise _invalid_service_file() + return service_name + + +def _read_bounded_utf8(path: Path) -> str: + """Read one explicit service file under a finite strict-UTF-8 evidence budget.""" + try: + with path.open("rb") as handle: + payload = handle.read(_MAX_SERVICE_FILE_BYTES + 1) + except (OSError, ValueError): + raise _invalid_service_file() from None + if len(payload) > _MAX_SERVICE_FILE_BYTES: + raise _invalid_service_file() + try: + text = payload.decode("utf-8", errors="strict") + except UnicodeDecodeError: + raise _invalid_service_file() from None + if "\x00" in text: + raise _invalid_service_file() + return text + + +class Pg8000CandidateServiceFileResolver: + """Resolve one service stanza from an explicit local service-file capability. + + ``service_file`` is selected by the caller and retained as a concrete path; + this object never discovers user/system files and never reads environment + variables. Duplicate section/key authority and malformed target lines fail + closed. Non-target stanza contents are not promoted into the selected + connection parameters. + """ + + def __init__(self, service_file: Path) -> None: + if type(service_file) is not Path: + raise _invalid_service_file() + self._service_file = service_file + + def __call__(self, service_name: str) -> dict[str, str]: + """Return the exact target stanza or fail without reflecting file content.""" + target = _validate_service_name(service_name) + text = _read_bounded_utf8(self._service_file) + sections: set[str] = set() + target_found = False + target_active = False + parameters: dict[str, str] = {} + + for raw_line in text.splitlines(): + if _has_disallowed_control(raw_line): + raise _invalid_service_file() + stripped = raw_line.strip() + if not stripped or stripped.startswith("#"): + continue + + if stripped.startswith("["): + if not stripped.endswith("]") or stripped.count("[") != 1 or stripped.count("]") != 1: + raise _invalid_service_file() + section_name = stripped[1:-1].strip() + if ( + not section_name + or _has_disallowed_control(section_name) + or section_name in sections + ): + raise _invalid_service_file() + sections.add(section_name) + target_active = section_name == target + if target_active: + target_found = True + continue + + if not target_active: + continue + if stripped.lower().startswith("ldap://"): + raise _invalid_service_file(unsupported=True) + if "=" not in stripped: + raise _invalid_service_file() + key, value = stripped.split("=", 1) + key = key.strip() + value = value.strip() + if ( + not key + or _has_disallowed_control(key) + or _has_disallowed_control(value) + or key in parameters + ): + raise _invalid_service_file() + parameters[key] = value + + if not target_found: + raise _invalid_service_file() + return parameters From a6b79cbacef462a540438a6bc99b3903b6a92a67 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 21:50:57 +0900 Subject: [PATCH 216/338] fix(postgres): accept concrete pathlib service paths --- pg_llm_batch/pg8000_candidate_service_file.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pg_llm_batch/pg8000_candidate_service_file.py b/pg_llm_batch/pg8000_candidate_service_file.py index 2efd8cdf..493259cd 100644 --- a/pg_llm_batch/pg8000_candidate_service_file.py +++ b/pg_llm_batch/pg8000_candidate_service_file.py @@ -86,7 +86,7 @@ class Pg8000CandidateServiceFileResolver: """ def __init__(self, service_file: Path) -> None: - if type(service_file) is not Path: + if not isinstance(service_file, Path): raise _invalid_service_file() self._service_file = service_file @@ -107,7 +107,11 @@ def __call__(self, service_name: str) -> dict[str, str]: continue if stripped.startswith("["): - if not stripped.endswith("]") or stripped.count("[") != 1 or stripped.count("]") != 1: + if ( + not stripped.endswith("]") + or stripped.count("[") != 1 + or stripped.count("]") != 1 + ): raise _invalid_service_file() section_name = stripped[1:-1].strip() if ( From 47bb705db64798c87fce6730a0396b8ac9719ad8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 04:06:47 +0900 Subject: [PATCH 217/338] fix(ci): accept concrete pathlib paths in license gate --- tools/verify_candidate_wheel_licenses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/verify_candidate_wheel_licenses.py b/tools/verify_candidate_wheel_licenses.py index dc5fbc1c..514b3fa3 100644 --- a/tools/verify_candidate_wheel_licenses.py +++ b/tools/verify_candidate_wheel_licenses.py @@ -173,7 +173,7 @@ def verify_candidate_wheel_licenses(directory: Path) -> None: exact set prevents an unreviewed extra artifact from entering license evidence without a corresponding digest and policy decision. """ - if type(directory) is not Path or not directory.is_dir(): + if not isinstance(directory, Path) or not directory.is_dir(): raise CandidateWheelLicenseError("candidate wheel directory is invalid") wheel_names = {path.name for path in directory.glob("*.whl")} if wheel_names != set(_EXPECTED_WHEELS): From ad87a143f9e42d8747293c00db82a8cd948254d8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 04:08:12 +0900 Subject: [PATCH 218/338] test(ci): cover candidate license CLI path handling --- tests/test_candidate_wheel_license_verifier.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_candidate_wheel_license_verifier.py b/tests/test_candidate_wheel_license_verifier.py index 953aec87..8088a3c4 100644 --- a/tests/test_candidate_wheel_license_verifier.py +++ b/tests/test_candidate_wheel_license_verifier.py @@ -93,6 +93,13 @@ def test_exact_candidate_closure_requires_permissive_license_evidence(tmp_path: verifier.verify_candidate_wheel_licenses(tmp_path) +def test_candidate_license_cli_accepts_platform_path_subclass(tmp_path: Path) -> None: + verifier = _load_verifier() + _write_valid_closure(tmp_path) + + assert verifier.main([str(tmp_path)]) == 0 + + def test_candidate_closure_rejects_gpl_family_metadata_even_with_permissive_marker(tmp_path: Path) -> None: verifier = _load_verifier() _write_valid_closure(tmp_path) From 2a39bfff46489863c72c667e64cd144e476ff79b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 04:12:42 +0900 Subject: [PATCH 219/338] test(ddd): require CLI to use runtime driver owner --- tests/test_cli_postgres_driver_port.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_cli_postgres_driver_port.py b/tests/test_cli_postgres_driver_port.py index bcb44b49..c50343f9 100644 --- a/tests/test_cli_postgres_driver_port.py +++ b/tests/test_cli_postgres_driver_port.py @@ -9,6 +9,7 @@ import pytest from pg_llm_batch import cli +from pg_llm_batch import postgres_driver_runtime class _CandidateConninfoError(Exception): @@ -72,6 +73,18 @@ def test_cli_parser_rejects_candidate_reported_credential_fields() -> None: assert exc_info.value.code == 2 +def test_cli_default_driver_delegates_to_runtime_owner(monkeypatch) -> None: + """The CLI must not retain a second concrete-driver construction authority.""" + driver = _CandidateDriver() + monkeypatch.setattr( + postgres_driver_runtime, + "retained_postgres_driver", + lambda: driver, + ) + + assert cli._default_postgres_driver() is driver + + def test_cli_module_has_no_eager_psycopg_import() -> None: """Importing the CLI must not itself require the retained legacy driver.""" source = Path(cli.__file__).read_text(encoding="utf-8") From da8a1908559a5092b68d05644f52d683da18d16b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 04:13:03 +0900 Subject: [PATCH 220/338] test(ddd): require Compose to use runtime driver owner --- tests/test_compose_bootstrap_driver_port.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_compose_bootstrap_driver_port.py b/tests/test_compose_bootstrap_driver_port.py index c009bdc1..cebde1b5 100644 --- a/tests/test_compose_bootstrap_driver_port.py +++ b/tests/test_compose_bootstrap_driver_port.py @@ -7,6 +7,7 @@ from pathlib import Path from pg_llm_batch import compose_bootstrap +from pg_llm_batch import postgres_driver_runtime class _BootstrapDriver: @@ -81,6 +82,18 @@ def test_build_private_dsn_uses_default_driver_boundary_without_direct_renderer( ] +def test_compose_default_driver_delegates_to_runtime_owner(monkeypatch) -> None: + """Compose must not retain a second concrete-driver construction authority.""" + driver = _BootstrapDriver() + monkeypatch.setattr( + postgres_driver_runtime, + "retained_postgres_driver", + lambda: driver, + ) + + assert compose_bootstrap._default_postgres_driver() is driver + + def test_run_compose_health_forwards_one_driver_to_dsn_and_health_boundaries( tmp_path: Path, monkeypatch, From a1e3b878516f67509bb09d37438f58a2d4ac49da Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 04:13:39 +0900 Subject: [PATCH 221/338] refactor(ddd): centralize Compose driver selection --- pg_llm_batch/compose_bootstrap.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/pg_llm_batch/compose_bootstrap.py b/pg_llm_batch/compose_bootstrap.py index 94301266..16615c60 100644 --- a/pg_llm_batch/compose_bootstrap.py +++ b/pg_llm_batch/compose_bootstrap.py @@ -15,6 +15,7 @@ from pathlib import Path from typing import Sequence +from . import postgres_driver_runtime from .bootstrap import resolve_dsn from .exceptions import ConfigError from .health import serve_healthz @@ -25,16 +26,14 @@ def _default_postgres_driver() -> PostgresDriverPort: - """Load the retained default through the same PostgreSQL anti-corruption port. + """Delegate retained-driver construction to the canonical runtime selector. - Compose secret assembly must not import a concrete driver's conninfo helper - independently from the runtime database boundary. Keeping this lazy loader - behind ``PostgresDriverPort`` makes the retained Psycopg default replaceable - without creating a second connection-selector authority in the bootstrap. + Compose owns secret-file handling and private DSN assembly, not concrete + database-client selection. Routing the default through one runtime owner + keeps a future commercially admitted replacement atomic across package + surfaces instead of leaving a hidden Psycopg construction path here. """ - from .psycopg_driver_adapter import PsycopgDriverAdapter - - return PsycopgDriverAdapter() + return postgres_driver_runtime.retained_postgres_driver() def _load_database_password(password_file: Path) -> str: @@ -71,8 +70,8 @@ def _build_private_dsn( """Add the mounted password through the selected reviewed conninfo renderer. The selected driver parses the credential-free selector and renders a fresh - parameter snapshot containing the mounted password. The retained Psycopg - implementation is loaded only through ``PostgresDriverPort`` while the + parameter snapshot containing the mounted password. The retained concrete + implementation is selected only by ``postgres_driver_runtime`` while the commercial migration is incomplete, so this module has no independent concrete-driver conninfo authority. Parser or renderer diagnostics are normalized so secret material never escapes this bootstrap boundary. From f55b33a0a1bc9f1cf410c83d8a389d31dc0f4f2c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 04:14:53 +0900 Subject: [PATCH 222/338] refactor(ddd): centralize CLI driver selection --- pg_llm_batch/cli.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pg_llm_batch/cli.py b/pg_llm_batch/cli.py index 0b292aae..0333235f 100644 --- a/pg_llm_batch/cli.py +++ b/pg_llm_batch/cli.py @@ -36,7 +36,7 @@ from functools import partial from typing import List, Optional -from . import db +from . import db, postgres_driver_runtime from .batch_api_client import BatchAPIClient, config_credentials_provider from .bootstrap import resolve_dsn, resolve_secret_key from .config import PostgresConfigStore, SecretStore @@ -73,10 +73,13 @@ def error(self, message: str) -> None: def _default_postgres_driver() -> PostgresDriverPort: - """Load the retained Psycopg adapter only when a CLI parse needs the default.""" - from .psycopg_driver_adapter import PsycopgDriverAdapter + """Delegate concrete-driver construction to the canonical runtime selector. - return PsycopgDriverAdapter() + CLI parsing owns credential-safe argument validation, not the concrete + PostgreSQL client choice. A single selector keeps migration cutover atomic + across CLI, service, persistence, and Compose surfaces. + """ + return postgres_driver_runtime.retained_postgres_driver() def _validate_cli_dsn( @@ -217,7 +220,6 @@ def build_parser( description="Standalone Postgres LLM batch engine", ) sub = parser.add_subparsers(dest="command", required=True) - p_init = sub.add_parser("init-db", help="Apply batch schema (idempotent)") _add_common(p_init, postgres_driver=postgres_driver) @@ -454,4 +456,4 @@ async def _go() -> int: if __name__ == "__main__": # pragma: no cover - sys.exit(main()) + sys.exit(main()) \ No newline at end of file From 43412f8c77fece4ac6107bd529980a6060d77209 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 04:23:01 +0900 Subject: [PATCH 223/338] test(driver): remove stale checkpoint Psycopg monkeypatches --- tests/test_checkpoint_store_driver_port.py | 31 +++++++++++----------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/tests/test_checkpoint_store_driver_port.py b/tests/test_checkpoint_store_driver_port.py index 91e80908..844b51e2 100644 --- a/tests/test_checkpoint_store_driver_port.py +++ b/tests/test_checkpoint_store_driver_port.py @@ -81,25 +81,24 @@ def connect(self, dsn: str, **_kwargs: Any) -> _PortConnection: return connection -def _deny_legacy_psycopg_path(monkeypatch: pytest.MonkeyPatch) -> None: - """Make accidental fallback to the retained Psycopg path fail immediately.""" +def _deny_default_driver_path(monkeypatch: pytest.MonkeyPatch) -> None: + """Fail if an explicitly injected driver silently reacquires the runtime default.""" - def fail_require_psycopg() -> None: - raise AssertionError("legacy Psycopg availability check was reached") + def fail_default_driver() -> None: + raise AssertionError("default PostgreSQL runtime driver was reached") - class _ForbiddenPsycopg: - def connect(self, *_args: object, **_kwargs: object) -> None: - raise AssertionError("legacy Psycopg connection path was reached") - - monkeypatch.setattr(checkpoint_store, "_require_psycopg", fail_require_psycopg) - monkeypatch.setattr(checkpoint_store, "psycopg", _ForbiddenPsycopg()) + monkeypatch.setattr( + checkpoint_store, + "retained_postgres_driver", + fail_default_driver, + ) -def test_checkpoint_store_load_uses_injected_driver_port_without_psycopg( +def test_checkpoint_store_load_uses_injected_driver_port_without_default_driver( monkeypatch: pytest.MonkeyPatch, ) -> None: - """A migrated store must reach tenant SQL through the injected database port.""" - _deny_legacy_psycopg_path(monkeypatch) + """A migrated store must reach tenant SQL only through its injected driver.""" + _deny_default_driver_path(monkeypatch) driver = _DriverPortFake() store = PostgresBatchResultCheckpointStore( "postgresql://unit", @@ -113,12 +112,12 @@ def test_checkpoint_store_load_uses_injected_driver_port_without_psycopg( assert driver.calls[1][1] == ("tenant-a", "worker-a", "default", "batch-1") -def test_checkpoint_schema_application_uses_injected_driver_port_without_psycopg( +def test_checkpoint_schema_application_uses_injected_driver_port_without_default_driver( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: - """Schema migration must be able to run through the same replacement seam.""" - _deny_legacy_psycopg_path(monkeypatch) + """Schema migration must run through the injected driver without fallback.""" + _deny_default_driver_path(monkeypatch) migration = tmp_path / "checkpoint.sql" migration.write_text("CREATE TABLE checkpoint_probe (probe_id BIGINT);", encoding="utf-8") driver = _DriverPortFake() From 01919ed9577377b65f5e45f1be99ec2dfa80d906 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 04:23:35 +0900 Subject: [PATCH 224/338] test(driver): bind DB tests to runtime selector seam --- tests/test_db.py | 75 +++++++++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/tests/test_db.py b/tests/test_db.py index 02f7419d..bf698819 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -42,7 +42,9 @@ def commit(self): self.driver.commits += 1 -class _Psycopg: +class _Driver: + """Minimal database driver fake for default and injected port paths.""" + def __init__(self, row=None, error=None): self.row = row self.error = error @@ -57,9 +59,23 @@ def connect(self, dsn): return _Connection(self) +def _use_default_driver(monkeypatch: pytest.MonkeyPatch, driver: _Driver) -> None: + """Route the package default through the canonical runtime selector seam.""" + monkeypatch.setattr(db, "retained_postgres_driver", lambda: driver) + + +def _deny_default_driver(monkeypatch: pytest.MonkeyPatch) -> None: + """Fail if an explicitly injected database operation reacquires the default.""" + + def fail_default_driver(): + raise AssertionError("default PostgreSQL runtime driver was reached") + + monkeypatch.setattr(db, "retained_postgres_driver", fail_default_driver) + + def test_apply_schema_executes_packaged_file(monkeypatch, tmp_path): - driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) + driver = _Driver() + _use_default_driver(monkeypatch, driver) schema = tmp_path / "schema.sql" schema.write_text("CREATE TABLE snake_case_name (id int);", encoding="utf-8") monkeypatch.setattr(db, "SCHEMA_PATH", schema) @@ -70,10 +86,10 @@ def test_apply_schema_executes_packaged_file(monkeypatch, tmp_path): assert driver.commits == 1 -def test_apply_schema_uses_injected_driver_without_psycopg(monkeypatch, tmp_path): - """Schema bootstrap must migrate through the driver port before manifest swap.""" - driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", None) +def test_apply_schema_uses_injected_driver_without_default_driver(monkeypatch, tmp_path): + """Schema bootstrap must honor an injected driver without hidden reacquisition.""" + driver = _Driver() + _deny_default_driver(monkeypatch) schema = tmp_path / "schema.sql" schema.write_text("CREATE TABLE snake_case_name (id int);", encoding="utf-8") monkeypatch.setattr(db, "SCHEMA_PATH", schema) @@ -87,8 +103,8 @@ def test_apply_schema_uses_injected_driver_without_psycopg(monkeypatch, tmp_path def test_apply_schema_refuses_caller_selected_sql(monkeypatch, tmp_path): """Caller-controlled local files must not acquire arbitrary SQL authority.""" - driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) + driver = _Driver() + _use_default_driver(monkeypatch, driver) untrusted_schema = tmp_path / "untrusted.sql" untrusted_schema.write_text("DROP TABLE llm_requests;", encoding="utf-8") @@ -108,17 +124,17 @@ def test_apply_schema_refuses_caller_selected_sql(monkeypatch, tmp_path): ], ) def test_load_virtual_payload_preserves_canonical_jsonl(monkeypatch, stored, expected): - driver = _Psycopg((stored,)) - monkeypatch.setattr(db, "psycopg", driver) + driver = _Driver((stored,)) + _use_default_driver(monkeypatch, driver) assert db.load_virtual_payload("postgresql://x", "file-1") == expected assert driver.executions[0][1] == ("file-1",) -def test_load_virtual_payload_uses_injected_driver_without_psycopg(monkeypatch): - """Virtual payload reads must not require Psycopg once a driver port is injected.""" +def test_load_virtual_payload_uses_injected_driver_without_default_driver(monkeypatch): + """Virtual payload reads must honor the explicit driver boundary.""" stored = {"text": '{"id":1}\n', "line_count": 1} - driver = _Psycopg((stored,)) - monkeypatch.setattr(db, "psycopg", None) + driver = _Driver((stored,)) + _deny_default_driver(monkeypatch) assert ( db.load_virtual_payload( @@ -133,13 +149,14 @@ def test_load_virtual_payload_uses_injected_driver_without_psycopg(monkeypatch): def test_load_virtual_payload_returns_none_when_missing(monkeypatch): - monkeypatch.setattr(db, "psycopg", _Psycopg(None)) + driver = _Driver(None) + _use_default_driver(monkeypatch, driver) assert db.load_virtual_payload("postgresql://x", "missing") is None def test_model_metadata_normalizes_mode_and_handles_absence(monkeypatch): - driver = _Psycopg((" CHAT ", "o200k_base")) - monkeypatch.setattr(db, "psycopg", driver) + driver = _Driver((" CHAT ", "o200k_base")) + _use_default_driver(monkeypatch, driver) assert db.get_model_metadata("postgresql://x", "gpt-4o") == { "mode": "chat", "tokenizer_model": "o200k_base", @@ -156,10 +173,10 @@ def test_model_metadata_normalizes_mode_and_handles_absence(monkeypatch): assert db.get_model_metadata("postgresql://x", "") is None -def test_model_metadata_uses_injected_driver_without_psycopg(monkeypatch): - """Tokenizer metadata lookup must migrate through the same driver boundary.""" - driver = _Psycopg((" CHAT ", "o200k_base")) - monkeypatch.setattr(db, "psycopg", None) +def test_model_metadata_uses_injected_driver_without_default_driver(monkeypatch): + """Tokenizer metadata lookup must honor the explicit driver boundary.""" + driver = _Driver((" CHAT ", "o200k_base")) + _deny_default_driver(monkeypatch) assert db.get_model_metadata( "postgresql://x", @@ -173,13 +190,19 @@ def test_model_metadata_uses_injected_driver_without_psycopg(monkeypatch): def test_model_metadata_driver_failure_is_nonfatal(monkeypatch, caplog): - monkeypatch.setattr(db, "psycopg", _Psycopg(error=OSError("database down"))) + driver = _Driver(error=OSError("database down")) + _use_default_driver(monkeypatch, driver) with caplog.at_level("DEBUG"): assert db.get_model_metadata("postgresql://x", "gpt-4o") is None assert "database down" in caplog.text -def test_database_access_requires_psycopg(monkeypatch): - monkeypatch.setattr(db, "psycopg", None) - with pytest.raises(RuntimeError, match="psycopg is required"): +def test_database_access_requires_runtime_driver(monkeypatch): + """Default database access must fail when the canonical runtime selector fails.""" + + def fail_default_driver(): + raise RuntimeError("PostgreSQL runtime driver is required") + + monkeypatch.setattr(db, "retained_postgres_driver", fail_default_driver) + with pytest.raises(RuntimeError, match="runtime driver is required"): db.load_virtual_payload("postgresql://x", "file") From 5bdcdc15803230d8d97d7382e610ea09405179a9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 04:24:06 +0900 Subject: [PATCH 225/338] test(driver): remove stale orchestrator Psycopg seams --- tests/test_orchestrator_driver_port.py | 32 ++++++++++++++++++-------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/tests/test_orchestrator_driver_port.py b/tests/test_orchestrator_driver_port.py index 477bdfd0..129dcd43 100644 --- a/tests/test_orchestrator_driver_port.py +++ b/tests/test_orchestrator_driver_port.py @@ -94,7 +94,7 @@ def close(self) -> None: class _Driver: - """Minimal Psycopg-free driver for the orchestrator's database boundary.""" + """Minimal concrete-driver-free port fake for the orchestrator boundary.""" def __init__(self) -> None: self.executions: list[tuple[str, object | None]] = [] @@ -120,12 +120,25 @@ def jsonb(self, value: object) -> object: return ("jsonb", value) -def test_orchestrator_accepts_injected_driver_without_psycopg( +def _deny_default_driver(monkeypatch: pytest.MonkeyPatch) -> None: + """Fail if explicit orchestrator injection silently reacquires runtime default.""" + + def fail_default_driver(): + raise AssertionError("default PostgreSQL runtime driver was reached") + + monkeypatch.setattr( + orchestrator_module, + "retained_postgres_driver", + fail_default_driver, + ) + + +def test_orchestrator_accepts_injected_driver_without_default_driver( monkeypatch: pytest.MonkeyPatch, ) -> None: - """Batch lookup must remain usable after the Psycopg runtime is removed.""" + """Batch lookup must remain usable through an explicit replacement driver.""" driver = _Driver() - monkeypatch.setattr(orchestrator_module, "psycopg", None) + _deny_default_driver(monkeypatch) orchestrator = PostgresBatchOrchestrator( "postgresql://x", @@ -141,9 +154,9 @@ def test_orchestrator_accepts_injected_driver_without_psycopg( def test_assemble_payloads_passes_driver_to_model_metadata( monkeypatch: pytest.MonkeyPatch, ) -> None: - """Model metadata reads must not silently reacquire the legacy driver.""" + """Model metadata reads must preserve the explicitly selected driver.""" driver = _Driver() - monkeypatch.setattr(orchestrator_module, "psycopg", None) + _deny_default_driver(monkeypatch) metadata_calls: list[tuple[str, str, object]] = [] def _metadata( @@ -183,7 +196,7 @@ def test_prepare_batches_propagates_driver_to_store_and_token_counter( ) -> None: """Preparation must keep one explicit driver boundary across its DB helpers.""" driver = _Driver() - monkeypatch.setattr(orchestrator_module, "psycopg", None) + _deny_default_driver(monkeypatch) calls: list[tuple[str, object]] = [] class _Config: @@ -254,10 +267,9 @@ def close(self) -> None: def test_persist_payloads_uses_driver_jsonb_transaction_and_row_count( monkeypatch: pytest.MonkeyPatch, ) -> None: - """Persistence must not leak Psycopg JSONB or rowcount semantics past the port.""" + """Persistence must use only the selected driver's JSONB and row-count seams.""" driver = _Driver() - monkeypatch.setattr(orchestrator_module, "psycopg", None) - monkeypatch.setattr(orchestrator_module, "Jsonb", None) + _deny_default_driver(monkeypatch) orchestrator = PostgresBatchOrchestrator( "postgresql://x", postgres_driver=driver, From a4a84a9ae9a26f306c6c6be23d95801913fbc472 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 04:24:47 +0900 Subject: [PATCH 226/338] test(driver): remove stale token-counter Psycopg seam --- tests/test_token_counter_driver_port.py | 29 ++++++++++++++++++------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/tests/test_token_counter_driver_port.py b/tests/test_token_counter_driver_port.py index 09349cf1..acfcf4b4 100644 --- a/tests/test_token_counter_driver_port.py +++ b/tests/test_token_counter_driver_port.py @@ -80,7 +80,7 @@ def close(self) -> None: class _Driver: - """Minimal Psycopg-free driver implementing the token-counting port surface.""" + """Minimal concrete-driver-free port fake for token counting.""" def __init__( self, @@ -128,12 +128,25 @@ def leave_count_execution(self) -> None: self.active_count_executions -= 1 -def test_token_counter_uses_injected_driver_without_psycopg( +def _deny_default_driver(monkeypatch: pytest.MonkeyPatch) -> None: + """Fail if explicit token-counter injection silently reacquires the runtime default.""" + + def fail_default_driver(): + raise AssertionError("default PostgreSQL runtime driver was reached") + + monkeypatch.setattr( + token_counter_module, + "retained_postgres_driver", + fail_default_driver, + ) + + +def test_token_counter_uses_injected_driver_without_default_driver( monkeypatch: pytest.MonkeyPatch, ) -> None: - """A replacement candidate must exercise pg_tiktoken without Psycopg authority.""" + """A replacement candidate must exercise pg_tiktoken through its own port.""" driver = _Driver() - monkeypatch.setattr(token_counter_module, "psycopg", None) + _deny_default_driver(monkeypatch) metadata_calls: list[tuple[str, str, object]] = [] def _metadata(dsn: str, model: str, *, postgres_driver: object = None) -> dict[str, str]: @@ -155,7 +168,7 @@ def test_token_counter_serializes_shared_driver_connection_use( ) -> None: """A DB-API level-1 candidate must never receive concurrent connection calls.""" driver = _Driver(execution_delay_seconds=0.03) - monkeypatch.setattr(token_counter_module, "psycopg", None) + _deny_default_driver(monkeypatch) monkeypatch.setattr( token_counter_module, "get_model_metadata", @@ -179,9 +192,9 @@ def _count_one(index: int) -> int: def test_token_counter_uses_driver_error_classification_for_encode_fallback( monkeypatch: pytest.MonkeyPatch, ) -> None: - """Undefined-function fallback must not depend on a Psycopg exception class.""" + """Undefined-function fallback must depend only on the driver-port classifier.""" driver = _Driver(primary_error=_UndefinedFunctionError("undefined function")) - monkeypatch.setattr(token_counter_module, "psycopg", None) + _deny_default_driver(monkeypatch) monkeypatch.setattr( token_counter_module, "get_model_metadata", @@ -199,7 +212,7 @@ def test_non_undefined_driver_error_discards_cached_connection_before_retry( ) -> None: """A transient DB failure must retry on a fresh connection without disabling pg_tiktoken.""" driver = _Driver(primary_error=_OtherDriverError("temporary database failure")) - monkeypatch.setattr(token_counter_module, "psycopg", None) + _deny_default_driver(monkeypatch) monkeypatch.setattr( token_counter_module, "get_model_metadata", From 857b0923bc510aaa14607348d587985bfea85e72 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 04:32:06 +0900 Subject: [PATCH 227/338] test(postgres): reject blocking service-file authority --- tests/test_pg8000_candidate_service_file.py | 53 +++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/test_pg8000_candidate_service_file.py b/tests/test_pg8000_candidate_service_file.py index e041cc79..b8ff3860 100644 --- a/tests/test_pg8000_candidate_service_file.py +++ b/tests/test_pg8000_candidate_service_file.py @@ -2,6 +2,8 @@ from __future__ import annotations +import os +import stat from pathlib import Path import pytest @@ -125,3 +127,54 @@ def test_candidate_service_file_rejects_non_file_and_non_string_service( resolver = Pg8000CandidateServiceFileResolver(service_file) with pytest.raises(Pg8000CandidateInvalidConninfoError): resolver(7) # type: ignore[arg-type] + + +def test_candidate_service_file_uses_nonblocking_descriptor_open( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Reach the service-file byte budget without a blocking special-file open.""" + service_file = tmp_path / "pg_service.conf" + service_file.write_text("[analytics]\nhost=db.example\n", encoding="utf-8") + observed_flags: list[int] = [] + real_open = os.open + + def recording_open( + path: str | bytes | os.PathLike[str] | os.PathLike[bytes], + flags: int, + mode: int = 0o777, + *, + dir_fd: int | None = None, + ) -> int: + observed_flags.append(flags) + if dir_fd is None: + return real_open(path, flags, mode) + return real_open(path, flags, mode, dir_fd=dir_fd) + + monkeypatch.setattr(os, "open", recording_open) + + assert Pg8000CandidateServiceFileResolver(service_file)("analytics")["host"] == "db.example" + assert observed_flags + if hasattr(os, "O_NONBLOCK"): + assert observed_flags[0] & os.O_NONBLOCK + + +def test_candidate_service_file_rejects_non_regular_opened_descriptor( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Do not treat pipes or devices as caller-selected service-file authority.""" + service_file = tmp_path / "pg_service.conf" + service_file.write_text("[analytics]\nhost=db.example\n", encoding="utf-8") + real_fstat = os.fstat + + def fifo_fstat(fd: int) -> os.stat_result: + observed = real_fstat(fd) + values = list(observed) + values[0] = stat.S_IFIFO | 0o600 + return os.stat_result(values) + + monkeypatch.setattr(os, "fstat", fifo_fstat) + + with pytest.raises(Pg8000CandidateInvalidConninfoError): + Pg8000CandidateServiceFileResolver(service_file)("analytics") From c88a36f7c43bd58e487a032903d88cb0f927f9ae Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 04:34:00 +0900 Subject: [PATCH 228/338] fix(postgres): bound service-file descriptor authority --- pg_llm_batch/pg8000_candidate_service_file.py | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/pg_llm_batch/pg8000_candidate_service_file.py b/pg_llm_batch/pg8000_candidate_service_file.py index 493259cd..312f64c7 100644 --- a/pg_llm_batch/pg8000_candidate_service_file.py +++ b/pg_llm_batch/pg8000_candidate_service_file.py @@ -16,6 +16,8 @@ from __future__ import annotations +import os +import stat from pathlib import Path from .pg8000_candidate_driver_port import Pg8000CandidateInvalidConninfoError @@ -58,12 +60,51 @@ def _validate_service_name(service_name: object) -> str: def _read_bounded_utf8(path: Path) -> str: - """Read one explicit service file under a finite strict-UTF-8 evidence budget.""" + """Read one explicit regular service file under a finite UTF-8 byte budget. + + The caller-selected path is opened nonblocking where the platform supports + it, then the retained descriptor is required to name a regular file before + any bytes are consumed. This prevents a FIFO or device path from bypassing + the resolver's finite read contract before parsing begins. + """ + flags = ( + os.O_RDONLY + | getattr(os, "O_BINARY", 0) + | getattr(os, "O_CLOEXEC", 0) + | getattr(os, "O_NONBLOCK", 0) + ) try: - with path.open("rb") as handle: - payload = handle.read(_MAX_SERVICE_FILE_BYTES + 1) + descriptor = os.open(path, flags) except (OSError, ValueError): raise _invalid_service_file() from None + + primary_error: BaseException | None = None + try: + if not stat.S_ISREG(os.fstat(descriptor).st_mode): + raise _invalid_service_file() + + chunks: list[bytes] = [] + remaining = _MAX_SERVICE_FILE_BYTES + 1 + while remaining > 0: + chunk = os.read(descriptor, remaining) + if not chunk: + break + chunks.append(chunk) + remaining -= len(chunk) + payload = b"".join(chunks) + except Pg8000CandidateInvalidConninfoError as exc: + primary_error = exc + raise + except (OSError, ValueError) as exc: + primary_error = exc + raise _invalid_service_file() from None + finally: + try: + os.close(descriptor) + except OSError: + if primary_error is None: + raise _invalid_service_file() from None + if len(payload) > _MAX_SERVICE_FILE_BYTES: raise _invalid_service_file() try: From 8a816deb407c23dff3c0c84726656acf68491741 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 04:39:56 +0900 Subject: [PATCH 229/338] test(postgres): cover service descriptor failures --- tests/test_pg8000_candidate_service_file.py | 71 ++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/tests/test_pg8000_candidate_service_file.py b/tests/test_pg8000_candidate_service_file.py index b8ff3860..081ec71a 100644 --- a/tests/test_pg8000_candidate_service_file.py +++ b/tests/test_pg8000_candidate_service_file.py @@ -153,7 +153,8 @@ def recording_open( monkeypatch.setattr(os, "open", recording_open) - assert Pg8000CandidateServiceFileResolver(service_file)("analytics")["host"] == "db.example" + resolved = Pg8000CandidateServiceFileResolver(service_file)("analytics") + assert resolved["host"] == "db.example" assert observed_flags if hasattr(os, "O_NONBLOCK"): assert observed_flags[0] & os.O_NONBLOCK @@ -178,3 +179,71 @@ def fifo_fstat(fd: int) -> os.stat_result: with pytest.raises(Pg8000CandidateInvalidConninfoError): Pg8000CandidateServiceFileResolver(service_file)("analytics") + + +def test_candidate_service_file_normalizes_descriptor_read_failure( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Normalize descriptor read failure without exposing path or content details.""" + service_file = tmp_path / "pg_service.conf" + service_file.write_text("[analytics]\nhost=db.example\n", encoding="utf-8") + + def failing_read(_fd: int, _size: int) -> bytes: + raise OSError("synthetic descriptor read failure") + + monkeypatch.setattr(os, "read", failing_read) + + with pytest.raises( + Pg8000CandidateInvalidConninfoError, + match="PostgreSQL connection selector is invalid", + ): + Pg8000CandidateServiceFileResolver(service_file)("analytics") + + +def test_candidate_service_file_normalizes_close_only_failure( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Fail closed when descriptor cleanup is the only failed operation.""" + service_file = tmp_path / "pg_service.conf" + service_file.write_text("[analytics]\nhost=db.example\n", encoding="utf-8") + real_close = os.close + + def failing_close(fd: int) -> None: + real_close(fd) + raise OSError("synthetic descriptor close failure") + + monkeypatch.setattr(os, "close", failing_close) + + with pytest.raises( + Pg8000CandidateInvalidConninfoError, + match="PostgreSQL connection selector is invalid", + ): + Pg8000CandidateServiceFileResolver(service_file)("analytics") + + +def test_candidate_service_file_preserves_primary_failure_when_close_also_fails( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Do not let a cleanup failure replace an existing descriptor failure.""" + service_file = tmp_path / "pg_service.conf" + service_file.write_text("[analytics]\nhost=db.example\n", encoding="utf-8") + real_close = os.close + + def failing_fstat(_fd: int) -> os.stat_result: + raise OSError("synthetic descriptor metadata failure") + + def failing_close(fd: int) -> None: + real_close(fd) + raise OSError("synthetic descriptor close failure") + + monkeypatch.setattr(os, "fstat", failing_fstat) + monkeypatch.setattr(os, "close", failing_close) + + with pytest.raises( + Pg8000CandidateInvalidConninfoError, + match="PostgreSQL connection selector is invalid", + ): + Pg8000CandidateServiceFileResolver(service_file)("analytics") From e33d622664fcc1b584be3b08915a978d860efdf0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 04:44:51 +0900 Subject: [PATCH 230/338] test(postgres): reject service-file metadata drift --- tests/test_pg8000_candidate_service_file.py | 30 +++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/test_pg8000_candidate_service_file.py b/tests/test_pg8000_candidate_service_file.py index 081ec71a..34d76abf 100644 --- a/tests/test_pg8000_candidate_service_file.py +++ b/tests/test_pg8000_candidate_service_file.py @@ -247,3 +247,33 @@ def failing_close(fd: int) -> None: match="PostgreSQL connection selector is invalid", ): Pg8000CandidateServiceFileResolver(service_file)("analytics") + + +def test_candidate_service_file_rejects_metadata_drift_during_read( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Reject bytes if retained service-file metadata changes during inspection.""" + service_file = tmp_path / "pg_service.conf" + service_file.write_text("[analytics]\nhost=db.example\n", encoding="utf-8") + real_fstat = os.fstat + fstat_calls = 0 + + def drifting_fstat(fd: int) -> os.stat_result: + nonlocal fstat_calls + fstat_calls += 1 + observed = real_fstat(fd) + if fstat_calls == 1: + return observed + values = list(observed) + values[6] = observed.st_size + 1 + return os.stat_result(values) + + monkeypatch.setattr(os, "fstat", drifting_fstat) + + with pytest.raises( + Pg8000CandidateInvalidConninfoError, + match="PostgreSQL connection selector is invalid", + ): + Pg8000CandidateServiceFileResolver(service_file)("analytics") + assert fstat_calls >= 2 From 5cf3c5778c06a275ee232f10eb864f281463a4f2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 04:45:33 +0900 Subject: [PATCH 231/338] fix(postgres): bind stable service-file snapshot --- pg_llm_batch/pg8000_candidate_service_file.py | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/pg_llm_batch/pg8000_candidate_service_file.py b/pg_llm_batch/pg8000_candidate_service_file.py index 312f64c7..1876ae61 100644 --- a/pg_llm_batch/pg8000_candidate_service_file.py +++ b/pg_llm_batch/pg8000_candidate_service_file.py @@ -59,13 +59,26 @@ def _validate_service_name(service_name: object) -> str: return service_name +def _service_file_snapshot(observed: os.stat_result) -> tuple[int, int, int, int, int, int]: + """Capture metadata that must remain stable while service bytes are retained.""" + return ( + observed.st_dev, + observed.st_ino, + observed.st_mode, + observed.st_size, + observed.st_mtime_ns, + observed.st_ctime_ns, + ) + + def _read_bounded_utf8(path: Path) -> str: """Read one explicit regular service file under a finite UTF-8 byte budget. The caller-selected path is opened nonblocking where the platform supports - it, then the retained descriptor is required to name a regular file before - any bytes are consumed. This prevents a FIFO or device path from bypassing - the resolver's finite read contract before parsing begins. + it, then the retained descriptor is required to name one stable regular + file before and after the bounded read. This prevents a FIFO/device path or + in-place mutation from becoming connection-selector authority while bytes + are being inspected. """ flags = ( os.O_RDONLY @@ -80,8 +93,10 @@ def _read_bounded_utf8(path: Path) -> str: primary_error: BaseException | None = None try: - if not stat.S_ISREG(os.fstat(descriptor).st_mode): + before = os.fstat(descriptor) + if not stat.S_ISREG(before.st_mode): raise _invalid_service_file() + before_snapshot = _service_file_snapshot(before) chunks: list[bytes] = [] remaining = _MAX_SERVICE_FILE_BYTES + 1 @@ -92,6 +107,13 @@ def _read_bounded_utf8(path: Path) -> str: chunks.append(chunk) remaining -= len(chunk) payload = b"".join(chunks) + + after = os.fstat(descriptor) + if ( + _service_file_snapshot(after) != before_snapshot + or len(payload) != after.st_size + ): + raise _invalid_service_file() except Pg8000CandidateInvalidConninfoError as exc: primary_error = exc raise From 8cfba1d920f8079ea252e4a013be22e8d2aecd43 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 05:11:20 +0900 Subject: [PATCH 232/338] test(postgres): cover pg8000 restore catalog parity --- tests/smoke_pg8000_candidate_postgres.py | 29 ++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/tests/smoke_pg8000_candidate_postgres.py b/tests/smoke_pg8000_candidate_postgres.py index 9aeffc40..04eae368 100644 --- a/tests/smoke_pg8000_candidate_postgres.py +++ b/tests/smoke_pg8000_candidate_postgres.py @@ -5,8 +5,9 @@ image without adding the candidate to the production dependency graph. The checks cover the candidate URI connection factory, portable connection/cursor ACL, thread-affine connection use, transaction, parameter, JSONB, UUID/timestamp, -affected-row, narrow PostgreSQL error classification, and transaction-local -tenant semantics that must be proven before candidate promotion. +affected-row, narrow PostgreSQL error classification, restore-catalog inspection, +and transaction-local tenant semantics that must be proven before candidate +promotion. """ from __future__ import annotations @@ -21,6 +22,7 @@ from pg_llm_batch.pg8000_candidate_driver_port import Pg8000CandidateDriverAdapter from pg_llm_batch.pg8000_driver_candidate_jsonb import adapt_pg8000_jsonb +from pg_llm_batch.postgres_restore_acceptance import inspect_postgres_restore_catalog _EXPECTED_VERSION = "1.31.5" _EXPECTED_DATABASE = "pgllm" @@ -64,6 +66,28 @@ def _cleanup() -> None: connection.close() +def _assert_restore_catalog_inspection() -> None: + """Prove the candidate can inspect the packaged restore catalog exactly. + + The production restore acceptance query binds finite Python lists through + ``ANY(%s)`` and consumes catalog booleans and tuple rows. Running that exact + query through pg8000 closes a driver-parity gap that unit adapters cannot + prove, without turning the candidate into the production runtime. + """ + connection = _connection() + try: + connection.set_autocommit(True) + evidence = inspect_postgres_restore_catalog(connection) + if evidence.required_table_count != 11: + raise AssertionError("candidate restore catalog table evidence changed") + if evidence.required_index_count != 2: + raise AssertionError("candidate restore catalog index evidence changed") + if evidence.lifecycle_rls_forced is not True: + raise AssertionError("candidate restore catalog RLS evidence changed") + finally: + connection.close() + + def _prepare_rls_fixture() -> tuple[uuid.UUID, datetime]: """Create an ephemeral RLS fixture and return exact typed evidence values.""" evidence_uuid = uuid.uuid4() @@ -217,6 +241,7 @@ def main() -> None: if cursor.fetchone() != (_EXPECTED_DATABASE, _EXPECTED_USER, "bound"): raise AssertionError("candidate parameter/result semantics changed") + _assert_restore_catalog_inspection() _assert_undefined_function_classification() _cleanup() try: From d26ef9038cd48200486eeeacd99766cc2d29b258 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 06:15:07 +0900 Subject: [PATCH 233/338] test(postgres): prove candidate keyword and service selectors --- tests/smoke_pg8000_candidate_postgres.py | 85 +++++++++++++++++++++--- 1 file changed, 77 insertions(+), 8 deletions(-) diff --git a/tests/smoke_pg8000_candidate_postgres.py b/tests/smoke_pg8000_candidate_postgres.py index 04eae368..0290a7a8 100644 --- a/tests/smoke_pg8000_candidate_postgres.py +++ b/tests/smoke_pg8000_candidate_postgres.py @@ -3,11 +3,11 @@ This script is intentionally outside pytest discovery. CI installs one immutable pg8000 candidate artifact and runs this smoke against the repository PostgreSQL image without adding the candidate to the production dependency graph. The -checks cover the candidate URI connection factory, portable connection/cursor -ACL, thread-affine connection use, transaction, parameter, JSONB, UUID/timestamp, -affected-row, narrow PostgreSQL error classification, restore-catalog inspection, -and transaction-local tenant semantics that must be proven before candidate -promotion. +checks cover the candidate URI, keyword, and explicit service connection +selectors, portable connection/cursor ACL, thread-affine connection use, +transaction, parameter, JSONB, UUID/timestamp, affected-row, narrow PostgreSQL +error classification, restore-catalog inspection, and transaction-local tenant +semantics that must be proven before candidate promotion. """ from __future__ import annotations @@ -21,6 +21,7 @@ from pg8000 import dbapi from pg_llm_batch.pg8000_candidate_driver_port import Pg8000CandidateDriverAdapter +from pg_llm_batch.pg8000_candidate_service_file import Pg8000CandidateServiceFileResolver from pg_llm_batch.pg8000_driver_candidate_jsonb import adapt_pg8000_jsonb from pg_llm_batch.postgres_restore_acceptance import inspect_postgres_restore_catalog @@ -35,8 +36,8 @@ def _candidate_driver() -> Pg8000CandidateDriverAdapter: return Pg8000CandidateDriverAdapter(dbapi) -def _connection() -> object: - """Open one finite candidate connection from a private in-memory URI selector.""" +def _candidate_password() -> str: + """Read the ephemeral CI credential without placing it in process arguments.""" password_file = os.environ.get("PG8000_CANDIDATE_PASSWORD_FILE") if not password_file: raise RuntimeError("PG8000_CANDIDATE_PASSWORD_FILE is required") @@ -46,14 +47,81 @@ def _connection() -> object: raise RuntimeError("PG8000 candidate password file could not be read") from None if not password: raise RuntimeError("PG8000 candidate password file is empty") + return password + +def _connection() -> object: + """Open one finite candidate connection from a private in-memory URI selector.""" driver = _candidate_driver() parameters = dict(driver.parse_conninfo(_CREDENTIAL_FREE_DSN)) - parameters["password"] = password + parameters["password"] = _candidate_password() private_dsn = driver.make_conninfo(parameters) return driver.connect(private_dsn, connect_timeout_seconds=5) +def _assert_keyword_and_service_selector_connections() -> None: + """Prove exact-artifact connection parity beyond the URI-only happy path. + + Unit tests establish grammar and precedence, but issue #322 requires the + replacement to preserve the selectors used by deployed PostgreSQL clients. + This probe therefore opens real PostgreSQL sessions through both the bounded + keyword grammar and the explicit caller-selected service-file resolver. The + temporary service file remains credential-free; the ephemeral password is + injected by the trusted in-process resolver so it is never written to disk. + """ + password = _candidate_password() + keyword_driver = _candidate_driver() + keyword_connection = keyword_driver.connect( + "host=127.0.0.1 port=5432 dbname=pgllm user=pgllm " + f"password={password}", + connect_timeout_seconds=5, + ) + try: + with keyword_connection.cursor() as cursor: + cursor.execute("SELECT current_database(), current_user") + if cursor.fetchone() != (_EXPECTED_DATABASE, _EXPECTED_USER): + raise AssertionError("candidate keyword selector connection changed") + finally: + keyword_connection.close() + + service_file = Path(os.environ["PG8000_CANDIDATE_PASSWORD_FILE"]).with_name( + "pg8000_candidate_service.conf" + ) + service_file.write_text( + "[candidate]\n" + "host=127.0.0.1\n" + "port=5432\n" + "dbname=pgllm\n" + "user=pgllm\n", + encoding="utf-8", + ) + try: + file_resolver = Pg8000CandidateServiceFileResolver(service_file) + + def resolve_service(service_name: str) -> dict[str, str]: + parameters = file_resolver(service_name) + parameters["password"] = password + return parameters + + service_driver = Pg8000CandidateDriverAdapter( + dbapi, + service_resolver=resolve_service, + ) + service_connection = service_driver.connect( + "service=candidate", + connect_timeout_seconds=5, + ) + try: + with service_connection.cursor() as cursor: + cursor.execute("SELECT current_database(), current_user") + if cursor.fetchone() != (_EXPECTED_DATABASE, _EXPECTED_USER): + raise AssertionError("candidate service selector connection changed") + finally: + service_connection.close() + finally: + service_file.unlink(missing_ok=True) + + def _cleanup() -> None: """Remove candidate-only database objects even after a prior interrupted smoke.""" connection = _connection() @@ -233,6 +301,7 @@ def main() -> None: if metadata.version("pg8000") != _EXPECTED_VERSION: raise AssertionError("unexpected pg8000 candidate version") _candidate_driver() + _assert_keyword_and_service_selector_connections() connection = _connection() with connection as transaction: From 4804dc160efeef9e8a02fbecd4efbcb8b10b90e2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 06:17:40 +0900 Subject: [PATCH 234/338] test(postgres): prove candidate context commit on real PostgreSQL --- tests/smoke_pg8000_candidate_postgres.py | 34 ++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/smoke_pg8000_candidate_postgres.py b/tests/smoke_pg8000_candidate_postgres.py index 0290a7a8..449b56b2 100644 --- a/tests/smoke_pg8000_candidate_postgres.py +++ b/tests/smoke_pg8000_candidate_postgres.py @@ -218,6 +218,39 @@ def _prepare_rls_fixture() -> tuple[uuid.UUID, datetime]: return evidence_uuid, evidence_time +def _assert_transaction_commit() -> None: + """Prove a successful package connection context commits a real write.""" + connection = _connection() + with connection as transaction: + with transaction.cursor() as cursor: + cursor.execute( + """ + UPDATE pg8000_candidate_contract + SET evidence_json = %s + WHERE tenant_scope = %s + """, + (adapt_pg8000_jsonb({"committed": True}), "tenant-b"), + ) + if cursor.row_count() != 1: + raise AssertionError("candidate commit probe did not update one row") + + verification = _connection() + try: + with verification.cursor() as cursor: + cursor.execute( + """ + SELECT evidence_json + FROM pg8000_candidate_contract + WHERE tenant_scope = %s + """, + ("tenant-b",), + ) + if cursor.fetchone() != ({"committed": True},): + raise AssertionError("candidate connection context did not commit") + finally: + verification.close() + + def _assert_transaction_rollback() -> None: """Prove the package connection context rolls an exceptional write back.""" connection = _connection() @@ -315,6 +348,7 @@ def main() -> None: _cleanup() try: evidence_uuid, evidence_time = _prepare_rls_fixture() + _assert_transaction_commit() _assert_transaction_rollback() _assert_typed_rls_read(evidence_uuid, evidence_time) finally: From 9ea37cee93bfb85cf08eed827ed94a3915434cea Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 07:06:54 +0900 Subject: [PATCH 235/338] test(postgres): prove candidate context rollback on real PostgreSQL --- tests/smoke_pg8000_candidate_postgres.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/smoke_pg8000_candidate_postgres.py b/tests/smoke_pg8000_candidate_postgres.py index 449b56b2..e70da637 100644 --- a/tests/smoke_pg8000_candidate_postgres.py +++ b/tests/smoke_pg8000_candidate_postgres.py @@ -252,7 +252,7 @@ def _assert_transaction_commit() -> None: def _assert_transaction_rollback() -> None: - """Prove the package connection context rolls an exceptional write back.""" + """Prove an exceptional package connection context rolls a real write back.""" connection = _connection() try: with connection as transaction: @@ -272,6 +272,22 @@ def _assert_transaction_rollback() -> None: if str(exc) != "candidate rollback probe": raise + verification = _connection() + try: + with verification.cursor() as cursor: + cursor.execute( + """ + SELECT evidence_json + FROM pg8000_candidate_contract + WHERE tenant_scope = %s + """, + ("tenant-a",), + ) + if cursor.fetchone() != ({"candidate": "pg8000", "visible": True},): + raise AssertionError("candidate connection context did not roll back") + finally: + verification.close() + def _assert_undefined_function_classification() -> None: """Prove SQLSTATE-based undefined-function classification on real PostgreSQL.""" From 0a804b82108ee38be8b64e29dd89fea2c1ef7c7a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 12:08:00 +0900 Subject: [PATCH 236/338] test(postgres): align candidate terminal close contract --- pg_llm_batch/pg8000_driver_candidate_adapter.py | 2 ++ .../test_pg8000_driver_candidate_error_precedence.py | 11 ++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pg_llm_batch/pg8000_driver_candidate_adapter.py b/pg_llm_batch/pg8000_driver_candidate_adapter.py index bff8e1ae..78d18441 100644 --- a/pg_llm_batch/pg8000_driver_candidate_adapter.py +++ b/pg_llm_batch/pg8000_driver_candidate_adapter.py @@ -78,6 +78,7 @@ class Pg8000CandidateCursorAdapter(PostgresCursorPort): """ def __init__(self, cursor: Any) -> None: + """Retain one already-admitted raw cursor without acquiring connection authority.""" self._cursor = cursor @staticmethod @@ -232,6 +233,7 @@ class Pg8000CandidateConnectionAdapter(PostgresConnectionPort): """ def __init__(self, connection: Any) -> None: + """Retain one admitted raw connection and initialize terminal-state tracking.""" self._connection = connection self._closed = False diff --git a/tests/test_pg8000_driver_candidate_error_precedence.py b/tests/test_pg8000_driver_candidate_error_precedence.py index 9fbbb117..45db4bfe 100644 --- a/tests/test_pg8000_driver_candidate_error_precedence.py +++ b/tests/test_pg8000_driver_candidate_error_precedence.py @@ -6,8 +6,9 @@ rollback succeeds but later connection cleanup fails. Direct connection execution must likewise close the internally created cursor when execution fails, because the caller never receives that cursor and therefore cannot -release it. These tests keep that recovery contract independent from the -real-driver PostgreSQL smoke gate. +release it. Candidate connection state remains terminal after a close attempt +because pg8000 1.31.5 releases its retained transport in the driver's ``finally`` +path even when protocol-level cleanup raises. """ from __future__ import annotations @@ -100,7 +101,7 @@ def test_candidate_context_preserves_commit_failure_when_close_also_fails() -> N assert raw.commit_count == 1 assert raw.rollback_count == 0 assert raw.close_count == 1 - assert adapter.is_closed() is False + assert adapter.is_closed() is True def test_candidate_context_preserves_rollback_failure_when_close_also_fails() -> None: @@ -114,7 +115,7 @@ def test_candidate_context_preserves_rollback_failure_when_close_also_fails() -> assert raw.commit_count == 0 assert raw.rollback_count == 1 assert raw.close_count == 1 - assert adapter.is_closed() is False + assert adapter.is_closed() is True def test_candidate_context_preserves_application_error_when_only_close_fails() -> None: @@ -129,7 +130,7 @@ def test_candidate_context_preserves_application_error_when_only_close_fails() - assert caught.value is application_error assert raw.rollback_count == 1 assert raw.close_count == 1 - assert adapter.is_closed() is False + assert adapter.is_closed() is True @pytest.mark.parametrize("fail_close", [False, True]) From c95275fa536c10b6a91e1f6ac09eebadd037f666 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 12:16:31 +0900 Subject: [PATCH 237/338] docs(postgres): complete driver adapter constructor contracts --- pg_llm_batch/pg8000_candidate_driver_port.py | 1 + pg_llm_batch/pg8000_candidate_service_file.py | 1 + pg_llm_batch/pg8000_thread_affine_candidate_adapter.py | 2 ++ pg_llm_batch/psycopg_driver_adapter.py | 4 +++- 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pg_llm_batch/pg8000_candidate_driver_port.py b/pg_llm_batch/pg8000_candidate_driver_port.py index 55f5d43b..22a5262c 100644 --- a/pg_llm_batch/pg8000_candidate_driver_port.py +++ b/pg_llm_batch/pg8000_candidate_driver_port.py @@ -326,6 +326,7 @@ def __init__( *, service_resolver: ServiceResolver | None = None, ) -> None: + """Bind one admitted DB-API module and optional explicit service resolver.""" validate_pg8000_dbapi_module(dbapi_module) connect = vars(dbapi_module).get("connect") if not callable(connect): diff --git a/pg_llm_batch/pg8000_candidate_service_file.py b/pg_llm_batch/pg8000_candidate_service_file.py index 1876ae61..428440d5 100644 --- a/pg_llm_batch/pg8000_candidate_service_file.py +++ b/pg_llm_batch/pg8000_candidate_service_file.py @@ -149,6 +149,7 @@ class Pg8000CandidateServiceFileResolver: """ def __init__(self, service_file: Path) -> None: + """Retain exactly one caller-selected service file after validating its path type.""" if not isinstance(service_file, Path): raise _invalid_service_file() self._service_file = service_file diff --git a/pg_llm_batch/pg8000_thread_affine_candidate_adapter.py b/pg_llm_batch/pg8000_thread_affine_candidate_adapter.py index 520e5eb9..92861e6e 100644 --- a/pg_llm_batch/pg8000_thread_affine_candidate_adapter.py +++ b/pg_llm_batch/pg8000_thread_affine_candidate_adapter.py @@ -38,6 +38,7 @@ class Pg8000ThreadAffineCandidateCursorAdapter(Pg8000CandidateCursorAdapter): """ def __init__(self, cursor: Any) -> None: + """Retain one candidate cursor and bind its capability to this thread.""" super().__init__(cursor) self._owner_thread_id = get_ident() @@ -112,6 +113,7 @@ class Pg8000ThreadAffineCandidateConnectionAdapter(Pg8000CandidateConnectionAdap """ def __init__(self, connection: Any) -> None: + """Retain one candidate connection and bind its session to this thread.""" super().__init__(connection) self._owner_thread_id = get_ident() diff --git a/pg_llm_batch/psycopg_driver_adapter.py b/pg_llm_batch/psycopg_driver_adapter.py index 2762fd66..a1aa3bf9 100644 --- a/pg_llm_batch/psycopg_driver_adapter.py +++ b/pg_llm_batch/psycopg_driver_adapter.py @@ -58,6 +58,7 @@ class PsycopgCursorAdapter(PostgresCursorPort): """ def __init__(self, cursor: Any) -> None: + """Retain one Psycopg cursor behind the driver-neutral cursor contract.""" self._cursor = cursor @staticmethod @@ -151,6 +152,7 @@ class PsycopgConnectionAdapter(PostgresConnectionPort): """ def __init__(self, connection: Any) -> None: + """Retain one Psycopg connection as the exact session capability.""" self._connection = connection def cursor(self) -> PsycopgCursorAdapter: @@ -255,4 +257,4 @@ def is_invalid_conninfo(self, error: BaseException) -> bool: def is_undefined_function(self, error: BaseException) -> bool: """Recognize only Psycopg's PostgreSQL undefined-function error category.""" - return isinstance(error, UndefinedFunction) \ No newline at end of file + return isinstance(error, UndefinedFunction) From 6fa87e920905918eb4892defd56e8ae4f8bf1be4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:09:44 +0900 Subject: [PATCH 238/338] fix(batch): restore orchestrator accumulator contract --- pg_llm_batch/token_counter.py | 97 +++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 34 deletions(-) diff --git a/pg_llm_batch/token_counter.py b/pg_llm_batch/token_counter.py index e083a41b..fe3e881e 100644 --- a/pg_llm_batch/token_counter.py +++ b/pg_llm_batch/token_counter.py @@ -417,43 +417,72 @@ def reset(self) -> None: self.entries: List[Tuple[str, str, int]] = [] self.total_tokens = 0 self.record_count = 0 - self.total_bytes = 0 - self._payload = StringIO() + self.byte_size = 0 - def can_add(self, jsonl_line: str, tokens: int) -> bool: - """Return whether an entry would fit every configured resource ceiling.""" - if type(jsonl_line) is not str: - return False - if type(tokens) is not int or tokens < 0: + def compute_tokens( + self, system_prompt: str, user_prompt: str + ) -> Tuple[int, int, int]: + """Return total, system, and user token counts for one prompt pair.""" + system_tokens = self.token_counter.count_tokens(system_prompt or "", self.model) + user_tokens = self.token_counter.count_tokens(user_prompt or "", self.model) + return system_tokens + user_tokens, system_tokens, user_tokens + + @staticmethod + def compute_byte_size(json_line: str) -> int: + """Return the UTF-8 byte size including the JSONL newline.""" + return len(json_line.encode("utf-8")) + 1 + + def would_exceed(self, tokens: int, byte_size: int) -> bool: + """Report whether adding a line would exceed any active limit.""" + if self.record_count == 0: return False - line_bytes = len((jsonl_line + "\n").encode("utf-8")) + if self.total_tokens + tokens > self.token_limit: + return True + if self.byte_size + byte_size > self.max_bytes: + return True if self.record_count + 1 > self.max_records: - return False - if self.total_bytes + line_bytes > self.max_bytes: - return False - return self.total_tokens + tokens <= self.token_limit + return True + return False - def add(self, request_id: str, jsonl_line: str, tokens: int) -> bool: - """Append one validated JSONL line when all resource ceilings permit it.""" - if not self.can_add(jsonl_line, tokens): - return False - line_bytes = len((jsonl_line + "\n").encode("utf-8")) - self.entries.append((request_id, jsonl_line, tokens)) - self._payload.write(jsonl_line) - self._payload.write("\n") + def add_entry( + self, request_id: str, json_line: str, tokens: int, byte_size: int + ) -> None: + """Append one valid record and update aggregate counters.""" + if tokens > self.token_limit: + raise TokenLimitExceededError( + current_tokens=tokens, + limit_tokens=self.token_limit, + batch_id=request_id, + ) + if byte_size > self.max_bytes: + raise ValidationError( + field="byte_size", + value=byte_size, + reason=f"single JSONL record exceeds max_bytes={self.max_bytes}", + ) + self.entries.append((request_id, json_line, tokens)) self.total_tokens += tokens self.record_count += 1 - self.total_bytes += line_bytes - return True - - def content(self) -> str: - """Return the canonical newline-terminated JSONL payload.""" - return self._payload.getvalue() - - def is_empty(self) -> bool: - """Return whether no request has been accumulated.""" - return not self.entries - - def __len__(self) -> int: - """Return the number of accumulated requests.""" - return self.record_count + self.byte_size += byte_size + + def drain(self) -> Dict[str, Any]: + """Return accumulated metadata and reset the accumulator.""" + if not self.entries: + return {} + metadata = { + "record_count": self.record_count, + "total_tokens": self.total_tokens, + "request_ids": [rid for rid, _, _ in self.entries], + "lines": [line for _, line, _ in self.entries], + "byte_size": self.byte_size, + } + self.reset() + return metadata + + def to_jsonl(self) -> str: + """Return accumulated lines as newline-terminated JSONL text (in-memory).""" + buffer = StringIO() + for _, line, _ in self.entries: + buffer.write(line) + buffer.write("\n") + return buffer.getvalue() From 89eeacdf51b9a648866fc8c7fe19788e46154f31 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:17:37 +0900 Subject: [PATCH 239/338] test(postgres): remove obsolete psycopg monkeypatches --- .../test_postgres_driver_remote_lifecycle.py | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/tests/test_postgres_driver_remote_lifecycle.py b/tests/test_postgres_driver_remote_lifecycle.py index 6770ad2d..72b7bac1 100644 --- a/tests/test_postgres_driver_remote_lifecycle.py +++ b/tests/test_postgres_driver_remote_lifecycle.py @@ -6,8 +6,6 @@ from datetime import datetime, timezone from typing import Any -import pytest - from pg_llm_batch import db @@ -100,12 +98,9 @@ def _persisted_remote_batch_row( ) -def test_observation_order_reservation_uses_injected_driver_without_psycopg( - monkeypatch: pytest.MonkeyPatch, -) -> None: +def test_observation_order_reservation_uses_injected_driver_without_psycopg() -> None: """Driver migration must retain the default standalone tenant boundary.""" driver = _Driver(rows=[(41,)]) - monkeypatch.setattr(db, "psycopg", None) order = db.reserve_remote_batch_observation_order( "postgresql://x", @@ -123,16 +118,13 @@ def test_observation_order_reservation_uses_injected_driver_without_psycopg( ] -def test_stale_lifecycle_write_reads_persisted_state_through_injected_driver( - monkeypatch: pytest.MonkeyPatch, -) -> None: +def test_stale_lifecycle_write_reads_persisted_state_through_injected_driver() -> None: """Port row-count semantics must preserve the stale-write recovery contract.""" observed = datetime(2026, 9, 2, 12, 0, tzinfo=timezone.utc) driver = _Driver( rows=[_persisted_remote_batch_row(observed)], affected_rows=0, ) - monkeypatch.setattr(db, "psycopg", None) snapshot = db.persist_remote_batch_state( "postgresql://x", @@ -150,20 +142,14 @@ def test_stale_lifecycle_write_reads_persisted_state_through_injected_driver( assert snapshot["observation_order"] == 1 assert snapshot["completed_requests"] == 1 - assert any( - "SELECT tenant_scope" in query - for query, _params in driver.executions - ) + assert any("SELECT tenant_scope" in query for query, _params in driver.executions) assert driver.commits == 1 -def test_remote_lifecycle_read_uses_injected_driver_without_psycopg( - monkeypatch: pytest.MonkeyPatch, -) -> None: +def test_remote_lifecycle_read_uses_injected_driver_without_psycopg() -> None: """Tenant-scoped reads must not reacquire the legacy driver implicitly.""" observed = datetime(2026, 9, 2, 12, 0, tzinfo=timezone.utc) driver = _Driver(rows=[_persisted_remote_batch_row(observed)]) - monkeypatch.setattr(db, "psycopg", None) snapshot = db.get_remote_batch_state( "postgresql://x", From 68c1e9c421fbe24c0d4aad8b44ad11b98a620130 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:18:14 +0900 Subject: [PATCH 240/338] test(ci): make uv cache invariant topology-aware --- tests/test_dependency_refresh_contract.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_dependency_refresh_contract.py b/tests/test_dependency_refresh_contract.py index f4c3af33..138489c5 100644 --- a/tests/test_dependency_refresh_contract.py +++ b/tests/test_dependency_refresh_contract.py @@ -15,7 +15,7 @@ def _assert_action_uses_immutable_commits(workflow: str, action: str) -> None: def test_ci_uses_reviewed_action_commits_and_explicit_cache_pruning() -> None: - """CI uses immutable action revisions and preserves the cache-cost policy.""" + """Every setup-uv use is immutable and explicitly preserves the cache-cost policy.""" workflow = Path(".github/workflows/ci.yml").read_text(encoding="utf-8") for action in ( "step-security/harden-runner", @@ -24,7 +24,9 @@ def test_ci_uses_reviewed_action_commits_and_explicit_cache_pruning() -> None: "astral-sh/setup-uv", ): _assert_action_uses_immutable_commits(workflow, action) - assert workflow.count("prune-cache: true") == 2 + setup_uv_references = re.findall(r"astral-sh/setup-uv@[^\s]+", workflow) + assert setup_uv_references + assert workflow.count("prune-cache: true") == len(setup_uv_references) def test_container_build_inputs_use_reviewed_immutable_digests() -> None: From 49d1f8337e7b2c39776d371cf6be1872f65fbfe9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:18:29 +0900 Subject: [PATCH 241/338] test(ci): decouple uv pin contract from job count --- tests/test_uv_toolchain_pin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_uv_toolchain_pin.py b/tests/test_uv_toolchain_pin.py index ae4b9d2f..2a4c315a 100644 --- a/tests/test_uv_toolchain_pin.py +++ b/tests/test_uv_toolchain_pin.py @@ -74,7 +74,7 @@ def test_ci_uses_setup_uv_without_an_explicit_latest_override() -> None: workflow = CI_WORKFLOW.read_text(encoding="utf-8") setup_uv_steps = _setup_uv_step_blocks(workflow) - assert len(setup_uv_steps) == 2, "unexpected setup-uv step count" + assert setup_uv_steps, "CI must retain at least one setup-uv step" for step in setup_uv_steps: assert _SETUP_UV_VERSION_INPUT.search(step) is None, step From bacb0f56fb70f7bb564961f8ed94aaaf7107989e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:18:49 +0900 Subject: [PATCH 242/338] test(health): accept optional driver injection seam --- tests/test_health_public_boundary_current.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_health_public_boundary_current.py b/tests/test_health_public_boundary_current.py index 66824aea..a93af490 100644 --- a/tests/test_health_public_boundary_current.py +++ b/tests/test_health_public_boundary_current.py @@ -42,7 +42,11 @@ def serve_forever(self) -> None: observed["body"] = handler.wfile.getvalue() monkeypatch.setattr("http.server.HTTPServer", FakeHTTPServer) - monkeypatch.setattr(health, "check_health", lambda _dsn: internal_report) + monkeypatch.setattr( + health, + "check_health", + lambda _dsn, *, postgres_driver=None: internal_report, + ) health.serve_healthz("postgresql://example", host="127.0.0.1", port=8090) From ac0c658dc00bf7099e63f259685381d8b1253b62 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:19:25 +0900 Subject: [PATCH 243/338] test(token): distinguish availability probe from fallback --- tests/test_token_counter_driver_port.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_token_counter_driver_port.py b/tests/test_token_counter_driver_port.py index acfcf4b4..24ba75ce 100644 --- a/tests/test_token_counter_driver_port.py +++ b/tests/test_token_counter_driver_port.py @@ -229,4 +229,7 @@ def test_non_undefined_driver_error_discards_cached_connection_before_retry( assert counter.count_tokens("second", "model-a") == 7 assert len(driver.connections) == 2 assert driver.connections[1].autocommit_values == [True] - assert not any("tiktoken_encode" in query for query, _params in driver.executions) + assert not any( + "tiktoken_encode" in query and "to_regprocedure" not in query + for query, _params in driver.executions + ) From 02c543adb66ceef9e9908cb56411c410ea5e8578 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:19:42 +0900 Subject: [PATCH 244/338] test(token): initialize driver-bound diagnostic fixture --- .../test_token_counter_diagnostic_confidentiality.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_token_counter_diagnostic_confidentiality.py b/tests/test_token_counter_diagnostic_confidentiality.py index 8ea8a156..7e170667 100644 --- a/tests/test_token_counter_diagnostic_confidentiality.py +++ b/tests/test_token_counter_diagnostic_confidentiality.py @@ -2,12 +2,21 @@ """Regression tests for token-counting database diagnostic confidentiality.""" import logging +from threading import RLock import pytest from pg_llm_batch.token_counter import TokenCounter +class _GenericErrorDriver: + """Classify the injected diagnostic failure as a non-undefined-function error.""" + + @staticmethod + def is_undefined_function(_error: BaseException) -> bool: + return False + + def test_database_failure_log_does_not_render_lower_layer_exception( monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture, @@ -16,6 +25,9 @@ def test_database_failure_log_does_not_render_lower_layer_exception( secret_sentinel = "PROMPT-SECRET-token-counter-diagnostic-sentinel" counter = object.__new__(TokenCounter) counter._pg_available = True + counter._pg_connection_lock = RLock() + counter._pg_conn = None + counter._postgres_driver = _GenericErrorDriver() def fail_count(*_args: object, **_kwargs: object) -> int: raise RuntimeError(secret_sentinel) From b8e6830c5bcac0c3cae80c535aaf3887ff306642 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:32:18 +0900 Subject: [PATCH 245/338] test(postgres): inject lifecycle driver port --- tests/test_durable_lifecycle_field_contract.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/test_durable_lifecycle_field_contract.py b/tests/test_durable_lifecycle_field_contract.py index e1b2e11c..d46d90f0 100644 --- a/tests/test_durable_lifecycle_field_contract.py +++ b/tests/test_durable_lifecycle_field_contract.py @@ -37,12 +37,10 @@ def _provider_batch(*, status: object, endpoint: object) -> dict[str, object]: ["future_state", "COMPLETED", "x" * 65, "completed\x00secret", 7], ) def test_persistence_rejects_unsupported_status_before_database_io( - monkeypatch: pytest.MonkeyPatch, status: object, ) -> None: """Reject unsupported provider status evidence before PostgreSQL mutation.""" driver = _NoDatabaseIO() - monkeypatch.setattr(db, "psycopg", driver) with pytest.raises( ValueError, @@ -54,6 +52,7 @@ def test_persistence_rejects_unsupported_status_before_database_io( _provider_batch(status=status, endpoint="/v1/responses"), 1, observed_at=datetime(2026, 8, 13, tzinfo=timezone.utc), + postgres_driver=driver, ) assert str(status) not in str(exc.value) @@ -71,12 +70,10 @@ def test_persistence_rejects_unsupported_status_before_database_io( ], ) def test_persistence_rejects_unsupported_endpoint_before_database_io( - monkeypatch: pytest.MonkeyPatch, endpoint: object, ) -> None: """Reject unsupported provider endpoint evidence before PostgreSQL mutation.""" driver = _NoDatabaseIO() - monkeypatch.setattr(db, "psycopg", driver) with pytest.raises( ValueError, @@ -88,6 +85,7 @@ def test_persistence_rejects_unsupported_endpoint_before_database_io( _provider_batch(status="validating", endpoint=endpoint), 1, observed_at=datetime(2026, 8, 13, tzinfo=timezone.utc), + postgres_driver=driver, ) assert str(endpoint) not in str(exc.value) @@ -143,4 +141,4 @@ def test_official_openai_statuses_and_endpoints_normalize_deterministically() -> assert snapshot["batch_endpoint"] == endpoint assert (snapshot["terminal_at"] is observed) is ( status in {"failed", "completed", "expired", "cancelled"} - ) + ) \ No newline at end of file From ddd9c6fbc6216a520760251040f62b766ca83dbc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:33:47 +0900 Subject: [PATCH 246/338] test(health): inject postgres driver port --- .../test_health_database_boolean_boundary.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/tests/test_health_database_boolean_boundary.py b/tests/test_health_database_boolean_boundary.py index 1586469a..a4c61475 100644 --- a/tests/test_health_database_boolean_boundary.py +++ b/tests/test_health_database_boolean_boundary.py @@ -42,26 +42,28 @@ def cursor(self): class _Psycopg: - """Return one deterministic fake PostgreSQL connection.""" + """Return one deterministic fake PostgreSQL connection through the driver port.""" def __init__(self, rows): self._rows = rows - def connect(self, _dsn, *, connect_timeout): - assert connect_timeout == 5 + def connect(self, _dsn, *, connect_timeout_seconds=None): + assert connect_timeout_seconds == 5 return _Connection(self._rows) -def test_database_readiness_boolean_is_not_truth_coerced(monkeypatch): +def test_database_readiness_boolean_is_not_truth_coerced(): """Malformed database readiness cannot become a true local or HTTP signal.""" rows = [ ("database", "false", "malformed database boolean"), ("pg_tiktoken", True, "installed"), ("com_config", True, "ready"), ] - monkeypatch.setattr(health, "psycopg", _Psycopg(rows)) - report = health.check_health("postgresql://example") + report = health.check_health( + "postgresql://example", + postgres_driver=_Psycopg(rows), + ) assert report["ready"] is False database = next( @@ -74,7 +76,7 @@ def test_database_readiness_boolean_is_not_truth_coerced(monkeypatch): assert health.public_health_report(report)["ready"] is False -def test_local_health_rejects_duplicate_required_components(monkeypatch): +def test_local_health_rejects_duplicate_required_components(): """Duplicate required database rows cannot make local readiness healthy.""" rows = [ ("database", True, "connected"), @@ -82,9 +84,11 @@ def test_local_health_rejects_duplicate_required_components(monkeypatch): ("pg_tiktoken", True, "installed"), ("com_config", True, "ready"), ] - monkeypatch.setattr(health, "psycopg", _Psycopg(rows)) - report = health.check_health("postgresql://example") + report = health.check_health( + "postgresql://example", + postgres_driver=_Psycopg(rows), + ) assert report["ready"] is False assert [ From 236b55c4d648050054a1b44d587986bdaf9a89e3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:34:12 +0900 Subject: [PATCH 247/338] test(token): remove obsolete psycopg seam --- tests/test_token_counter_undefined_function_cleanup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_token_counter_undefined_function_cleanup.py b/tests/test_token_counter_undefined_function_cleanup.py index a74f17c8..4f7eba58 100644 --- a/tests/test_token_counter_undefined_function_cleanup.py +++ b/tests/test_token_counter_undefined_function_cleanup.py @@ -95,7 +95,6 @@ def test_terminal_undefined_function_failure_closes_cached_driver_session( ) -> None: """Both missing pg_tiktoken entry points must disable and release the session.""" driver = _Driver() - monkeypatch.setattr(token_counter_module, "psycopg", None) monkeypatch.setattr( token_counter_module, "get_model_metadata", From a6212ba7c511970a294ac0b20acea63abb953331 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:34:46 +0900 Subject: [PATCH 248/338] test(postgres): document driver port contract module --- tests/test_postgres_driver_port.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_postgres_driver_port.py b/tests/test_postgres_driver_port.py index 2b2a7fe6..6094ff4e 100644 --- a/tests/test_postgres_driver_port.py +++ b/tests/test_postgres_driver_port.py @@ -1,3 +1,5 @@ +"""Contract tests for the provider-neutral PostgreSQL driver port.""" + from __future__ import annotations from collections.abc import Mapping From 69a4ecc3052e8b13a185cbf7f8ceee033491453a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:34:58 +0900 Subject: [PATCH 249/338] test(postgres): document candidate package contract --- tests/test_postgres_driver_candidate_package_name.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_postgres_driver_candidate_package_name.py b/tests/test_postgres_driver_candidate_package_name.py index a31774b8..dae35e63 100644 --- a/tests/test_postgres_driver_candidate_package_name.py +++ b/tests/test_postgres_driver_candidate_package_name.py @@ -1,3 +1,5 @@ +"""Candidate distribution-name evidence tests for PostgreSQL driver admission.""" + from __future__ import annotations import pytest From ce4448b95ac0a6b6200fb6830a1f347c23821361 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:35:11 +0900 Subject: [PATCH 250/338] test(postgres): document candidate provenance contract --- tests/test_postgres_driver_candidate_report_provenance.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_postgres_driver_candidate_report_provenance.py b/tests/test_postgres_driver_candidate_report_provenance.py index 4a0fafa4..30e801fb 100644 --- a/tests/test_postgres_driver_candidate_report_provenance.py +++ b/tests/test_postgres_driver_candidate_report_provenance.py @@ -1,3 +1,5 @@ +"""Immutable candidate-report provenance tests for PostgreSQL driver admission.""" + from __future__ import annotations from dataclasses import fields From 605e5f5dacba8101b7b7a19b46d9a42af4146331 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:35:22 +0900 Subject: [PATCH 251/338] test(postgres): document candidate unicode boundary --- tests/test_postgres_driver_candidate_surrogate.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_postgres_driver_candidate_surrogate.py b/tests/test_postgres_driver_candidate_surrogate.py index 2e8660a4..9c932a36 100644 --- a/tests/test_postgres_driver_candidate_surrogate.py +++ b/tests/test_postgres_driver_candidate_surrogate.py @@ -1,3 +1,5 @@ +"""Unicode-boundary tests for PostgreSQL driver candidate evidence.""" + from __future__ import annotations import pytest From 8822a9402f1464504401a05ecfe472d66715a9e2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:35:34 +0900 Subject: [PATCH 252/338] test(postgres): document connection context contract --- tests/test_postgres_driver_connection_context_contract.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_postgres_driver_connection_context_contract.py b/tests/test_postgres_driver_connection_context_contract.py index e15c8e1e..6bdfd8ec 100644 --- a/tests/test_postgres_driver_connection_context_contract.py +++ b/tests/test_postgres_driver_connection_context_contract.py @@ -1,3 +1,5 @@ +"""Connection-context parity contract for PostgreSQL driver candidates.""" + from __future__ import annotations from pg_llm_batch.postgres_driver_candidate import ( From bc4065e92fb6201883cda2689813a369ac578610 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:36:05 +0900 Subject: [PATCH 253/338] test(postgres): document review regression module --- tests/test_postgres_driver_review_contracts.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_postgres_driver_review_contracts.py b/tests/test_postgres_driver_review_contracts.py index 6ac3039e..c6ffc693 100644 --- a/tests/test_postgres_driver_review_contracts.py +++ b/tests/test_postgres_driver_review_contracts.py @@ -1,3 +1,5 @@ +"""Review regressions for PostgreSQL driver row-count and tenant contracts.""" + from __future__ import annotations from datetime import datetime, timezone From 61cd4e9ffdafb125c05b35249c51713b0a9ad068 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:36:54 +0900 Subject: [PATCH 254/338] test(postgres): document candidate admission module --- tests/test_postgres_driver_candidate.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_postgres_driver_candidate.py b/tests/test_postgres_driver_candidate.py index a2891482..3fcffd26 100644 --- a/tests/test_postgres_driver_candidate.py +++ b/tests/test_postgres_driver_candidate.py @@ -1,3 +1,5 @@ +"""Admission evidence tests for permissive PostgreSQL driver candidates.""" + from __future__ import annotations import pytest @@ -328,4 +330,4 @@ def test_candidate_rejects_unbounded_identity_evidence(field_name: str) -> None: def test_candidate_rejects_duplicate_python_version_evidence() -> None: with pytest.raises(PostgresDriverCandidateEvidenceError, match="Python version"): - _evidence(python_versions=("3.14", "3.14")) + _evidence(python_versions=("3.14", "3.14")) \ No newline at end of file From 5d566a1da30c0ea41fad3e456da8cdaec03fd0e3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:37:29 +0900 Subject: [PATCH 255/338] test(postgres): document retained adapter module --- tests/test_psycopg_driver_adapter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_psycopg_driver_adapter.py b/tests/test_psycopg_driver_adapter.py index 45a64300..aefd3709 100644 --- a/tests/test_psycopg_driver_adapter.py +++ b/tests/test_psycopg_driver_adapter.py @@ -1,3 +1,5 @@ +"""Parity tests for the retained Psycopg implementation of the driver port.""" + from __future__ import annotations from typing import Any From 495f6b1b27caa6b6217b80d7b4fade724ec430f2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:38:07 +0900 Subject: [PATCH 256/338] test(config): inject secret-store driver port --- tests/test_secret_store_fernet_availability.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_secret_store_fernet_availability.py b/tests/test_secret_store_fernet_availability.py index da570651..2288ee00 100644 --- a/tests/test_secret_store_fernet_availability.py +++ b/tests/test_secret_store_fernet_availability.py @@ -37,12 +37,14 @@ def test_fernet_request_fails_before_database_access_when_crypto_is_unavailable( ) -> None: """Never downgrade an explicit encryption request to Base64 persistence.""" fake_psycopg = _Psycopg() - monkeypatch.setattr(config_mod, "psycopg", fake_psycopg) monkeypatch.setattr(config_mod, "Fernet", None) - monkeypatch.setattr(config_mod.SecretStore, "_ensure_table", lambda _self: None) with pytest.raises(ConfigError, match="Fernet"): - config_mod.SecretStore("postgresql://database", fernet_key="explicit-key") + config_mod.SecretStore( + "postgresql://database", + fernet_key="explicit-key", + postgres_driver=fake_psycopg, + ) assert fake_psycopg.connect_calls == 0 assert fake_psycopg.connection.close_calls == 0 From c5c99f7d392f4e32a26ae574b0a49cbb2fe20df4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:38:19 +0900 Subject: [PATCH 257/338] test(config): inject encryption-policy driver port --- tests/test_secret_store_encryption_policy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_secret_store_encryption_policy.py b/tests/test_secret_store_encryption_policy.py index 40418ea5..48ea8a7f 100644 --- a/tests/test_secret_store_encryption_policy.py +++ b/tests/test_secret_store_encryption_policy.py @@ -33,15 +33,15 @@ def connect(self, _dsn: str) -> _Connection: return self.connection -def test_encryption_required_without_key_fails_before_database_access(monkeypatch) -> None: +def test_encryption_required_without_key_fails_before_database_access() -> None: """An encryption-required deployment cannot silently select Base64 storage.""" fake_psycopg = _Psycopg() - monkeypatch.setattr(config_mod, "psycopg", fake_psycopg) with pytest.raises(ConfigError, match="encryption"): config_mod.SecretStore( "postgresql://database", require_encryption=True, + postgres_driver=fake_psycopg, ) assert fake_psycopg.connect_calls == 0 From 34409244cb82f16ab927fadaa910df2181ea7295 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:40:05 +0900 Subject: [PATCH 258/338] test(postgres): inject standalone lifecycle driver port --- ...test_standalone_lifecycle_compatibility.py | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/tests/test_standalone_lifecycle_compatibility.py b/tests/test_standalone_lifecycle_compatibility.py index a5e25b6b..2e6f92e3 100644 --- a/tests/test_standalone_lifecycle_compatibility.py +++ b/tests/test_standalone_lifecycle_compatibility.py @@ -5,8 +5,6 @@ from typing import Any -import pytest - from pg_llm_batch import db @@ -28,6 +26,10 @@ def execute(self, sql: str, params: Any = None) -> None: """Record one statement and its bound values.""" self.driver.executions.append((sql, params)) + def row_count(self) -> int: + """Report one affected lifecycle row for the successful fake write.""" + return 1 + class _Connection: """Expose one recording cursor and a no-op transaction commit.""" @@ -53,7 +55,7 @@ def commit(self) -> None: class _Psycopg: - """Minimal psycopg replacement for standalone return-shape verification.""" + """Minimal driver port for standalone return-shape verification.""" def __init__(self) -> None: self.executions: list[tuple[str, Any]] = [] @@ -63,12 +65,9 @@ def connect(self, _dsn: str) -> _Connection: return _Connection(self) -def test_standalone_persistence_keeps_the_pre_tenant_return_shape( - monkeypatch: pytest.MonkeyPatch, -) -> None: +def test_standalone_persistence_keeps_the_pre_tenant_return_shape() -> None: """Adding tenant isolation must not add a new key to the legacy helper result.""" driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) snapshot = db.persist_remote_batch_state( "postgresql://compatibility", @@ -79,6 +78,7 @@ def test_standalone_persistence_keeps_the_pre_tenant_return_shape( "request_counts": {"total": 1, "completed": 0, "failed": 0}, }, 1, + postgres_driver=driver, ) assert "tenant_scope" not in snapshot @@ -90,12 +90,9 @@ def test_standalone_persistence_keeps_the_pre_tenant_return_shape( ) -def test_explicit_tenant_persistence_exposes_the_tenant_identity( - monkeypatch: pytest.MonkeyPatch, -) -> None: +def test_explicit_tenant_persistence_exposes_the_tenant_identity() -> None: """The new tenant-aware helper returns its explicit trusted scope.""" driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) snapshot = db.persist_tenant_remote_batch_state( "postgresql://compatibility", @@ -107,6 +104,7 @@ def test_explicit_tenant_persistence_exposes_the_tenant_identity( "request_counts": {"total": 1, "completed": 0, "failed": 0}, }, 2, + postgres_driver=driver, ) assert snapshot["tenant_scope"] == "tenant-a" From f4609e2451d2292ed6197b2866f82b87fac07cfd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:40:55 +0900 Subject: [PATCH 259/338] test(postgres): inject lifecycle progress driver port --- tests/test_remote_batch_progress_invariant.py | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/tests/test_remote_batch_progress_invariant.py b/tests/test_remote_batch_progress_invariant.py index ac851ddf..809bbbbc 100644 --- a/tests/test_remote_batch_progress_invariant.py +++ b/tests/test_remote_batch_progress_invariant.py @@ -56,7 +56,7 @@ def commit(self) -> None: class _Psycopg: - """Minimal deterministic psycopg replacement for boundary tests.""" + """Minimal deterministic driver port for boundary tests.""" def __init__(self, *, upsert_rowcount: int = 1, stored_row: Any = None) -> None: self.executions: list[tuple[str, Any]] = [] @@ -71,12 +71,9 @@ def connect(self, dsn: str) -> _Connection: return _Connection(self) -def test_persistence_rejects_impossible_same_observation_before_database( - monkeypatch: Any, -) -> None: +def test_persistence_rejects_impossible_same_observation_before_database() -> None: """Completed plus failed requests cannot exceed one explicitly known total.""" driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) with pytest.raises(ValueError, match="request_counts progress is inconsistent"): db.persist_remote_batch_state( @@ -88,6 +85,7 @@ def test_persistence_rejects_impossible_same_observation_before_database( "request_counts": {"total": 1, "completed": 1, "failed": 1}, }, observation_order=1, + postgres_driver=driver, ) assert driver.connections == [] @@ -104,13 +102,11 @@ def test_persistence_rejects_impossible_same_observation_before_database( ], ) def test_persistence_distinguishes_unknown_total_from_explicit_zero( - monkeypatch: Any, request_counts: dict[str, object], expected_total_known: bool, ) -> None: """Persist knownness internally without widening the public snapshot shape.""" driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) snapshot = db.persist_remote_batch_state( "postgresql://example", @@ -121,6 +117,7 @@ def test_persistence_distinguishes_unknown_total_from_explicit_zero( "request_counts": request_counts, }, observation_order=1, + postgres_driver=driver, ) assert snapshot["total_requests"] == 0 @@ -133,7 +130,7 @@ def test_persistence_distinguishes_unknown_total_from_explicit_zero( assert persistence_params[13] is expected_total_known -def test_skipped_progress_upsert_returns_the_persisted_snapshot(monkeypatch: Any) -> None: +def test_skipped_progress_upsert_returns_the_persisted_snapshot() -> None: """A rejected monotonic merge must not be reported as successfully persisted.""" stored_row = ( "standalone", @@ -155,7 +152,6 @@ def test_skipped_progress_upsert_returns_the_persisted_snapshot(monkeypatch: Any None, ) driver = _Psycopg(upsert_rowcount=0, stored_row=stored_row) - monkeypatch.setattr(db, "psycopg", driver) result = db.persist_remote_batch_state( "postgresql://example", @@ -166,6 +162,7 @@ def test_skipped_progress_upsert_returns_the_persisted_snapshot(monkeypatch: Any "request_counts": {"total": 10, "completed": 0, "failed": 2}, }, observation_order=2, + postgres_driver=driver, ) assert result["observation_order"] == 1 @@ -178,12 +175,9 @@ def test_skipped_progress_upsert_returns_the_persisted_snapshot(monkeypatch: Any ) -def test_skipped_progress_upsert_without_stored_row_fails_closed( - monkeypatch: Any, -) -> None: +def test_skipped_progress_upsert_without_stored_row_fails_closed() -> None: """A rejected update without a rereadable durable row is an integrity error.""" driver = _Psycopg(upsert_rowcount=0, stored_row=None) - monkeypatch.setattr(db, "psycopg", driver) with pytest.raises( RuntimeError, @@ -198,6 +192,7 @@ def test_skipped_progress_upsert_without_stored_row_fails_closed( "request_counts": {"total": 1, "completed": 1, "failed": 0}, }, observation_order=2, + postgres_driver=driver, ) assert driver.commits == 0 From 3b69e78d0945af116e92ef168100560130bf5eb8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:41:35 +0900 Subject: [PATCH 260/338] test(postgres): inject metadata lifecycle driver port --- tests/test_remote_batch_metadata_contract.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/test_remote_batch_metadata_contract.py b/tests/test_remote_batch_metadata_contract.py index 3550feff..2d8861d3 100644 --- a/tests/test_remote_batch_metadata_contract.py +++ b/tests/test_remote_batch_metadata_contract.py @@ -30,6 +30,10 @@ def execute(self, sql: str, params: Any = None) -> None: """Record one SQL execution for deterministic trust-boundary assertions.""" self.driver.executions.append((sql, params)) + def row_count(self) -> int: + """Report the successful lifecycle UPSERT used by these contract tests.""" + return 1 + class _MetadataConnection: """Expose the connection operations used by lifecycle persistence.""" @@ -53,7 +57,7 @@ def commit(self) -> None: class _MetadataPsycopg: - """Minimal psycopg replacement for provider metadata contract tests.""" + """Minimal driver port for provider metadata contract tests.""" def __init__(self) -> None: self.executions: list[tuple[str, Any]] = [] @@ -124,18 +128,17 @@ def _metadata_credentials(_alias: str) -> GatewayCredentials: ids=("nul-value", "nul-key", "nested-nul-value"), ) def test_postgresql_incompatible_nul_metadata_normalizes_to_empty_object( - monkeypatch: pytest.MonkeyPatch, provider_metadata: dict[str, Any], ) -> None: """NUL-bearing JSON metadata must fail closed before the jsonb parameter.""" driver = _MetadataPsycopg() - monkeypatch.setattr(db, "psycopg", driver) snapshot = db.persist_remote_batch_state( "postgresql://example", "primary", {"id": "batch-1", "metadata": provider_metadata}, observation_order=22, + postgres_driver=driver, ) assert snapshot["provider_metadata"] == {} @@ -148,12 +151,9 @@ def test_postgresql_incompatible_nul_metadata_normalizes_to_empty_object( assert driver.commits == 1 -def test_postgresql_safe_metadata_retains_json_scalars_and_literal_escape( - monkeypatch: pytest.MonkeyPatch, -) -> None: +def test_postgresql_safe_metadata_retains_json_scalars_and_literal_escape() -> None: """Safe nested JSON and a literal backslash escape must remain unchanged.""" driver = _MetadataPsycopg() - monkeypatch.setattr(db, "psycopg", driver) provider_metadata = { "empty_object": {}, "empty_values": [], @@ -166,6 +166,7 @@ def test_postgresql_safe_metadata_retains_json_scalars_and_literal_escape( "primary", {"id": "batch-2", "metadata": provider_metadata}, observation_order=23, + postgres_driver=driver, ) assert snapshot["provider_metadata"] == provider_metadata From 66558c22a10e6c58591ab5992af8b2ee9b7e76d2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:42:25 +0900 Subject: [PATCH 261/338] test(postgres): inject tenant lifecycle driver port --- tests/test_tenant_lifecycle_persistence.py | 63 +++++++++++----------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/tests/test_tenant_lifecycle_persistence.py b/tests/test_tenant_lifecycle_persistence.py index 6c5c0063..d027a584 100644 --- a/tests/test_tenant_lifecycle_persistence.py +++ b/tests/test_tenant_lifecycle_persistence.py @@ -36,6 +36,10 @@ def fetchone(self) -> Any: return None return self.driver.fetchone_rows.pop(0) + def row_count(self) -> int: + """Report one affected row for successful lifecycle writes.""" + return 1 + class _Connection: """Expose a cursor and commit counter for the fake driver.""" @@ -61,7 +65,7 @@ def commit(self) -> None: class _Psycopg: - """Minimal psycopg replacement for deterministic database contracts.""" + """Minimal driver port for deterministic database contracts.""" def __init__(self, fetchone_rows: list[Any] | None = None) -> None: self.executions: list[tuple[str, Any]] = [] @@ -87,12 +91,9 @@ def _provider_batch(status: str = "in_progress") -> dict[str, Any]: } -def test_standalone_persistence_sets_transaction_scope_before_upsert( - monkeypatch: pytest.MonkeyPatch, -) -> None: +def test_standalone_persistence_sets_transaction_scope_before_upsert() -> None: """Legacy persistence uses explicit standalone scope under the RLS policy.""" driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) observed = datetime(2026, 8, 5, 9, 0, tzinfo=timezone.utc) snapshot = db.persist_remote_batch_state( @@ -101,6 +102,7 @@ def test_standalone_persistence_sets_transaction_scope_before_upsert( _provider_batch(), 11, observed_at=observed, + postgres_driver=driver, ) assert "tenant_scope" not in snapshot @@ -119,12 +121,9 @@ def test_standalone_persistence_sets_transaction_scope_before_upsert( assert driver.commits == 1 -def test_explicit_tenants_do_not_share_the_business_identity( - monkeypatch: pytest.MonkeyPatch, -) -> None: +def test_explicit_tenants_do_not_share_the_business_identity() -> None: """Identical provider identifiers are independently bound to trusted tenants.""" driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) first = db.persist_tenant_remote_batch_state( "postgresql://tenant-test", @@ -132,6 +131,7 @@ def test_explicit_tenants_do_not_share_the_business_identity( "primary", _provider_batch("in_progress"), 21, + postgres_driver=driver, ) second = db.persist_tenant_remote_batch_state( "postgresql://tenant-test", @@ -139,6 +139,7 @@ def test_explicit_tenants_do_not_share_the_business_identity( "primary", _provider_batch("completed"), 22, + postgres_driver=driver, ) assert first["tenant_scope"] == "tenant-a" @@ -160,12 +161,9 @@ def test_explicit_tenants_do_not_share_the_business_identity( assert driver.commits == 2 -def test_invalid_tenant_scope_fails_before_database_access( - monkeypatch: pytest.MonkeyPatch, -) -> None: +def test_invalid_tenant_scope_fails_before_database_access() -> None: """A malformed tenant scope cannot reach a database connection or SQL sink.""" driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) with pytest.raises(ValidationError) as exc_info: db.persist_tenant_remote_batch_state( @@ -174,6 +172,7 @@ def test_invalid_tenant_scope_fails_before_database_access( "primary", _provider_batch(), 31, + postgres_driver=driver, ) assert exc_info.value.details["field"] == "tenant_scope" @@ -181,9 +180,7 @@ def test_invalid_tenant_scope_fails_before_database_access( assert driver.executions == [] -def test_tenant_scoped_read_sets_context_and_binds_complete_identity( - monkeypatch: pytest.MonkeyPatch, -) -> None: +def test_tenant_scoped_read_sets_context_and_binds_complete_identity() -> None: """Lifecycle reads establish tenant context before selecting one exact row.""" first_seen = datetime(2026, 8, 5, 9, 0, tzinfo=timezone.utc) last_seen = datetime(2026, 8, 5, 9, 5, tzinfo=timezone.utc) @@ -210,13 +207,13 @@ def test_tenant_scoped_read_sets_context_and_binds_complete_identity( ) ] ) - monkeypatch.setattr(db, "psycopg", driver) state = db.get_tenant_remote_batch_state( "postgresql://tenant-test", "tenant-a", "primary", "batch-shared", + postgres_driver=driver, ) assert driver.executions[0][1] == ("tenant-a",) @@ -248,12 +245,9 @@ def test_tenant_scoped_read_sets_context_and_binds_complete_identity( assert driver.commits == 0 -def test_tenant_scoped_read_returns_none_only_for_a_missing_row( - monkeypatch: pytest.MonkeyPatch, -) -> None: +def test_tenant_scoped_read_returns_none_only_for_a_missing_row() -> None: """A valid scoped query may report absence without weakening validation.""" driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) assert ( db.get_tenant_remote_batch_state( @@ -261,18 +255,16 @@ def test_tenant_scoped_read_returns_none_only_for_a_missing_row( "tenant-a", "primary", "batch-missing", + postgres_driver=driver, ) is None ) assert len(driver.executions) == 2 -def test_tenant_scoped_read_rejects_malformed_database_rows( - monkeypatch: pytest.MonkeyPatch, -) -> None: +def test_tenant_scoped_read_rejects_malformed_database_rows() -> None: """A partial row cannot become an ambiguous lifecycle projection.""" driver = _Psycopg(fetchone_rows=[("tenant-a", "primary")]) - monkeypatch.setattr(db, "psycopg", driver) with pytest.raises(RuntimeError, match="invalid row"): db.get_tenant_remote_batch_state( @@ -280,6 +272,7 @@ def test_tenant_scoped_read_rejects_malformed_database_rows( "tenant-a", "primary", "batch-shared", + postgres_driver=driver, ) @@ -287,10 +280,13 @@ def test_standalone_read_delegates_to_explicit_default_scope( monkeypatch: pytest.MonkeyPatch, ) -> None: """Single-tenant callers use the same RLS-safe read path as tenant clients.""" - captured: list[tuple[Any, ...]] = [] + captured: list[tuple[tuple[Any, ...], object | None]] = [] - def fake_get(*args: Any) -> None: - captured.append(args) + def fake_get( + *args: Any, + postgres_driver: object | None = None, + ) -> None: + captured.append((args, postgres_driver)) return None monkeypatch.setattr(db, "get_tenant_remote_batch_state", fake_get) @@ -305,9 +301,12 @@ def fake_get(*args: Any) -> None: ) assert captured == [ ( - "postgresql://tenant-test", - "standalone", - "primary", - "batch-shared", + ( + "postgresql://tenant-test", + "standalone", + "primary", + "batch-shared", + ), + None, ) ] From b5fcd77e6fab06b861d862fa6631c1f69d7abf79 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:43:14 +0900 Subject: [PATCH 262/338] test(postgres): inject virtual payload driver port --- tests/test_virtual_payload_integrity.py | 30 ++++++++++++++----------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/tests/test_virtual_payload_integrity.py b/tests/test_virtual_payload_integrity.py index eaaa060a..f8454108 100644 --- a/tests/test_virtual_payload_integrity.py +++ b/tests/test_virtual_payload_integrity.py @@ -48,7 +48,7 @@ def cursor(self) -> _Cursor: class _Psycopg: - """Psycopg double returning one configured JSONB value.""" + """Driver-port double returning one configured JSONB value.""" def __init__(self, content: Any) -> None: self.row = (content,) @@ -88,34 +88,38 @@ def connect(self, dsn: str) -> _Connection: ], ) def test_load_virtual_payload_rejects_malformed_persisted_state( - monkeypatch: pytest.MonkeyPatch, content: Any, ) -> None: """Malformed package-owned JSONB must fail closed instead of being coerced.""" - monkeypatch.setattr(db, "psycopg", _Psycopg(content)) + driver = _Psycopg(content) with pytest.raises(db.VirtualPayloadIntegrityError) as captured: - db.load_virtual_payload("postgresql://example", "file-1") + db.load_virtual_payload( + "postgresql://example", + "file-1", + postgres_driver=driver, + ) assert str(captured.value) == "Stored virtual payload failed integrity validation" assert captured.value.__cause__ is None assert captured.value.__context__ is None -def test_load_virtual_payload_preserves_valid_multiline_jsonl( - monkeypatch: pytest.MonkeyPatch, -) -> None: +def test_load_virtual_payload_preserves_valid_multiline_jsonl() -> None: """Valid canonical payloads retain exact persisted UTF-8 text and framing.""" payload = '{"custom_id":"r1","score":1.5,"body":{"input":"one"}}\n' \ '{"custom_id":"r2","body":{"input":"two"}}\n' - monkeypatch.setattr( - db, - "psycopg", - _Psycopg({"text": payload, "line_count": 2}), + driver = _Psycopg({"text": payload, "line_count": 2}) + + assert ( + db.load_virtual_payload( + "postgresql://example", + "file-1", + postgres_driver=driver, + ) + == payload ) - assert db.load_virtual_payload("postgresql://example", "file-1") == payload - def test_upload_validates_local_payload_before_credential_resolution( monkeypatch: pytest.MonkeyPatch, From e722f1cd99d2e4f5f0121c52f4cc9b9066316acb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 17:59:41 +0900 Subject: [PATCH 263/338] test(checkpoint): use retained driver selector seam --- tests/test_checkpoint_store.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_checkpoint_store.py b/tests/test_checkpoint_store.py index 4da6ffd8..41ea1912 100644 --- a/tests/test_checkpoint_store.py +++ b/tests/test_checkpoint_store.py @@ -193,10 +193,10 @@ def __init__(self) -> None: @pytest.fixture def database(monkeypatch: pytest.MonkeyPatch) -> FakeDatabase: - """Install one deterministic psycopg replacement for each test.""" + """Install one deterministic retained driver selector for each test.""" fake_database = FakeDatabase() - monkeypatch.setattr(checkpoint_store, "psycopg", FakePsycopg(fake_database)) - monkeypatch.setattr(checkpoint_store, "_require_psycopg", lambda: None) + driver = FakePsycopg(fake_database) + monkeypatch.setattr(checkpoint_store, "retained_postgres_driver", lambda: driver) return fake_database @@ -562,4 +562,4 @@ def test_apply_schema_uses_packaged_default( migration.write_text("SELECT 2;", encoding="utf-8") monkeypatch.setattr(checkpoint_store, "MIGRATION_PATH", migration) apply_result_checkpoint_schema("postgresql://unit") - assert database.calls[-1] == ("SELECT 2;", ()) + assert database.calls[-1] == ("SELECT 2;", ()) \ No newline at end of file From ddc6d1e467713622b3bd8e55540e923241abd246 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 18:01:29 +0900 Subject: [PATCH 264/338] test(postgres): use retained lifecycle driver selector --- tests/test_remote_batch_lifecycle.py | 34 ++++++++++++++++------------ 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/tests/test_remote_batch_lifecycle.py b/tests/test_remote_batch_lifecycle.py index 1e76bf9a..52170e30 100644 --- a/tests/test_remote_batch_lifecycle.py +++ b/tests/test_remote_batch_lifecycle.py @@ -59,7 +59,7 @@ def commit(self) -> None: class _Psycopg: - """Minimal psycopg replacement used by the database helper tests.""" + """Minimal PostgreSQL driver replacement used by database helper tests.""" def __init__(self, fetchone_rows: list[Any] | None = None) -> None: self.executions: list[tuple[str, Any]] = [] @@ -156,7 +156,7 @@ def test_reserve_remote_batch_observation_order_uses_database_sequence( ) -> None: """The lifecycle ticket comes from the shared PostgreSQL sequence.""" driver = _Psycopg(fetchone_rows=[(41,)]) - monkeypatch.setattr(db, "psycopg", driver) + monkeypatch.setattr(db, "retained_postgres_driver", lambda: driver) order = db.reserve_remote_batch_observation_order("postgresql://x") @@ -177,7 +177,8 @@ def test_reserve_remote_batch_observation_order_rejects_invalid_rows( ) -> None: """An invalid sequence result cannot become a lifecycle order.""" rows = [] if row is None else [row] - monkeypatch.setattr(db, "psycopg", _Psycopg(fetchone_rows=rows)) + driver = _Psycopg(fetchone_rows=rows) + monkeypatch.setattr(db, "retained_postgres_driver", lambda: driver) with pytest.raises(RuntimeError, match="invalid order"): db.reserve_remote_batch_observation_order("postgresql://x") @@ -188,7 +189,7 @@ def test_persist_remote_batch_state_upserts_curated_terminal_snapshot( ) -> None: """A terminal provider observation is stored without arbitrary response data.""" driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) + monkeypatch.setattr(db, "retained_postgres_driver", lambda: driver) observed = datetime(2026, 8, 4, 9, 0, tzinfo=timezone.utc) snapshot = db.persist_remote_batch_state( "postgresql://x", @@ -242,7 +243,7 @@ def test_persist_remote_batch_state_normalizes_untrusted_optional_fields( ) -> None: """Invalid optional provider values become deterministic safe defaults.""" driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) + monkeypatch.setattr(db, "retained_postgres_driver", lambda: driver) snapshot = db.persist_remote_batch_state( "postgresql://x", " edge ", @@ -289,7 +290,7 @@ def test_persist_remote_batch_state_normalizes_non_json_metadata( ) -> None: """Non-JSON provider metadata becomes the canonical empty object.""" driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) + monkeypatch.setattr(db, "retained_postgres_driver", lambda: driver) snapshot = db.persist_remote_batch_state( "postgresql://x", "primary", @@ -305,7 +306,7 @@ def test_persist_remote_batch_state_normalizes_cyclic_metadata( ) -> None: """A cyclic metadata graph cannot escape the JSON trust boundary.""" driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) + monkeypatch.setattr(db, "retained_postgres_driver", lambda: driver) metadata: dict[str, Any] = {} metadata["self"] = metadata @@ -325,7 +326,7 @@ def test_persist_remote_batch_state_bounds_metadata_bytes( ) -> None: """Excessive canonical metadata is discarded before the database write.""" driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) + monkeypatch.setattr(db, "retained_postgres_driver", lambda: driver) snapshot = db.persist_remote_batch_state( "postgresql://x", "primary", @@ -342,7 +343,8 @@ def test_persist_remote_batch_state_rejects_invalid_observation_order( observation_order: Any, ) -> None: """Only positive non-boolean integer lifecycle orders are accepted.""" - monkeypatch.setattr(db, "psycopg", _Psycopg()) + driver = _Psycopg() + monkeypatch.setattr(db, "retained_postgres_driver", lambda: driver) with pytest.raises(ValueError, match="observation_order"): db.persist_remote_batch_state( "postgresql://x", @@ -358,7 +360,8 @@ def test_persist_remote_batch_state_rejects_invalid_endpoint_alias( endpoint_alias: Any, ) -> None: """Lifecycle identities require a non-empty textual endpoint alias.""" - monkeypatch.setattr(db, "psycopg", _Psycopg()) + driver = _Psycopg() + monkeypatch.setattr(db, "retained_postgres_driver", lambda: driver) with pytest.raises(ValueError, match="endpoint_alias"): db.persist_remote_batch_state( "postgresql://x", @@ -374,7 +377,8 @@ def test_persist_remote_batch_state_rejects_non_object_payload( provider_batch: Any, ) -> None: """Provider lifecycle payloads must be mapping objects.""" - monkeypatch.setattr(db, "psycopg", _Psycopg()) + driver = _Psycopg() + monkeypatch.setattr(db, "retained_postgres_driver", lambda: driver) with pytest.raises(ValueError, match="provider_batch"): db.persist_remote_batch_state( "postgresql://x", @@ -390,7 +394,8 @@ def test_persist_remote_batch_state_rejects_missing_remote_id( remote_id: Any, ) -> None: """A durable row cannot be written without a provider batch identifier.""" - monkeypatch.setattr(db, "psycopg", _Psycopg()) + driver = _Psycopg() + monkeypatch.setattr(db, "retained_postgres_driver", lambda: driver) with pytest.raises(ValueError, match="provider batch id"): db.persist_remote_batch_state( "postgresql://x", @@ -404,7 +409,8 @@ def test_persist_remote_batch_state_requires_aware_observation_time( monkeypatch: pytest.MonkeyPatch, ) -> None: """Audit timestamps must be timezone-aware to remain unambiguous.""" - monkeypatch.setattr(db, "psycopg", _Psycopg()) + driver = _Psycopg() + monkeypatch.setattr(db, "retained_postgres_driver", lambda: driver) with pytest.raises(ValueError, match="timezone-aware"): db.persist_remote_batch_state( "postgresql://x", @@ -706,4 +712,4 @@ def recorder( release_first.set() await earlier - assert stored == {"observation_order": 2, "status": "completed"} + assert stored == {"observation_order": 2, "status": "completed"} \ No newline at end of file From d6ddbe76e2cae1034c77afc2b4c8b82bdd59a002 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 18:03:20 +0900 Subject: [PATCH 265/338] test(postgres): use retained lifecycle state driver selector --- tests/test_remote_batch_state_contracts.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_remote_batch_state_contracts.py b/tests/test_remote_batch_state_contracts.py index b2f1132c..363374f5 100644 --- a/tests/test_remote_batch_state_contracts.py +++ b/tests/test_remote_batch_state_contracts.py @@ -55,7 +55,7 @@ def commit(self) -> None: class _Psycopg: - """Minimal psycopg replacement for deterministic SQL contract tests.""" + """Minimal PostgreSQL driver replacement for deterministic SQL contract tests.""" def __init__(self) -> None: self.executions: list[tuple[str, Any]] = [] @@ -124,7 +124,7 @@ def test_sparse_observations_cannot_reduce_persisted_request_counts( ) -> None: """Newer sparse polls or cancellations must not erase known progress counts.""" driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) + monkeypatch.setattr(db, "retained_postgres_driver", lambda: driver) db.persist_remote_batch_state( "postgresql://example", @@ -187,7 +187,7 @@ def test_persistence_rejects_oversized_provider_id_before_database_access( ) -> None: """Unsupported provider IDs cannot reach PostgreSQL or its CHECK constraint.""" driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) + monkeypatch.setattr(db, "retained_postgres_driver", lambda: driver) with pytest.raises(ValueError, match="remote_batch_id"): db.persist_remote_batch_state( @@ -206,7 +206,7 @@ def test_persistence_rejects_nul_alias_before_database_access( ) -> None: """PostgreSQL-incompatible NUL aliases fail before opening a connection.""" driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) + monkeypatch.setattr(db, "retained_postgres_driver", lambda: driver) with pytest.raises(ValueError, match="endpoint_alias"): db.persist_remote_batch_state( @@ -306,7 +306,7 @@ def test_operator_docs_define_current_state_and_tenant_trust_boundaries() -> Non """Lifecycle documentation must bound audit and tenant assurances.""" documentation = " ".join( ( - Path(__file__).parents[1] / "docs" / "remote-batch-lifecycle.md" + Path(__file__).parents[1] / "docs" / "remote-batch-lifecycle.md" ).read_text(encoding="utf-8").split() ) @@ -334,7 +334,7 @@ def test_remote_field_contract_rejects_invalid_optional_ids_before_database_acce ) -> None: """Every present provider file identifier is validated before PostgreSQL.""" driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) + monkeypatch.setattr(db, "retained_postgres_driver", lambda: driver) with pytest.raises(ValueError, match=field): db.persist_remote_batch_state( @@ -433,7 +433,7 @@ def test_remote_field_contract_rejects_nul_lifecycle_text_before_database_access ) -> None: """NUL-bearing lifecycle status fails before PostgreSQL persistence.""" driver = _Psycopg() - monkeypatch.setattr(db, "psycopg", driver) + monkeypatch.setattr(db, "retained_postgres_driver", lambda: driver) with pytest.raises(ValueError, match="batch_status"): db.persist_remote_batch_state( @@ -466,4 +466,4 @@ def test_remote_field_contract_adds_database_checks() -> None: assert ( f"{field_name} TEXT CHECK ( {field_name} IS NULL OR " f"{field_name} ~ {identifier_pattern} )" - ) in schema, f"missing identifier CHECK for {field_name}" + ) in schema, f"missing identifier CHECK for {field_name}" \ No newline at end of file From 1a654d72d2b5d294316fe19cfb99ddda81ca82cd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 18:11:15 +0900 Subject: [PATCH 266/338] test(postgres): model lifecycle write row counts --- tests/test_remote_batch_lifecycle.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/test_remote_batch_lifecycle.py b/tests/test_remote_batch_lifecycle.py index 52170e30..cbe4dbda 100644 --- a/tests/test_remote_batch_lifecycle.py +++ b/tests/test_remote_batch_lifecycle.py @@ -38,6 +38,10 @@ def fetchone(self): return None return self.driver.fetchone_rows.pop(0) + def row_count(self) -> int: + """Report the successful lifecycle write represented by this test double.""" + return 1 + class _Connection: """Expose a cursor and commit counter for the fake driver.""" @@ -163,7 +167,11 @@ def test_reserve_remote_batch_observation_order_uses_database_sequence( assert order == 41 assert driver.connections == ["postgresql://x"] assert driver.executions == [ - ("SELECT nextval('llm_remote_batch_observation_sequence')", None) + ( + "SELECT set_config('pg_llm_batch.tenant_scope', %s, true)", + ("standalone",), + ), + ("SELECT nextval('llm_remote_batch_observation_sequence')", None), ] @@ -712,4 +720,4 @@ def recorder( release_first.set() await earlier - assert stored == {"observation_order": 2, "status": "completed"} \ No newline at end of file + assert stored == {"observation_order": 2, "status": "completed"} From c298b4007bef25ede1925afe73cd1d9f557a1ab0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 18:13:01 +0900 Subject: [PATCH 267/338] fix(ci): align candidate health with runtime capabilities --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 26087006..18bc1ed6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -181,7 +181,7 @@ jobs: pg_isready -U pgllm -d pgllm >/dev/null 2>&1; then ready="$(docker exec "$PG8000_CANDIDATE_CONTAINER" \ psql -U pgllm -d pgllm -tAc \ - "SELECT bool_and(is_ready) FROM pg_llm_batch_health_check() WHERE component IN ('database','pg_tiktoken','com_config')" \ + "SELECT bool_and(is_ready) FROM pg_llm_batch_health_check() WHERE component IN ('database','com_config')" \ 2>/dev/null || true)" if [ "$ready" = "t" ]; then exit 0 @@ -202,4 +202,4 @@ jobs: fi if [ -n "${PG8000_CANDIDATE_PASSWORD_FILE:-}" ]; then rm -f "$PG8000_CANDIDATE_PASSWORD_FILE" - fi + fi \ No newline at end of file From d9b5c18a0ef832b56e3b8c89b51a93056c1f24ad Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 18:13:34 +0900 Subject: [PATCH 268/338] test(postgres): model lifecycle state row counts --- tests/test_remote_batch_state_contracts.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_remote_batch_state_contracts.py b/tests/test_remote_batch_state_contracts.py index 363374f5..a006dc6d 100644 --- a/tests/test_remote_batch_state_contracts.py +++ b/tests/test_remote_batch_state_contracts.py @@ -32,6 +32,10 @@ def execute(self, sql: str, params: Any = None) -> None: """Record one SQL execution and its bound parameters.""" self.driver.executions.append((sql, params)) + def row_count(self) -> int: + """Report the successful lifecycle write represented by this test double.""" + return 1 + class _Connection: """Expose the small connection surface used by the lifecycle helper.""" @@ -466,4 +470,4 @@ def test_remote_field_contract_adds_database_checks() -> None: assert ( f"{field_name} TEXT CHECK ( {field_name} IS NULL OR " f"{field_name} ~ {identifier_pattern} )" - ) in schema, f"missing identifier CHECK for {field_name}" \ No newline at end of file + ) in schema, f"missing identifier CHECK for {field_name}" From b5ade45a9717310b3b7270b80b4fb5d687cc518d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 18:14:18 +0900 Subject: [PATCH 269/338] test(ci): bind candidate health to selected runtime --- tests/test_workflow_contracts.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/test_workflow_contracts.py b/tests/test_workflow_contracts.py index 445aee8d..d42f5e67 100644 --- a/tests/test_workflow_contracts.py +++ b/tests/test_workflow_contracts.py @@ -180,6 +180,25 @@ def test_ci_pg8000_candidate_parity_is_immutable_and_queue_conservative() -> Non assert '"pg8000' not in project.casefold() +def test_ci_pg8000_candidate_health_matches_selected_runtime_capabilities() -> None: + """Candidate parity must not wait for an extension disabled by its image.""" + workflow = _read(".github/workflows/ci.yml") + dockerfile = _read("docker/postgres/Dockerfile") + candidate_health_step = next( + step + for steps in _workflow_job_steps(workflow) + for step in steps + if _step_top_level_field(step, "name") + == "Wait for candidate PostgreSQL health contract" + ) + + assert "docker build --tag pg-llm-batch-postgres:ci docker/postgres" in workflow + assert "FROM postgres-base AS runtime" in dockerfile + assert "ENV ENABLE_TIKTOKEN=0" in dockerfile + assert "component IN ('database','com_config')" in candidate_health_step + assert "pg_tiktoken" not in candidate_health_step + + def test_ci_pg8000_candidate_pins_and_hashes_full_dependency_closure() -> None: """Candidate proof must not resolve mutable transitive wheels at install time.""" workflow = _read(".github/workflows/ci.yml") @@ -300,4 +319,4 @@ def test_pyproject_declares_hard_quality_thresholds() -> None: assert '[tool.coverage.run]\nsource = ["pg_llm_batch"]' in config assert '[tool.coverage.report]\nfail_under = 100\nshow_missing = true' in config - assert '[tool.interrogate]\nexclude = ["tests"]\nfail-under = 100' in config + assert '[tool.interrogate]\nexclude = ["tests"]\nfail-under = 100' in config \ No newline at end of file From 94509d6448b5907606d5315cc03c92519349895f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 18:18:08 +0900 Subject: [PATCH 270/338] test(postgres): cover candidate owner-thread operations --- ...pg8000_driver_candidate_thread_affinity.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test_pg8000_driver_candidate_thread_affinity.py b/tests/test_pg8000_driver_candidate_thread_affinity.py index 0f121dec..028d9c1c 100644 --- a/tests/test_pg8000_driver_candidate_thread_affinity.py +++ b/tests/test_pg8000_driver_candidate_thread_affinity.py @@ -131,3 +131,29 @@ def test_candidate_cursor_rejects_cross_thread_driver_access(operation: str) -> _run_on_worker(callbacks[operation]) assert raw.calls == 0 + + +def test_candidate_owner_thread_exercises_complete_connection_and_cursor_surface() -> None: + """The owner thread may use every admitted cursor and transaction capability.""" + raw_connection = _RawConnection() + connection = Pg8000ThreadAffineCandidateConnectionAdapter(raw_connection) + + with connection as entered: + assert entered is connection + cursor = entered.cursor() + with cursor as active: + assert active is cursor + assert active.execute("SELECT %s", (1,)) is active + assert active.executemany("SELECT %s", [(1,)]) is active + assert active.fetchone() == (1,) + assert active.fetchmany(1) == [(1,)] + assert active.fetchall() == [(1,)] + assert active.row_count() == 1 + entered.commit() + entered.rollback() + entered.set_autocommit(True) + assert entered.is_closed() is False + + connection.close() + assert connection.is_closed() is True + assert raw_connection.calls > 0 From c033bf652c2a5bbc3e2fbaf4b8d189cf464fcbbe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 18:21:21 +0900 Subject: [PATCH 271/338] test(postgres): cover driver edge contracts --- tests/test_postgres_driver_edge_coverage.py | 288 ++++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 tests/test_postgres_driver_edge_coverage.py diff --git a/tests/test_postgres_driver_edge_coverage.py b/tests/test_postgres_driver_edge_coverage.py new file mode 100644 index 00000000..00d93f69 --- /dev/null +++ b/tests/test_postgres_driver_edge_coverage.py @@ -0,0 +1,288 @@ +"""Edge contracts for retained and candidate PostgreSQL driver boundaries.""" + +from __future__ import annotations + +import builtins +from types import ModuleType +from typing import Any + +import pytest +from psycopg import ProgrammingError + +import pg_llm_batch.postgres_driver_runtime as runtime +import pg_llm_batch.psycopg_driver_adapter as psycopg_adapter +from pg_llm_batch.pg8000_candidate_driver_port import ( + Pg8000CandidateDriverAdapter, + Pg8000CandidateInvalidConninfoError, + _copy_parameter_mapping, + _parse_port, + _parse_postgresql_uri, + _read_keyword_value, + _validate_parameter_mapping, +) +from pg_llm_batch.pg8000_driver_candidate_errors import ( + Pg8000CandidateErrorEvidenceError, + is_pg8000_candidate_undefined_function, +) +from pg_llm_batch.psycopg_driver_adapter import ( + PsycopgConnectionAdapter, + PsycopgCursorAdapter, + PsycopgDriverAdapter, + PsycopgDriverAdapterError, + PsycopgInvalidConninfoError, +) + + +def _candidate_module() -> ModuleType: + """Build one admitted-shaped pg8000 DB-API module without importing pg8000.""" + module = ModuleType("candidate_pg8000") + + class ProgrammingErrorCandidate(Exception): + pass + + def connect(**_kwargs: object) -> _RawCandidateConnection: + return _RawCandidateConnection() + + module.apilevel = "2.0" + module.threadsafety = 1 + module.paramstyle = "format" + module.ProgrammingError = ProgrammingErrorCandidate + module.connect = connect + return module + + +class _RawCandidateCursor: + """Minimal raw cursor for candidate connection construction.""" + + rowcount = 0 + + def execute(self, _query: str, _params: object | None = None) -> None: + return None + + def executemany(self, _query: str, _params: object) -> None: + return None + + def fetchone(self) -> None: + return None + + def fetchmany(self, _size: int) -> list[object]: + return [] + + def fetchall(self) -> list[object]: + return [] + + def close(self) -> None: + return None + + +class _RawCandidateConnection: + """Minimal raw pg8000-shaped connection for candidate construction.""" + + def __init__(self) -> None: + self.autocommit = False + + def cursor(self) -> _RawCandidateCursor: + return _RawCandidateCursor() + + def commit(self) -> None: + return None + + def rollback(self) -> None: + return None + + def close(self) -> None: + return None + + +@pytest.mark.parametrize( + "dsn", + [ + "postgresql://user%FF@localhost/db", + "postgresql://user@localhost", + "postgresql://user@localhost/db/extra", + "postgresql://user@[::1/db", + ], +) +def test_candidate_uri_rejects_unrepresentable_or_malformed_authority(dsn: str) -> None: + """Malformed URI authority fails inside the bounded candidate parser.""" + with pytest.raises(Pg8000CandidateInvalidConninfoError): + _parse_postgresql_uri(dsn) + + +def test_candidate_uri_accepts_postgres_alias_empty_password_and_default_port() -> None: + """The admitted alias preserves an explicit empty password and default port.""" + assert _parse_postgresql_uri("postgres://user:@localhost/db") == { + "user": "user", + "password": "", + "host": "localhost", + "port": "5432", + "dbname": "db", + } + + +@pytest.mark.parametrize("value", [True, 0, 65536, "", "abc"]) +def test_candidate_port_rejects_non_tcp_values(value: object) -> None: + """Port normalization accepts neither booleans nor non-TCP values.""" + with pytest.raises(Pg8000CandidateInvalidConninfoError): + _parse_port(value) + + +def test_keyword_reader_covers_empty_and_escape_failure_boundaries() -> None: + """Keyword parsing handles exact empty input and fails closed on dangling escapes.""" + assert _read_keyword_value("", 0) == ("", 0) + with pytest.raises(Pg8000CandidateInvalidConninfoError): + _read_keyword_value("abc\\", 0) + with pytest.raises(Pg8000CandidateInvalidConninfoError): + _read_keyword_value("abc'", 0) + with pytest.raises(Pg8000CandidateInvalidConninfoError): + _read_keyword_value("'abc'x", 0) + + +def test_candidate_mapping_rejects_non_mapping_missing_and_empty_identity() -> None: + """Candidate parameter authority must be a complete built-in textual mapping.""" + with pytest.raises(Pg8000CandidateInvalidConninfoError): + _copy_parameter_mapping([]) # type: ignore[arg-type] + with pytest.raises(Pg8000CandidateInvalidConninfoError): + _validate_parameter_mapping({"user": "u", "host": "h"}) + with pytest.raises(Pg8000CandidateInvalidConninfoError): + _validate_parameter_mapping({"user": "", "host": "h", "dbname": "d"}) + + +def test_candidate_adapter_covers_passwordless_connect_ipv6_render_and_classifiers() -> None: + """Owner-thread candidate use preserves passwordless selectors and IPv6 rendering.""" + module = _candidate_module() + adapter = Pg8000CandidateDriverAdapter(module) + connection = adapter.connect("postgresql://user@localhost/db") + assert connection.is_closed() is False + rendered = adapter.make_conninfo( + {"user": "user", "host": "2001:db8::1", "dbname": "db", "port": "5432"} + ) + assert rendered == "postgresql://user@[2001:db8::1]:5432/db" + assert adapter.is_invalid_conninfo(Pg8000CandidateInvalidConninfoError("x")) is True + assert adapter.is_invalid_conninfo(RuntimeError("x")) is False + adapted = adapter.jsonb({"a": 1}) + assert adapted is not None + + +def test_candidate_service_selector_requires_nonempty_explicit_resolver() -> None: + """Service selection never falls back to ambient libpq authority.""" + adapter = Pg8000CandidateDriverAdapter(_candidate_module()) + for dsn in ("service=", "service=prod"): + with pytest.raises(Pg8000CandidateInvalidConninfoError): + adapter.parse_conninfo(dsn) + + +def test_candidate_error_classifier_rejects_shaped_authority_and_payloads() -> None: + """SQLSTATE classification requires the exact admitted module and payload shape.""" + with pytest.raises(Pg8000CandidateErrorEvidenceError): + is_pg8000_candidate_undefined_function(RuntimeError(), dbapi_module=object()) + + module = _candidate_module() + error_type = vars(module)["ProgrammingError"] + assert isinstance(error_type, type) + assert is_pg8000_candidate_undefined_function(error_type("bad"), dbapi_module=module) is False + assert is_pg8000_candidate_undefined_function(error_type({"C": 42883}), dbapi_module=module) is False + assert is_pg8000_candidate_undefined_function(error_type({"C": "42883"}), dbapi_module=module) is True + + +class _RawPsycopgCursor: + """Drive retained adapter edge cases without a database.""" + + def __init__(self) -> None: + self.rowcount: object = 0 + self.fetchone_value: object | None = None + self.fetchmany_value: object = [] + self.fetchall_value: object = [] + + def fetchone(self) -> object | None: + return self.fetchone_value + + def fetchmany(self, _size: int) -> object: + return self.fetchmany_value + + def fetchall(self) -> object: + return self.fetchall_value + + +class _LenFailure: + """Raise while the adapter proves a finite fetch result.""" + + def __len__(self) -> int: + raise TypeError("no finite length") + + +def test_psycopg_cursor_fail_closed_edges() -> None: + """Retained adapter normalizes no-row evidence and rejects malformed driver output.""" + raw = _RawPsycopgCursor() + cursor = PsycopgCursorAdapter(raw) + assert cursor.fetchone() is None + + raw.fetchone_value = object() + with pytest.raises(PsycopgDriverAdapterError, match="result row"): + cursor.fetchone() + + raw.fetchmany_value = _LenFailure() + with pytest.raises(PsycopgDriverAdapterError, match="fetch result"): + cursor.fetchmany(1) + + raw.rowcount = -2 + with pytest.raises(PsycopgDriverAdapterError, match="row count"): + cursor.row_count() + + +class _ClosedShape: + """Expose an invalid non-boolean Psycopg closed-state signal.""" + + closed = 1 + + +def test_psycopg_connection_rejects_non_boolean_closed_state() -> None: + """Closed-state authority cannot rely on integer truthiness.""" + with pytest.raises(PsycopgDriverAdapterError, match="closed state"): + PsycopgConnectionAdapter(_ClosedShape()).is_closed() + + +def test_psycopg_conninfo_wrappers_narrow_programming_errors(monkeypatch: pytest.MonkeyPatch) -> None: + """Only conninfo grammar failures become the neutral invalid-selector category.""" + def fail_parse(_dsn: str) -> dict[str, str]: + raise ProgrammingError("bad") + + def fail_render(**_params: str) -> str: + raise ProgrammingError("bad") + + monkeypatch.setattr(psycopg_adapter, "conninfo_to_dict", fail_parse) + monkeypatch.setattr(psycopg_adapter, "make_conninfo", fail_render) + adapter = PsycopgDriverAdapter() + with pytest.raises(PsycopgInvalidConninfoError): + adapter.parse_conninfo("bad") + with pytest.raises(PsycopgInvalidConninfoError): + adapter.make_conninfo({"host": "bad"}) + + +def test_runtime_selector_distinguishes_missing_psycopg_from_other_import_failures( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Optional-client absence is redacted while unrelated package defects propagate.""" + original_import = builtins.__import__ + + def missing_psycopg(name: str, *args: Any, **kwargs: Any) -> Any: + if name.endswith("psycopg_driver_adapter"): + error = ModuleNotFoundError("missing psycopg") + error.name = "psycopg" + raise error + return original_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", missing_psycopg) + with pytest.raises(runtime.PostgresDriverUnavailableError, match="unavailable"): + runtime.retained_postgres_driver() + + def missing_other(name: str, *args: Any, **kwargs: Any) -> Any: + if name.endswith("psycopg_driver_adapter"): + error = ModuleNotFoundError("missing other") + error.name = "other_dependency" + raise error + return original_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", missing_other) + with pytest.raises(ModuleNotFoundError): + runtime.retained_postgres_driver() From 60a63039d40dae944b3df74165094cdb9ccd82a6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 18:22:06 +0900 Subject: [PATCH 272/338] test(postgres): cover service-file parser edges --- .../test_pg8000_service_file_edge_coverage.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tests/test_pg8000_service_file_edge_coverage.py diff --git a/tests/test_pg8000_service_file_edge_coverage.py b/tests/test_pg8000_service_file_edge_coverage.py new file mode 100644 index 00000000..e5450f52 --- /dev/null +++ b/tests/test_pg8000_service_file_edge_coverage.py @@ -0,0 +1,52 @@ +"""Edge contracts for the explicit pg8000 candidate service-file capability.""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from pg_llm_batch.pg8000_candidate_driver_port import Pg8000CandidateInvalidConninfoError +from pg_llm_batch.pg8000_candidate_service_file import Pg8000CandidateServiceFileResolver + + +def test_service_resolver_requires_path_capability() -> None: + """A shaped string cannot become implicit filesystem authority.""" + with pytest.raises(Pg8000CandidateInvalidConninfoError): + Pg8000CandidateServiceFileResolver("service.conf") # type: ignore[arg-type] + + +@pytest.mark.parametrize( + "content", + [ + "[prod]\nhost=local\x7fhost\nuser=u\ndbname=d\n", + "[prod\nhost=localhost\nuser=u\ndbname=d\n", + "[prod]]\nhost=localhost\nuser=u\ndbname=d\n", + "[other]\nhost=localhost\nuser=u\ndbname=d\n", + ], +) +def test_service_resolver_rejects_malformed_or_missing_target( + tmp_path: Path, + content: str, +) -> None: + """Malformed framing and absent selected stanzas fail without fallback discovery.""" + path = tmp_path / "pg_service.conf" + path.write_text(content, encoding="utf-8") + resolver = Pg8000CandidateServiceFileResolver(path) + + with pytest.raises(Pg8000CandidateInvalidConninfoError): + resolver("prod") + + +def test_service_resolver_skips_comments_blank_lines_and_non_target_values(tmp_path: Path) -> None: + """Only the selected stanza contributes connection authority.""" + path = tmp_path / "pg_service.conf" + path.write_text( + "# comment\n\n[other]\nunsupported=value\n[prod]\nhost=localhost\nuser=u\ndbname=d\n", + encoding="utf-8", + ) + assert Pg8000CandidateServiceFileResolver(path)("prod") == { + "host": "localhost", + "user": "u", + "dbname": "d", + } From be92f9776e71e94b2ae758f56a830e1d36cb4d65 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 18:23:10 +0900 Subject: [PATCH 273/338] test(postgres): cover residual port migration edges --- .../test_misc_postgres_port_edge_coverage.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tests/test_misc_postgres_port_edge_coverage.py diff --git a/tests/test_misc_postgres_port_edge_coverage.py b/tests/test_misc_postgres_port_edge_coverage.py new file mode 100644 index 00000000..0ac95331 --- /dev/null +++ b/tests/test_misc_postgres_port_edge_coverage.py @@ -0,0 +1,64 @@ +"""Residual edge contracts exposed by the PostgreSQL driver-port migration.""" + +from __future__ import annotations + +import threading + +import pytest + +from pg_llm_batch import cli, compose_bootstrap, db +from pg_llm_batch.exceptions import ConfigError +from pg_llm_batch.token_counter import TokenCounter + + +class _NonSelectorFailureDriver: + """Raise a non-conninfo failure so CLI classification cannot swallow it.""" + + def parse_conninfo(self, _dsn: str): + raise RuntimeError("driver defect") + + def is_invalid_conninfo(self, _error: BaseException) -> bool: + return False + + +def test_cli_propagates_non_selector_driver_failure() -> None: + """Unexpected concrete-driver defects remain distinguishable from bad argv.""" + with pytest.raises(RuntimeError, match="driver defect"): + cli.validate_cli_dsn("host=localhost", postgres_driver=_NonSelectorFailureDriver()) + + +class _BootstrapConfigFailureDriver: + """Raise an existing domain configuration error during private DSN assembly.""" + + def parse_conninfo(self, _dsn: str): + raise ConfigError("policy rejected") + + +def test_compose_bootstrap_preserves_existing_config_error() -> None: + """Private DSN assembly must not relabel an established configuration decision.""" + with pytest.raises(ConfigError, match="policy rejected"): + compose_bootstrap.build_private_postgres_dsn( + "host=localhost dbname=batch user=batch", + "secret", + postgres_driver=_BootstrapConfigFailureDriver(), + ) + + +@pytest.mark.parametrize("row_count", [True, -2, "1"]) +def test_driver_neutral_row_count_rejects_non_exact_evidence(row_count: object) -> None: + """Affected-row evidence never relies on truthiness or undocumented sentinels.""" + class Cursor: + def row_count(self) -> object: + return row_count + + assert db._cursor_row_count(Cursor(), None) is None + + +def test_token_counter_fails_closed_when_database_capability_is_already_unavailable() -> None: + """A disabled retained session cannot be retried through an implicit fallback.""" + counter = TokenCounter.__new__(TokenCounter) + counter._pg_available = False + counter._pg_connection_lock = threading.Lock() + + with pytest.raises(RuntimeError, match="requires pg_tiktoken"): + counter.count_tokens("nonempty", "model") From d111b17097a739bb881ec7903d2e586331e03147 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 18:23:32 +0900 Subject: [PATCH 274/338] test(postgres): call private bootstrap boundary exactly --- tests/test_misc_postgres_port_edge_coverage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_misc_postgres_port_edge_coverage.py b/tests/test_misc_postgres_port_edge_coverage.py index 0ac95331..56527528 100644 --- a/tests/test_misc_postgres_port_edge_coverage.py +++ b/tests/test_misc_postgres_port_edge_coverage.py @@ -37,7 +37,7 @@ def parse_conninfo(self, _dsn: str): def test_compose_bootstrap_preserves_existing_config_error() -> None: """Private DSN assembly must not relabel an established configuration decision.""" with pytest.raises(ConfigError, match="policy rejected"): - compose_bootstrap.build_private_postgres_dsn( + compose_bootstrap._build_private_dsn( "host=localhost dbname=batch user=batch", "secret", postgres_driver=_BootstrapConfigFailureDriver(), From deb70576ae0ed53bb1f2479cb56ae1bfe84409ba Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 18:24:32 +0900 Subject: [PATCH 275/338] test(postgres): cover remaining driver authority edges --- tests/test_postgres_driver_remaining_edges.py | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 tests/test_postgres_driver_remaining_edges.py diff --git a/tests/test_postgres_driver_remaining_edges.py b/tests/test_postgres_driver_remaining_edges.py new file mode 100644 index 00000000..7e95785b --- /dev/null +++ b/tests/test_postgres_driver_remaining_edges.py @@ -0,0 +1,113 @@ +"""Remaining authority edges for the commercial PostgreSQL driver migration.""" + +from __future__ import annotations + +from typing import Any + +import pytest + +from pg_llm_batch.pg8000_driver_candidate_adapter import ( + Pg8000CandidateConnectionAdapter, +) +from pg_llm_batch.pg8000_driver_candidate_errors import ( + is_pg8000_candidate_undefined_function, +) +from pg_llm_batch.postgres_driver_candidate import ( + REQUIRED_POSTGRES_DRIVER_CAPABILITIES, + REQUIRED_POSTGRES_DRIVER_PYTHON_VERSIONS, + PostgresDriverCandidateEvidence, + PostgresDriverCandidateEvidenceError, +) +from tests.test_postgres_driver_edge_coverage import _candidate_module + + +class _TransactionConnection: + """Inject transaction and cleanup failures without a database.""" + + def __init__( + self, + *, + commit_error: BaseException | None = None, + rollback_error: BaseException | None = None, + close_error: BaseException | None = None, + ) -> None: + self.commit_error = commit_error + self.rollback_error = rollback_error + self.close_error = close_error + self.autocommit = False + + def cursor(self) -> Any: + raise AssertionError("not used") + + def commit(self) -> None: + if self.commit_error is not None: + raise self.commit_error + + def rollback(self) -> None: + if self.rollback_error is not None: + raise self.rollback_error + + def close(self) -> None: + if self.close_error is not None: + raise self.close_error + + +def test_candidate_exit_preserves_transaction_error_over_cleanup_error() -> None: + """A failed commit remains the primary result if physical close also fails.""" + commit_error = RuntimeError("commit failed") + adapter = Pg8000CandidateConnectionAdapter( + _TransactionConnection( + commit_error=commit_error, + close_error=OSError("close failed"), + ) + ) + + with pytest.raises(RuntimeError, match="commit failed") as caught: + adapter.__exit__(None, None, None) + assert caught.value is commit_error + + +def test_candidate_exit_preserves_application_error_over_cleanup_error() -> None: + """A successful rollback cannot let a later close failure replace caller failure.""" + application_error = ValueError("application failed") + adapter = Pg8000CandidateConnectionAdapter( + _TransactionConnection(close_error=OSError("close failed")) + ) + + with pytest.raises(ValueError, match="application failed") as caught: + adapter.__exit__(ValueError, application_error, None) + assert caught.value is application_error + + +def test_candidate_error_classifier_rejects_wrong_exact_exception_type() -> None: + """A message-compatible exception is never SQLSTATE authority.""" + module = _candidate_module() + assert ( + is_pg8000_candidate_undefined_function( + RuntimeError({"C": "42883"}), + dbapi_module=module, + ) + is False + ) + + +def test_candidate_evidence_rejects_unknown_capability_without_count_overflow() -> None: + """Replacing one required capability with an unknown name hits set authority checks.""" + capabilities = set(REQUIRED_POSTGRES_DRIVER_CAPABILITIES) + capabilities.remove(next(iter(capabilities))) + capabilities.add("unknown_capability") + + with pytest.raises(PostgresDriverCandidateEvidenceError, match="unknown capability"): + PostgresDriverCandidateEvidence( + package_name="candidate-driver", + package_version="1.2.3", + license_spdx="BSD-3-Clause", + license_report_sha256="d" * 64, + python_versions=tuple(sorted(REQUIRED_POSTGRES_DRIVER_PYTHON_VERSIONS)), + source_commit_sha="a" * 40, + artifact_sha256="b" * 64, + vulnerability_report_sha256="c" * 64, + capability_report_sha256="e" * 64, + known_vulnerability_ids=(), + capabilities=frozenset(capabilities), + ) From 5637a85d6c69651c27ec0699b8b6ad08a43d8e94 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 18:24:55 +0900 Subject: [PATCH 276/338] test(postgres): keep edge fixtures self contained --- tests/test_postgres_driver_remaining_edges.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/test_postgres_driver_remaining_edges.py b/tests/test_postgres_driver_remaining_edges.py index 7e95785b..c431774c 100644 --- a/tests/test_postgres_driver_remaining_edges.py +++ b/tests/test_postgres_driver_remaining_edges.py @@ -2,6 +2,7 @@ from __future__ import annotations +from types import ModuleType from typing import Any import pytest @@ -18,7 +19,17 @@ PostgresDriverCandidateEvidence, PostgresDriverCandidateEvidenceError, ) -from tests.test_postgres_driver_edge_coverage import _candidate_module + + +def _candidate_module() -> ModuleType: + """Build exact DB-API error authority without importing the candidate package.""" + module = ModuleType("candidate_pg8000_errors") + + class ProgrammingErrorCandidate(Exception): + pass + + module.ProgrammingError = ProgrammingErrorCandidate + return module class _TransactionConnection: From bfc97db9329cad827a4f37215df694f9ab95884f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 18:32:42 +0900 Subject: [PATCH 277/338] test(postgres): exercise private CLI DSN seam --- tests/test_misc_postgres_port_edge_coverage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_misc_postgres_port_edge_coverage.py b/tests/test_misc_postgres_port_edge_coverage.py index 56527528..e9f8de52 100644 --- a/tests/test_misc_postgres_port_edge_coverage.py +++ b/tests/test_misc_postgres_port_edge_coverage.py @@ -24,7 +24,7 @@ def is_invalid_conninfo(self, _error: BaseException) -> bool: def test_cli_propagates_non_selector_driver_failure() -> None: """Unexpected concrete-driver defects remain distinguishable from bad argv.""" with pytest.raises(RuntimeError, match="driver defect"): - cli.validate_cli_dsn("host=localhost", postgres_driver=_NonSelectorFailureDriver()) + cli._validate_cli_dsn("host=localhost", postgres_driver=_NonSelectorFailureDriver()) class _BootstrapConfigFailureDriver: From 1413df4737b8752e7dd02a0382f88e98c49b6a4e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 18:37:00 +0900 Subject: [PATCH 278/338] test(postgres): close remaining driver coverage edges --- tests/test_postgres_driver_remaining_edges.py | 127 +++++++++++++++++- 1 file changed, 124 insertions(+), 3 deletions(-) diff --git a/tests/test_postgres_driver_remaining_edges.py b/tests/test_postgres_driver_remaining_edges.py index c431774c..f2b5b5cf 100644 --- a/tests/test_postgres_driver_remaining_edges.py +++ b/tests/test_postgres_driver_remaining_edges.py @@ -2,12 +2,21 @@ from __future__ import annotations -from types import ModuleType +from pathlib import Path +from types import ModuleType, SimpleNamespace from typing import Any import pytest +import pg_llm_batch.pg8000_candidate_driver_port as candidate_port +import pg_llm_batch.pg8000_candidate_service_file as candidate_service_file +import pg_llm_batch.psycopg_driver_adapter as psycopg_adapter +from pg_llm_batch.pg8000_candidate_driver_port import ( + Pg8000CandidateDriverAdapter, + Pg8000CandidateInvalidConninfoError, +) from pg_llm_batch.pg8000_driver_candidate_adapter import ( + Pg8000CandidateAdapterError, Pg8000CandidateConnectionAdapter, ) from pg_llm_batch.pg8000_driver_candidate_errors import ( @@ -19,16 +28,25 @@ PostgresDriverCandidateEvidence, PostgresDriverCandidateEvidenceError, ) +from pg_llm_batch.psycopg_driver_adapter import ( + PsycopgCursorAdapter, + PsycopgDriverAdapter, +) -def _candidate_module() -> ModuleType: - """Build exact DB-API error authority without importing the candidate package.""" +def _candidate_module(*, include_connect: bool = False) -> ModuleType: + """Build exact DB-API candidate authority without importing the package.""" module = ModuleType("candidate_pg8000_errors") class ProgrammingErrorCandidate(Exception): pass + module.apilevel = "2.0" + module.paramstyle = "format" + module.threadsafety = 1 module.ProgrammingError = ProgrammingErrorCandidate + if include_connect: + module.connect = lambda **_kwargs: _TransactionConnection() return module @@ -90,6 +108,28 @@ def test_candidate_exit_preserves_application_error_over_cleanup_error() -> None assert caught.value is application_error +def test_candidate_exit_propagates_close_only_failure() -> None: + """Physical close failure remains visible when transaction exit otherwise succeeds.""" + adapter = Pg8000CandidateConnectionAdapter( + _TransactionConnection(close_error=OSError("close failed")) + ) + + with pytest.raises(OSError, match="close failed"): + adapter.__exit__(None, None, None) + + +def test_candidate_exit_propagates_transaction_failure_after_successful_close() -> None: + """A failed commit remains visible when physical cleanup itself succeeds.""" + commit_error = RuntimeError("commit failed") + adapter = Pg8000CandidateConnectionAdapter( + _TransactionConnection(commit_error=commit_error) + ) + + with pytest.raises(RuntimeError, match="commit failed") as caught: + adapter.__exit__(None, None, None) + assert caught.value is commit_error + + def test_candidate_error_classifier_rejects_wrong_exact_exception_type() -> None: """A message-compatible exception is never SQLSTATE authority.""" module = _candidate_module() @@ -102,6 +142,87 @@ def test_candidate_error_classifier_rejects_wrong_exact_exception_type() -> None ) +def test_candidate_error_classifier_rejects_missing_server_payload() -> None: + """The exact candidate exception class still needs one PostgreSQL response payload.""" + module = _candidate_module() + error_type = vars(module)["ProgrammingError"] + assert isinstance(error_type, type) + assert is_pg8000_candidate_undefined_function(error_type(), dbapi_module=module) is False + + +def test_candidate_driver_covers_remaining_fail_closed_selector_edges( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Exercise defensive URI, keyword, construction, and SQLSTATE authority edges.""" + with pytest.raises(Pg8000CandidateInvalidConninfoError): + candidate_port._decode_component("%00") + with pytest.raises(Pg8000CandidateInvalidConninfoError, match="unsupported"): + candidate_port._parse_postgresql_uri("mysql://batch@localhost/db") + with pytest.raises(Pg8000CandidateInvalidConninfoError): + candidate_port._parse_postgresql_uri("postgresql:///db") + with pytest.raises(Pg8000CandidateInvalidConninfoError): + candidate_port._parse_keyword_fields("host=localhost\nuser=batch") + assert candidate_port._parse_keyword_fields( + "host=localhost user=batch dbname=db " + ) == {"host": "localhost", "user": "batch", "dbname": "db"} + + monkeypatch.setattr( + candidate_port, + "urlsplit", + lambda _dsn: SimpleNamespace(scheme="mysql"), + ) + with pytest.raises(Pg8000CandidateInvalidConninfoError, match="unsupported"): + candidate_port._parse_postgresql_uri("postgresql://batch@localhost/db") + + with pytest.raises(Pg8000CandidateAdapterError, match="connection factory"): + Pg8000CandidateDriverAdapter(_candidate_module()) + + module = _candidate_module(include_connect=True) + driver = Pg8000CandidateDriverAdapter(module) + error_type = vars(module)["ProgrammingError"] + assert isinstance(error_type, type) + assert driver.is_undefined_function(error_type({"C": "42883"})) is True + + +def test_service_file_preserves_primary_failure_when_close_also_fails( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Descriptor cleanup cannot replace an already-established service-file failure.""" + monkeypatch.setattr(candidate_service_file.os, "open", lambda *_args: 7) + + def fail_fstat(_descriptor: int) -> object: + raise OSError("read failed") + + def fail_close(_descriptor: int) -> None: + raise OSError("close failed") + + monkeypatch.setattr(candidate_service_file.os, "fstat", fail_fstat) + monkeypatch.setattr(candidate_service_file.os, "close", fail_close) + + with pytest.raises(Pg8000CandidateInvalidConninfoError): + candidate_service_file._read_bounded_utf8(Path("unused-service.conf")) + + +def test_psycopg_adapter_covers_tuple_rows_and_default_connect_timeout( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Retained adapter keeps tuple identity and omits absent timeout authority.""" + raw_cursor = SimpleNamespace(fetchone=lambda: ("row",)) + assert PsycopgCursorAdapter(raw_cursor).fetchone() == ("row",) + + captured: dict[str, object] = {} + + def connect(dsn: str, **kwargs: object) -> _TransactionConnection: + captured["dsn"] = dsn + captured["kwargs"] = kwargs + return _TransactionConnection() + + monkeypatch.setattr(psycopg_adapter.psycopg, "connect", connect) + connection = PsycopgDriverAdapter().connect("host=localhost") + assert captured == {"dsn": "host=localhost", "kwargs": {}} + connection.close() + + def test_candidate_evidence_rejects_unknown_capability_without_count_overflow() -> None: """Replacing one required capability with an unknown name hits set authority checks.""" capabilities = set(REQUIRED_POSTGRES_DRIVER_CAPABILITIES) From f73990a120b137ea2d034de41d67a0ae658aa3d8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 18:39:40 +0900 Subject: [PATCH 279/338] docs(product): refresh protected-main truth --- docs/product-technical-gap-baseline.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index d31753f2..d451549d 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -8,7 +8,7 @@ pg-llm-batch owns durable PostgreSQL-backed asynchronous LLM batch preparation, ## Protected-main truth -The protected integration branch is `main`. At the latest refresh it was `bdff1273d3885dedc5187632e1c8838b470c9b6d`. The package remains version `0.1.0`, and its production dependency graph still includes `psycopg[binary]>=3.1`. Therefore issue #322, replacement of the LGPL-family Psycopg runtime dependency, remains an open commercial-policy defect. No public release should claim that the current `pip install .` runtime graph is commercially clean while that defect remains. +The protected integration branch is `main`. At the latest refresh it was `5913c4bad79d6bc29d7cc1c624abb7db2ea6a77c`. The package remains version `0.1.0`, and its production dependency graph still includes `psycopg[binary]>=3.1`. Therefore issue #322, replacement of the LGPL-family Psycopg runtime dependency, remains an open commercial-policy defect. No public release should claim that the current `pip install .` runtime graph is commercially clean while that defect remains. The repository has no immutable GitHub release at the latest refresh. A release is not ready merely because a branch is green: one exact protected head must pass the repository's applicable CI, security, coverage/docstring, package, SBOM/provenance, reproducibility, migration/rollback/recovery, operability, and review gates before version/tag/publication evidence is promoted. From d3a33b787270ce37e33829ee0408ba972faec594 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 18:45:51 +0900 Subject: [PATCH 280/338] fix(postgres): make service cleanup precedence explicit --- pg_llm_batch/pg8000_candidate_service_file.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/pg_llm_batch/pg8000_candidate_service_file.py b/pg_llm_batch/pg8000_candidate_service_file.py index 428440d5..0eb664ce 100644 --- a/pg_llm_batch/pg8000_candidate_service_file.py +++ b/pg_llm_batch/pg8000_candidate_service_file.py @@ -71,6 +71,16 @@ def _service_file_snapshot(observed: os.stat_result) -> tuple[int, int, int, int ) +def _close_descriptor(descriptor: int, *, preserve_primary_error: bool) -> None: + """Close one retained descriptor without replacing an established primary error.""" + try: + os.close(descriptor) + except OSError: + if preserve_primary_error: + return + raise _invalid_service_file() from None + + def _read_bounded_utf8(path: Path) -> str: """Read one explicit regular service file under a finite UTF-8 byte budget. @@ -121,11 +131,10 @@ def _read_bounded_utf8(path: Path) -> str: primary_error = exc raise _invalid_service_file() from None finally: - try: - os.close(descriptor) - except OSError: - if primary_error is None: - raise _invalid_service_file() from None + _close_descriptor( + descriptor, + preserve_primary_error=primary_error is not None, + ) if len(payload) > _MAX_SERVICE_FILE_BYTES: raise _invalid_service_file() From 09c1b3289722acdab30152fe5fd24df17878dd47 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 19:03:20 +0900 Subject: [PATCH 281/338] test(ci): lock candidate password file to runtime uid --- tests/test_workflow_contracts.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/test_workflow_contracts.py b/tests/test_workflow_contracts.py index d42f5e67..87996f07 100644 --- a/tests/test_workflow_contracts.py +++ b/tests/test_workflow_contracts.py @@ -180,6 +180,28 @@ def test_ci_pg8000_candidate_parity_is_immutable_and_queue_conservative() -> Non assert '"pg8000' not in project.casefold() +def test_ci_pg8000_candidate_secret_is_readable_only_by_exact_runtime_identity() -> None: + """Bind the 0600 password file to the UID that executes the built DB image.""" + workflow = _read(".github/workflows/ci.yml") + dockerfile = _read("docker/postgres/Dockerfile") + + assert "USER postgres" in dockerfile + assert 'mktemp "${RUNNER_TEMP:?}/pg8000-password.XXXXXX"' in workflow + assert 'chmod 600 "$password_file"' in workflow + assert ( + 'postgres_runtime_uid="$(docker run --rm --entrypoint id ' + 'pg-llm-batch-postgres:ci -u)"' + ) in workflow + assert 'sudo chown "$postgres_runtime_uid" "$password_file"' in workflow + assert 'test "$(stat -c \'%a\' "$password_file")" = "600"' in workflow + assert ( + 'test "$(stat -c \'%u\' "$password_file")" = "$postgres_runtime_uid"' + in workflow + ) + assert "POSTGRES_HOST_AUTH_METHOD=trust" not in workflow + assert "chmod 644" not in workflow + + def test_ci_pg8000_candidate_health_matches_selected_runtime_capabilities() -> None: """Candidate parity must not wait for an extension disabled by its image.""" workflow = _read(".github/workflows/ci.yml") @@ -319,4 +341,4 @@ def test_pyproject_declares_hard_quality_thresholds() -> None: assert '[tool.coverage.run]\nsource = ["pg_llm_batch"]' in config assert '[tool.coverage.report]\nfail_under = 100\nshow_missing = true' in config - assert '[tool.interrogate]\nexclude = ["tests"]\nfail-under = 100' in config \ No newline at end of file + assert '[tool.interrogate]\nexclude = ["tests"]\nfail-under = 100' in config From 82ff565164cbb519c074665cbb25b2f4a00d6c80 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 19:04:04 +0900 Subject: [PATCH 282/338] fix(ci): bind pg8000 password file to postgres uid --- .github/workflows/ci.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18bc1ed6..dfa0ab46 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -160,9 +160,14 @@ jobs: run: | candidate_password="$(python -c 'import secrets; print(secrets.token_urlsafe(32))')" echo "::add-mask::$candidate_password" - password_file="$(mktemp)" + password_file="$(mktemp "${RUNNER_TEMP:?}/pg8000-password.XXXXXX")" printf '%s' "$candidate_password" > "$password_file" chmod 600 "$password_file" + postgres_runtime_uid="$(docker run --rm --entrypoint id pg-llm-batch-postgres:ci -u)" + test -n "$postgres_runtime_uid" + sudo chown "$postgres_runtime_uid" "$password_file" + test "$(stat -c '%a' "$password_file")" = "600" + test "$(stat -c '%u' "$password_file")" = "$postgres_runtime_uid" container="pg-llm-batch-pg8000-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" echo "PG8000_CANDIDATE_PASSWORD_FILE=$password_file" >> "$GITHUB_ENV" echo "PG8000_CANDIDATE_CONTAINER=$container" >> "$GITHUB_ENV" @@ -201,5 +206,5 @@ jobs: docker rm --force "$PG8000_CANDIDATE_CONTAINER" >/dev/null 2>&1 || true fi if [ -n "${PG8000_CANDIDATE_PASSWORD_FILE:-}" ]; then - rm -f "$PG8000_CANDIDATE_PASSWORD_FILE" - fi \ No newline at end of file + sudo rm -f "$PG8000_CANDIDATE_PASSWORD_FILE" + fi From 41ba59ff22f0d50e0331ce0f2cd8d3dc4486b0a6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 19:09:21 +0900 Subject: [PATCH 283/338] test(ci): preserve separate host and container secrets --- tests/test_workflow_contracts.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/tests/test_workflow_contracts.py b/tests/test_workflow_contracts.py index 87996f07..deb21a8d 100644 --- a/tests/test_workflow_contracts.py +++ b/tests/test_workflow_contracts.py @@ -180,24 +180,28 @@ def test_ci_pg8000_candidate_parity_is_immutable_and_queue_conservative() -> Non assert '"pg8000' not in project.casefold() -def test_ci_pg8000_candidate_secret_is_readable_only_by_exact_runtime_identity() -> None: - """Bind the 0600 password file to the UID that executes the built DB image.""" +def test_ci_pg8000_candidate_keeps_0600_secrets_for_both_runtime_identities() -> None: + """Give host smoke and DB runtime separate private copies of one credential.""" workflow = _read(".github/workflows/ci.yml") dockerfile = _read("docker/postgres/Dockerfile") assert "USER postgres" in dockerfile - assert 'mktemp "${RUNNER_TEMP:?}/pg8000-password.XXXXXX"' in workflow - assert 'chmod 600 "$password_file"' in workflow + assert 'host_password_file="$(mktemp "${RUNNER_TEMP:?}/pg8000-host-password.XXXXXX")"' in workflow + assert 'container_password_file="$(mktemp "${RUNNER_TEMP:?}/pg8000-container-password.XXXXXX")"' in workflow + assert 'chmod 600 "$host_password_file" "$container_password_file"' in workflow + assert 'runner_uid="$(id -u)"' in workflow assert ( 'postgres_runtime_uid="$(docker run --rm --entrypoint id ' 'pg-llm-batch-postgres:ci -u)"' ) in workflow - assert 'sudo chown "$postgres_runtime_uid" "$password_file"' in workflow - assert 'test "$(stat -c \'%a\' "$password_file")" = "600"' in workflow + assert 'sudo chown "$postgres_runtime_uid" "$container_password_file"' in workflow + assert 'test "$(stat -c \'%u\' "$host_password_file")" = "$runner_uid"' in workflow assert ( - 'test "$(stat -c \'%u\' "$password_file")" = "$postgres_runtime_uid"' - in workflow - ) + 'test "$(stat -c \'%u\' "$container_password_file")" ' + '= "$postgres_runtime_uid"' + ) in workflow + assert 'PG8000_CANDIDATE_PASSWORD_FILE=$host_password_file' in workflow + assert 'source=$container_password_file,target=/run/secrets/postgres_password' in workflow assert "POSTGRES_HOST_AUTH_METHOD=trust" not in workflow assert "chmod 644" not in workflow From d958d4d4c5c8e2801b122f81a133da40669c4d09 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 19:10:24 +0900 Subject: [PATCH 284/338] fix(ci): split pg8000 host and container secret files --- .github/workflows/ci.yml | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dfa0ab46..3158790f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -160,20 +160,26 @@ jobs: run: | candidate_password="$(python -c 'import secrets; print(secrets.token_urlsafe(32))')" echo "::add-mask::$candidate_password" - password_file="$(mktemp "${RUNNER_TEMP:?}/pg8000-password.XXXXXX")" - printf '%s' "$candidate_password" > "$password_file" - chmod 600 "$password_file" + host_password_file="$(mktemp "${RUNNER_TEMP:?}/pg8000-host-password.XXXXXX")" + container_password_file="$(mktemp "${RUNNER_TEMP:?}/pg8000-container-password.XXXXXX")" + printf '%s' "$candidate_password" > "$host_password_file" + printf '%s' "$candidate_password" > "$container_password_file" + chmod 600 "$host_password_file" "$container_password_file" + runner_uid="$(id -u)" postgres_runtime_uid="$(docker run --rm --entrypoint id pg-llm-batch-postgres:ci -u)" test -n "$postgres_runtime_uid" - sudo chown "$postgres_runtime_uid" "$password_file" - test "$(stat -c '%a' "$password_file")" = "600" - test "$(stat -c '%u' "$password_file")" = "$postgres_runtime_uid" + sudo chown "$postgres_runtime_uid" "$container_password_file" + test "$(stat -c '%a' "$host_password_file")" = "600" + test "$(stat -c '%a' "$container_password_file")" = "600" + test "$(stat -c '%u' "$host_password_file")" = "$runner_uid" + test "$(stat -c '%u' "$container_password_file")" = "$postgres_runtime_uid" container="pg-llm-batch-pg8000-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" - echo "PG8000_CANDIDATE_PASSWORD_FILE=$password_file" >> "$GITHUB_ENV" + echo "PG8000_CANDIDATE_PASSWORD_FILE=$host_password_file" >> "$GITHUB_ENV" + echo "PG8000_CANDIDATE_CONTAINER_PASSWORD_FILE=$container_password_file" >> "$GITHUB_ENV" echo "PG8000_CANDIDATE_CONTAINER=$container" >> "$GITHUB_ENV" docker run --detach --name "$container" \ --publish 127.0.0.1:5432:5432 \ - --mount "type=bind,source=$password_file,target=/run/secrets/postgres_password,readonly" \ + --mount "type=bind,source=$container_password_file,target=/run/secrets/postgres_password,readonly" \ --env POSTGRES_USER=pgllm \ --env POSTGRES_DB=pgllm \ --env POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password \ @@ -206,5 +212,8 @@ jobs: docker rm --force "$PG8000_CANDIDATE_CONTAINER" >/dev/null 2>&1 || true fi if [ -n "${PG8000_CANDIDATE_PASSWORD_FILE:-}" ]; then - sudo rm -f "$PG8000_CANDIDATE_PASSWORD_FILE" + rm -f "$PG8000_CANDIDATE_PASSWORD_FILE" + fi + if [ -n "${PG8000_CANDIDATE_CONTAINER_PASSWORD_FILE:-}" ]; then + sudo rm -f "$PG8000_CANDIDATE_CONTAINER_PASSWORD_FILE" fi From 70b2a7d497414c7910b7a2315e7ab5cf0b626384 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 19:30:09 +0900 Subject: [PATCH 285/338] test(ci): follow split candidate secret ownership --- tests/test_pg8000_candidate_secret_boundary.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_pg8000_candidate_secret_boundary.py b/tests/test_pg8000_candidate_secret_boundary.py index f7651bb3..e3bf07b7 100644 --- a/tests/test_pg8000_candidate_secret_boundary.py +++ b/tests/test_pg8000_candidate_secret_boundary.py @@ -15,11 +15,11 @@ def _read(relative_path: str) -> str: def test_pg8000_candidate_credential_uses_only_ephemeral_password_file() -> None: - """Keep the candidate password in one masked file boundary, not GITHUB_ENV.""" + """Keep the candidate password in the host smoke file, not GITHUB_ENV.""" workflow = _read(".github/workflows/ci.yml") smoke = _read("tests/smoke_pg8000_candidate_postgres.py") - assert "PG8000_CANDIDATE_PASSWORD_FILE=$password_file" in workflow + assert "PG8000_CANDIDATE_PASSWORD_FILE=$host_password_file" in workflow assert "PG_LLM_BATCH_POSTGRES_PASSWORD=$candidate_password" not in workflow assert 'os.environ.get("PG_LLM_BATCH_POSTGRES_PASSWORD")' not in smoke assert 'os.environ.get("PG8000_CANDIDATE_PASSWORD_FILE")' in smoke From a3fdc99f105824c1c265279ef345c5c278eea7f0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 19:36:56 +0900 Subject: [PATCH 286/338] test(pg8000): lock no-parameter execute parity --- ...t_pg8000_candidate_no_parameter_execute.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/test_pg8000_candidate_no_parameter_execute.py diff --git a/tests/test_pg8000_candidate_no_parameter_execute.py b/tests/test_pg8000_candidate_no_parameter_execute.py new file mode 100644 index 00000000..56294db7 --- /dev/null +++ b/tests/test_pg8000_candidate_no_parameter_execute.py @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Regression coverage for pg8000 no-parameter DB-API execution.""" + +from __future__ import annotations + +from pg_llm_batch.pg8000_driver_candidate_adapter import Pg8000CandidateCursorAdapter + + +class _Pg8000NoParameterCursor: + """Model pg8000 1.31.5 rejecting an explicit ``None`` argument container.""" + + def __init__(self) -> None: + self.calls: list[tuple[str, tuple[object, ...]]] = [] + + def execute(self, query: str, *args: object) -> _Pg8000NoParameterCursor: + """Record native execute arity and reproduce pg8000's ``len(None)`` failure.""" + self.calls.append((query, args)) + if args == (None,): + raise TypeError("explicit None is not a pg8000 argument container") + return self + + +def test_candidate_cursor_omits_argument_container_for_parameterless_sql() -> None: + """Map the port's ``None`` sentinel to pg8000's native one-argument execute call.""" + raw = _Pg8000NoParameterCursor() + adapter = Pg8000CandidateCursorAdapter(raw) + + assert adapter.execute("SELECT current_database(), current_user") is adapter + assert raw.calls == [("SELECT current_database(), current_user", ())] From f158f6eb6b46d2a8660d67e630b599762b2e0d93 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 19:38:14 +0900 Subject: [PATCH 287/338] fix(pg8000): omit null execute argument container --- pg_llm_batch/pg8000_driver_candidate_adapter.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pg_llm_batch/pg8000_driver_candidate_adapter.py b/pg_llm_batch/pg8000_driver_candidate_adapter.py index 78d18441..48152650 100644 --- a/pg_llm_batch/pg8000_driver_candidate_adapter.py +++ b/pg_llm_batch/pg8000_driver_candidate_adapter.py @@ -101,14 +101,18 @@ def execute( query: str, params: object | None = None, ) -> Pg8000CandidateCursorAdapter: - """Forward package-authored SQL and bound parameters without interpolation. + """Execute package-authored SQL with pg8000-compatible parameter semantics. pg8000's DB-API interface defaults to ``format`` parameter style, which - matches the existing ``%s`` package SQL. This candidate method forwards - both objects unchanged so later real-driver tests can detect any semantic - mismatch rather than hiding it in an adapter rewrite. + matches the existing ``%s`` package SQL. Explicit parameter containers + are forwarded unchanged. The port-level ``None`` sentinel means that the + statement has no parameters, so the adapter omits pg8000's second + ``execute`` argument instead of passing ``None`` as an argument container. """ - self._cursor.execute(query, params) + if params is None: + self._cursor.execute(query) + else: + self._cursor.execute(query, params) return self def executemany( @@ -373,4 +377,4 @@ def __exit__( if transaction_error is not None: raise transaction_error - return False + return False \ No newline at end of file From 36027180fff12639f80e78dbf6660878f85bee03 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 19:44:23 +0900 Subject: [PATCH 288/338] test(pg8000): require real database error authority --- ...0_driver_candidate_error_classification.py | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/test_pg8000_driver_candidate_error_classification.py b/tests/test_pg8000_driver_candidate_error_classification.py index dffdbb4b..0eb69455 100644 --- a/tests/test_pg8000_driver_candidate_error_classification.py +++ b/tests/test_pg8000_driver_candidate_error_classification.py @@ -1,9 +1,9 @@ """Candidate-only PostgreSQL error-classification contract tests. -These tests keep pg8000 out of the production dependency graph. The exact +These tests keep pg8000 out of the production dependency graph. The exact candidate artifact is injected as a DB-API module so the classifier can prove one PostgreSQL SQLSTATE without trusting shaped exception objects or message -text. Real PostgreSQL acceptance remains a separate CI smoke before candidate +text. Real PostgreSQL acceptance remains a separate CI smoke before candidate promotion. """ @@ -19,14 +19,14 @@ ) -class _ProgrammingError(Exception): - """Stand in for the exact candidate DB-API ProgrammingError class.""" +class _DatabaseError(Exception): + """Stand in for pg8000 1.31.5's server-error DB-API class.""" def _dbapi_module() -> ModuleType: """Build one exact module-shaped DB-API authority for classifier tests.""" module = ModuleType("pg8000.dbapi") - module.ProgrammingError = _ProgrammingError + module.DatabaseError = _DatabaseError return module @@ -34,11 +34,11 @@ def test_candidate_classifies_only_exact_undefined_function_sqlstate() -> None: module = _dbapi_module() assert is_pg8000_candidate_undefined_function( - _ProgrammingError({"S": "ERROR", "C": "42883", "M": "hidden"}), + _DatabaseError({"S": "ERROR", "C": "42883", "M": "hidden"}), dbapi_module=module, ) is True assert is_pg8000_candidate_undefined_function( - _ProgrammingError({"S": "ERROR", "C": "42P01", "M": "hidden"}), + _DatabaseError({"S": "ERROR", "C": "42P01", "M": "hidden"}), dbapi_module=module, ) is False @@ -49,7 +49,7 @@ def test_candidate_classifier_rejects_untrusted_module_authority() -> None: match="DB-API module authority is invalid", ): is_pg8000_candidate_undefined_function( - _ProgrammingError({"C": "42883"}), + _DatabaseError({"C": "42883"}), dbapi_module=object(), ) @@ -62,7 +62,7 @@ def get(self, key: object) -> object: raise AssertionError("mapping-like payload was evaluated") assert is_pg8000_candidate_undefined_function( - _ProgrammingError(_MappingLike()), + _DatabaseError(_MappingLike()), dbapi_module=module, ) is False assert is_pg8000_candidate_undefined_function( @@ -71,15 +71,15 @@ def get(self, key: object) -> object: ) is False -def test_candidate_classifier_rejects_malformed_programming_error_authority() -> None: +def test_candidate_classifier_rejects_malformed_database_error_authority() -> None: module = _dbapi_module() - module.ProgrammingError = "ProgrammingError" + module.DatabaseError = "DatabaseError" with pytest.raises( Pg8000CandidateErrorEvidenceError, - match="ProgrammingError authority is invalid", + match="DatabaseError authority is invalid", ): is_pg8000_candidate_undefined_function( - _ProgrammingError({"C": "42883"}), + _DatabaseError({"C": "42883"}), dbapi_module=module, ) From a72dc4c5284bfb1f719fa257cbad5484a06c5f36 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 19:44:38 +0900 Subject: [PATCH 289/338] fix(pg8000): classify server database errors by SQLSTATE --- .../pg8000_driver_candidate_errors.py | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/pg_llm_batch/pg8000_driver_candidate_errors.py b/pg_llm_batch/pg8000_driver_candidate_errors.py index bf88c088..0319985b 100644 --- a/pg_llm_batch/pg8000_driver_candidate_errors.py +++ b/pg_llm_batch/pg8000_driver_candidate_errors.py @@ -1,9 +1,9 @@ """Candidate-only pg8000 PostgreSQL error classification. The production package still uses Psycopg while the commercial driver migration -is incomplete. This module proves only narrow pg8000 error semantics needed by +is incomplete. This module proves only narrow pg8000 error semantics needed by ``PostgresDriverPort`` without importing pg8000 into the committed runtime -dependency graph. Callers must inject the exact admitted DB-API module from the +dependency graph. Callers must inject the exact admitted DB-API module from the candidate environment; message text is never used as authority. """ @@ -18,34 +18,32 @@ class Pg8000CandidateErrorEvidenceError(RuntimeError): """Reject malformed candidate exception authority before classification. - Candidate metadata participates in a commercial dependency decision. An + Candidate metadata participates in a commercial dependency decision. An invalid module or exception-class authority therefore fails closed instead of being interpreted as a PostgreSQL server error or a successful parity result. """ -def _programming_error_type(dbapi_module: object) -> type[BaseException]: - """Return the exact DB-API ProgrammingError class from an admitted module. +def _database_error_type(dbapi_module: object) -> type[BaseException]: + """Return the exact DB-API DatabaseError class from an admitted module. - ``ModuleType`` identity is required so shaped objects cannot execute custom - attribute access while supplying security-relevant error metadata. The - exported class must be an actual ``BaseException`` subtype before any - candidate exception is inspected. + pg8000 1.31.5 reports PostgreSQL server responses, including SQLSTATE 42883, + as ``DatabaseError`` rather than ``ProgrammingError``. ``ModuleType`` identity + is required so shaped objects cannot execute custom attribute access while + supplying security-relevant error metadata. The exported class must be an + actual ``BaseException`` subtype before any candidate exception is inspected. """ if type(dbapi_module) is not ModuleType: raise Pg8000CandidateErrorEvidenceError( "PostgreSQL candidate DB-API module authority is invalid" ) - programming_error = vars(dbapi_module).get("ProgrammingError") - if ( - type(programming_error) is not type - or not issubclass(programming_error, BaseException) - ): + database_error = vars(dbapi_module).get("DatabaseError") + if type(database_error) is not type or not issubclass(database_error, BaseException): raise Pg8000CandidateErrorEvidenceError( - "PostgreSQL candidate ProgrammingError authority is invalid" + "PostgreSQL candidate DatabaseError authority is invalid" ) - return programming_error + return database_error def is_pg8000_candidate_undefined_function( @@ -56,18 +54,18 @@ def is_pg8000_candidate_undefined_function( """Recognize only PostgreSQL SQLSTATE 42883 from the exact candidate class. pg8000 server errors carry a PostgreSQL response mapping as the sole - ``ProgrammingError`` argument. Classification requires the exact injected + ``DatabaseError`` argument. Classification requires the exact injected DB-API exception type, an exact built-in ``dict`` payload, and an exact - string SQLSTATE. Severity and message text are intentionally ignored, so + string SQLSTATE. Severity and message text are intentionally ignored, so translated or attacker-controlled diagnostics cannot manufacture the undefined-function fallback signal used by token-counting code. Raises: Pg8000CandidateErrorEvidenceError: If the injected DB-API module does not - expose a trustworthy ``ProgrammingError`` class authority. + expose a trustworthy ``DatabaseError`` class authority. """ - programming_error = _programming_error_type(dbapi_module) - if type(error) is not programming_error: + database_error = _database_error_type(dbapi_module) + if type(error) is not database_error: return False arguments = error.args if type(arguments) is not tuple or len(arguments) != 1: From 581c40d20d0414d38b2036f1dfc2da66b3b802df Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 19:47:16 +0900 Subject: [PATCH 290/338] test(pg8000): align edge fixture with database errors --- tests/test_postgres_driver_edge_coverage.py | 63 ++------------------- 1 file changed, 4 insertions(+), 59 deletions(-) diff --git a/tests/test_postgres_driver_edge_coverage.py b/tests/test_postgres_driver_edge_coverage.py index 00d93f69..98381bc1 100644 --- a/tests/test_postgres_driver_edge_coverage.py +++ b/tests/test_postgres_driver_edge_coverage.py @@ -37,7 +37,7 @@ def _candidate_module() -> ModuleType: """Build one admitted-shaped pg8000 DB-API module without importing pg8000.""" module = ModuleType("candidate_pg8000") - class ProgrammingErrorCandidate(Exception): + class DatabaseErrorCandidate(Exception): pass def connect(**_kwargs: object) -> _RawCandidateConnection: @@ -46,7 +46,7 @@ def connect(**_kwargs: object) -> _RawCandidateConnection: module.apilevel = "2.0" module.threadsafety = 1 module.paramstyle = "format" - module.ProgrammingError = ProgrammingErrorCandidate + module.DatabaseError = DatabaseErrorCandidate module.connect = connect return module @@ -178,7 +178,7 @@ def test_candidate_error_classifier_rejects_shaped_authority_and_payloads() -> N is_pg8000_candidate_undefined_function(RuntimeError(), dbapi_module=object()) module = _candidate_module() - error_type = vars(module)["ProgrammingError"] + error_type = vars(module)["DatabaseError"] assert isinstance(error_type, type) assert is_pg8000_candidate_undefined_function(error_type("bad"), dbapi_module=module) is False assert is_pg8000_candidate_undefined_function(error_type({"C": 42883}), dbapi_module=module) is False @@ -230,59 +230,4 @@ def test_psycopg_cursor_fail_closed_edges() -> None: cursor.row_count() -class _ClosedShape: - """Expose an invalid non-boolean Psycopg closed-state signal.""" - - closed = 1 - - -def test_psycopg_connection_rejects_non_boolean_closed_state() -> None: - """Closed-state authority cannot rely on integer truthiness.""" - with pytest.raises(PsycopgDriverAdapterError, match="closed state"): - PsycopgConnectionAdapter(_ClosedShape()).is_closed() - - -def test_psycopg_conninfo_wrappers_narrow_programming_errors(monkeypatch: pytest.MonkeyPatch) -> None: - """Only conninfo grammar failures become the neutral invalid-selector category.""" - def fail_parse(_dsn: str) -> dict[str, str]: - raise ProgrammingError("bad") - - def fail_render(**_params: str) -> str: - raise ProgrammingError("bad") - - monkeypatch.setattr(psycopg_adapter, "conninfo_to_dict", fail_parse) - monkeypatch.setattr(psycopg_adapter, "make_conninfo", fail_render) - adapter = PsycopgDriverAdapter() - with pytest.raises(PsycopgInvalidConninfoError): - adapter.parse_conninfo("bad") - with pytest.raises(PsycopgInvalidConninfoError): - adapter.make_conninfo({"host": "bad"}) - - -def test_runtime_selector_distinguishes_missing_psycopg_from_other_import_failures( - monkeypatch: pytest.MonkeyPatch, -) -> None: - """Optional-client absence is redacted while unrelated package defects propagate.""" - original_import = builtins.__import__ - - def missing_psycopg(name: str, *args: Any, **kwargs: Any) -> Any: - if name.endswith("psycopg_driver_adapter"): - error = ModuleNotFoundError("missing psycopg") - error.name = "psycopg" - raise error - return original_import(name, *args, **kwargs) - - monkeypatch.setattr(builtins, "__import__", missing_psycopg) - with pytest.raises(runtime.PostgresDriverUnavailableError, match="unavailable"): - runtime.retained_postgres_driver() - - def missing_other(name: str, *args: Any, **kwargs: Any) -> Any: - if name.endswith("psycopg_driver_adapter"): - error = ModuleNotFoundError("missing other") - error.name = "other_dependency" - raise error - return original_import(name, *args, **kwargs) - - monkeypatch.setattr(builtins, "__import__", missing_other) - with pytest.raises(ModuleNotFoundError): - runtime.retained_postgres_driver() +# Remaining file content intentionally unchanged below this point. From d376d36ac542fd4a06c9cc338f91a2f84c4dcbe5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 19:48:39 +0900 Subject: [PATCH 291/338] repair(test): restore edge suite after fixture update --- tests/test_postgres_driver_edge_coverage.py | 57 ++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/tests/test_postgres_driver_edge_coverage.py b/tests/test_postgres_driver_edge_coverage.py index 98381bc1..53eb542a 100644 --- a/tests/test_postgres_driver_edge_coverage.py +++ b/tests/test_postgres_driver_edge_coverage.py @@ -230,4 +230,59 @@ def test_psycopg_cursor_fail_closed_edges() -> None: cursor.row_count() -# Remaining file content intentionally unchanged below this point. +class _ClosedShape: + """Expose an invalid non-boolean Psycopg closed-state signal.""" + + closed = 1 + + +def test_psycopg_connection_rejects_non_boolean_closed_state() -> None: + """Closed-state authority cannot rely on integer truthiness.""" + with pytest.raises(PsycopgDriverAdapterError, match="closed state"): + PsycopgConnectionAdapter(_ClosedShape()).is_closed() + + +def test_psycopg_conninfo_wrappers_narrow_programming_errors(monkeypatch: pytest.MonkeyPatch) -> None: + """Only conninfo grammar failures become the neutral invalid-selector category.""" + def fail_parse(_dsn: str) -> dict[str, str]: + raise ProgrammingError("bad") + + def fail_render(**_params: str) -> str: + raise ProgrammingError("bad") + + monkeypatch.setattr(psycopg_adapter, "conninfo_to_dict", fail_parse) + monkeypatch.setattr(psycopg_adapter, "make_conninfo", fail_render) + adapter = PsycopgDriverAdapter() + with pytest.raises(PsycopgInvalidConninfoError): + adapter.parse_conninfo("bad") + with pytest.raises(PsycopgInvalidConninfoError): + adapter.make_conninfo({"host": "bad"}) + + +def test_runtime_selector_distinguishes_missing_psycopg_from_other_import_failures( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Optional-client absence is redacted while unrelated package defects propagate.""" + original_import = builtins.__import__ + + def missing_psycopg(name: str, *args: Any, **kwargs: Any) -> Any: + if name.endswith("psycopg_driver_adapter"): + error = ModuleNotFoundError("missing psycopg") + error.name = "psycopg" + raise error + return original_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", missing_psycopg) + with pytest.raises(runtime.PostgresDriverUnavailableError, match="unavailable"): + runtime.retained_postgres_driver() + + def missing_other(name: str, *args: Any, **kwargs: Any) -> Any: + if name.endswith("psycopg_driver_adapter"): + error = ModuleNotFoundError("missing other") + error.name = "other_dependency" + raise error + return original_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", missing_other) + with pytest.raises(ModuleNotFoundError): + runtime.retained_postgres_driver() From 1d4a5a70b7f904823c4a64b10f1ec42c9906f428 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 19:49:24 +0900 Subject: [PATCH 292/338] test(pg8000): align remaining error fixtures --- tests/test_postgres_driver_remaining_edges.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_postgres_driver_remaining_edges.py b/tests/test_postgres_driver_remaining_edges.py index f2b5b5cf..a9d7716b 100644 --- a/tests/test_postgres_driver_remaining_edges.py +++ b/tests/test_postgres_driver_remaining_edges.py @@ -38,13 +38,13 @@ def _candidate_module(*, include_connect: bool = False) -> ModuleType: """Build exact DB-API candidate authority without importing the package.""" module = ModuleType("candidate_pg8000_errors") - class ProgrammingErrorCandidate(Exception): + class DatabaseErrorCandidate(Exception): pass module.apilevel = "2.0" module.paramstyle = "format" module.threadsafety = 1 - module.ProgrammingError = ProgrammingErrorCandidate + module.DatabaseError = DatabaseErrorCandidate if include_connect: module.connect = lambda **_kwargs: _TransactionConnection() return module @@ -145,7 +145,7 @@ def test_candidate_error_classifier_rejects_wrong_exact_exception_type() -> None def test_candidate_error_classifier_rejects_missing_server_payload() -> None: """The exact candidate exception class still needs one PostgreSQL response payload.""" module = _candidate_module() - error_type = vars(module)["ProgrammingError"] + error_type = vars(module)["DatabaseError"] assert isinstance(error_type, type) assert is_pg8000_candidate_undefined_function(error_type(), dbapi_module=module) is False @@ -179,7 +179,7 @@ def test_candidate_driver_covers_remaining_fail_closed_selector_edges( module = _candidate_module(include_connect=True) driver = Pg8000CandidateDriverAdapter(module) - error_type = vars(module)["ProgrammingError"] + error_type = vars(module)["DatabaseError"] assert isinstance(error_type, type) assert driver.is_undefined_function(error_type({"C": "42883"})) is True From e86ed11a97ea0f4689cde678239350388ef82a6e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 20:04:22 +0900 Subject: [PATCH 293/338] test(pg8000): prove real transport failure recovery --- tests/smoke_pg8000_candidate_postgres.py | 57 +++++++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/tests/smoke_pg8000_candidate_postgres.py b/tests/smoke_pg8000_candidate_postgres.py index e70da637..b28b69f2 100644 --- a/tests/smoke_pg8000_candidate_postgres.py +++ b/tests/smoke_pg8000_candidate_postgres.py @@ -6,8 +6,9 @@ checks cover the candidate URI, keyword, and explicit service connection selectors, portable connection/cursor ACL, thread-affine connection use, transaction, parameter, JSONB, UUID/timestamp, affected-row, narrow PostgreSQL -error classification, restore-catalog inspection, and transaction-local tenant -semantics that must be proven before candidate promotion. +error classification, restore-catalog inspection, transport recovery, and +transaction-local tenant semantics that must be proven before candidate +promotion. """ from __future__ import annotations @@ -309,6 +310,57 @@ def _assert_undefined_function_classification() -> None: connection.close() +def _assert_transport_failure_recovery() -> None: + """Prove a severed candidate session is discarded and a fresh one recovers. + + The CI PostgreSQL user owns the ephemeral server and may terminate one of its + own backends. The victim operation must surface the server-side disconnect; + local close must then mark that capability terminal even if protocol cleanup + itself reports the severed transport. Recovery authority is a newly opened + connection, never reuse of the failed session. + """ + victim = _connection() + terminator = _connection() + try: + with victim.cursor() as cursor: + cursor.execute("SELECT pg_backend_pid()") + row = cursor.fetchone() + if row is None or len(row) != 1 or type(row[0]) is not int: + raise AssertionError("candidate backend identity evidence changed") + backend_pid = row[0] + + terminator.set_autocommit(True) + with terminator.cursor() as cursor: + cursor.execute("SELECT pg_terminate_backend(%s)", (backend_pid,)) + if cursor.fetchone() != (True,): + raise AssertionError("candidate backend termination did not succeed") + + try: + with victim.cursor() as cursor: + cursor.execute("SELECT 1") + except BaseException: + pass + else: + raise AssertionError("candidate reused a server-terminated session") + finally: + terminator.close() + try: + victim.close() + except BaseException: + pass + if not victim.is_closed(): + raise AssertionError("candidate failed connection did not become terminal") + + recovered = _connection() + try: + with recovered.cursor() as cursor: + cursor.execute("SELECT 1") + if cursor.fetchone() != (1,): + raise AssertionError("candidate fresh-session recovery changed") + finally: + recovered.close() + + def _assert_typed_rls_read( expected_uuid: uuid.UUID, expected_time: datetime, @@ -361,6 +413,7 @@ def main() -> None: _assert_restore_catalog_inspection() _assert_undefined_function_classification() + _assert_transport_failure_recovery() _cleanup() try: evidence_uuid, evidence_time = _prepare_rls_fixture() From 1bdc1c31946b82338015650a8a6b9ca7f7f151e5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 20:16:02 +0900 Subject: [PATCH 294/338] docs(gaps): record candidate transport recovery evidence --- docs/product-technical-gap-baseline.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index d451549d..60313f98 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -16,18 +16,18 @@ The repository has no immutable GitHub release at the latest refresh. A release PR #233 remains the dependency-root delivery lane and must be judged from its live head and live base, not predecessor evidence. -PR #323 is the active Draft migration lane for issue #322. It establishes a driver-neutral PostgreSQL anti-corruption port, retains Psycopg only as the current baseline adapter, and evaluates pg8000 1.31.5 as candidate evidence without promoting it into the production manifest. The lane already exercises parameter binding, tuple-row normalization, row-count semantics, transaction and cleanup precedence, forced-RLS tenant scope, JSONB/UUID/timestamp behavior, exact candidate dependency hashes, and real PostgreSQL candidate smoke tests. +PR #323 is the active Draft migration lane for issue #322. It establishes a driver-neutral PostgreSQL anti-corruption port, retains Psycopg only as the current baseline adapter, and evaluates pg8000 1.31.5 as candidate evidence without promoting it into the production manifest. The lane exercises parameter binding, tuple-row normalization, row-count semantics, transaction and cleanup precedence, forced-RLS tenant scope, JSONB/UUID/timestamp behavior, exact candidate dependency hashes and license metadata, URI/keyword/explicit-service selection, packaged restore-catalog inspection, thread-affinity rejection at the anti-corruption boundary, and real PostgreSQL candidate execution. Exact branch evidence now also terminates a live candidate backend from a second authenticated session, requires the severed capability to fail and become terminal, and proves recovery only by opening a fresh connection. That is candidate recovery evidence; it is not production-driver promotion. -The current candidate supply-chain work also verifies license metadata for the exact five-wheel pg8000 candidate closure before installation. The verifier reads bounded wheel `METADATA` without importing candidate code, rejects GPL/LGPL/AGPL-family declarations, requires positive reviewed permissive-license evidence for every exact package/version, and rejects an unexpected wheel set. This strengthens candidate admission but does not itself approve a production driver replacement. +The current candidate supply-chain work verifies license metadata for the exact five-wheel pg8000 candidate closure before installation. The verifier reads bounded wheel `METADATA` without importing candidate code, rejects GPL/LGPL/AGPL-family declarations, requires positive reviewed permissive-license evidence for every exact package/version, and rejects an unexpected wheel set. This strengthens candidate admission but does not itself approve a production driver replacement. ## Highest-priority gaps | Gap | Current state | Required next evidence | | --- | --- | --- | | Commercial PostgreSQL runtime dependency | P0 / active | Complete issue #322: preserve shipped DB semantics while removing every disallowed GPL/LGPL/AGPL-family runtime package from the committed dependency graph. | -| Candidate driver contract parity | Active Draft | Close conninfo URI/keyword/service-selector compatibility, driver-level JSONB/error classification, concurrency/recovery, timeout/health, schema/restore, and package-installed behavior with realistic PostgreSQL evidence. | -| Candidate supply-chain admission | Active / strengthened | Exact wheel hashes and license metadata are now gated; complete vulnerability/SBOM/provenance and final runtime-graph evidence before promotion. | -| Exact-head CI execution | External control-plane dependency plus local continuation | Current required jobs must acquire a runner and check out the exact current head. Queued/pre-checkout evidence is non-passing; central `.github#712` owns the organization runner-admission diagnosis. | +| Candidate driver contract parity | Active Draft | Real server-terminated-session discard and fresh-session recovery are now proven on the candidate. Close remaining selector/conninfo compatibility, realistic concurrency beyond the deterministic anti-cross-thread guard, timeout/health, remaining schema/recovery surfaces, and package-installed behavior before production promotion. | +| Candidate supply-chain admission | Active / strengthened | Exact wheel hashes and license metadata are gated; complete vulnerability/SBOM/provenance and final runtime-graph evidence before promotion. | +| Dependency-root governance | External owner paths / non-passing | #233 has leaf CI/release/security evidence but still requires authenticated current-head compatibility CodeQL/OpenCode/Noema settlement and a structurally satisfiable independent approval path before normal protected integration. | | Immutable product release | Not yet published | After the production dependency replacement and all gates pass on one integrated protected head, perform version/CHANGELOG/tag/package/SBOM/provenance/reproducibility/rollback publication and verify artifact identity. | | Context Graph / EA projection | Candidate-only until released authority exists | `context-graph-contracts` and `enterprise-architecture-core` currently expose no immutable GitHub release. Do not pin mutable producer heads. Continue pg-owned release-readiness seams and adopt only a verified released contract. | @@ -53,4 +53,4 @@ Prompt, response, batch-result, and user data remain pg/product-domain data and ## Evidence discipline -Queued, pending, skipped-required, cancelled, absent, predecessor-head, model-only, and status-only evidence is non-passing. A current blocker is the next work item at its actual owner: pg-owned causes require a realistic RED, the smallest causal repair, focused/full GREEN, and exact-head refetch; foreign-owned causes require advancement of the existing owner path followed by independent pg work. A report, comment, handoff, or documentation-only change is never completion while executable code/test/release work remains. +Queued, pending, skipped-required, cancelled, absent, predecessor-head, model-only, and status-only evidence is non-passing. A current blocker is the next work item at its actual owner: pg-owned causes require a realistic RED, the smallest causal repair, focused/full GREEN, and exact-head refetch; foreign-owned causes require advancement of the existing owner path followed by independent pg work. A report, comment, handoff, or documentation-only change is never completion while executable code/test/release work remains. \ No newline at end of file From ab1673f04a29c4891d58a4136134e8a33b10507a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 20:35:10 +0900 Subject: [PATCH 295/338] test(postgres): require real pg8000 smoke across supported runtime matrix --- tests/test_pg8000_candidate_python_matrix.py | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/test_pg8000_candidate_python_matrix.py diff --git a/tests/test_pg8000_candidate_python_matrix.py b/tests/test_pg8000_candidate_python_matrix.py new file mode 100644 index 00000000..f54bfe5f --- /dev/null +++ b/tests/test_pg8000_candidate_python_matrix.py @@ -0,0 +1,27 @@ +"""Regression for exact-artifact pg8000 parity across shipped Python runtimes.""" + +from __future__ import annotations + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def test_real_pg8000_postgres_smoke_covers_release_runtime_matrix() -> None: + """Run the real candidate/PostgreSQL contract on each release-critical minor.""" + workflow = (ROOT / ".github/workflows/ci.yml").read_text(encoding="utf-8") + + expected_runtimes = { + "3.10": "/tmp/pg8000-candidate-py310/bin/python", + "3.12": "/tmp/pg8000-candidate-py312/bin/python", + "3.14": "/tmp/pg8000-candidate-py314/bin/python", + } + for python_version, interpreter in expected_runtimes.items(): + assert f'python-version: "{python_version}"' in workflow + assert f"{interpreter} tests/smoke_pg8000_candidate_postgres.py" in workflow + assert f"uv pip check --python {interpreter}" in workflow + + assert workflow.count("tests/smoke_pg8000_candidate_postgres.py") == len( + expected_runtimes + ) From bf8ecba67d71fc77971f2e402e278fe1a22d1ed6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 20:37:22 +0900 Subject: [PATCH 296/338] ci(postgres): prove pg8000 parity on release Python runtimes --- .github/workflows/ci.yml | 52 ++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3158790f..1c7fd776 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,10 +114,24 @@ jobs: run: bash tests/smoke_postgres_container_logging.sh - name: Run legacy SQL cleanup integration smoke run: bash tests/smoke_legacy_sql_cleanup.sh + - name: Set up Python 3.10 for candidate parity + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.10" + - name: Create Python 3.10 candidate environment + run: python -m venv /tmp/pg8000-candidate-py310 + - name: Set up Python 3.12 for candidate parity + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.12" + - name: Create Python 3.12 candidate environment + run: python -m venv /tmp/pg8000-candidate-py312 - name: Set up Python 3.14 for candidate parity uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.14" + - name: Create Python 3.14 candidate environment + run: python -m venv /tmp/pg8000-candidate-py314 - name: Set up uv for candidate parity uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: @@ -145,16 +159,26 @@ jobs: EOF - name: Verify pg8000 candidate dependency licenses run: python tools/verify_candidate_wheel_licenses.py /tmp/pg8000-candidate - - name: Install exact candidate closure into the CI environment - run: >- - uv pip install --python .venv/bin/python --no-deps - /tmp/pg8000-candidate/pg8000-1.31.5-py3-none-any.whl - /tmp/pg8000-candidate/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - /tmp/pg8000-candidate/scramp-1.4.17-py3-none-any.whl - /tmp/pg8000-candidate/asn1crypto-1.5.1-py2.py3-none-any.whl - /tmp/pg8000-candidate/six-1.17.0-py2.py3-none-any.whl - - name: Verify candidate environment dependency consistency - run: uv pip check --python .venv/bin/python + - name: Install exact candidate closure into release Python environments + shell: bash + run: | + for interpreter in \ + /tmp/pg8000-candidate-py310/bin/python \ + /tmp/pg8000-candidate-py312/bin/python \ + /tmp/pg8000-candidate-py314/bin/python; do + uv pip install --python "$interpreter" --no-deps \ + /tmp/pg8000-candidate/pg8000-1.31.5-py3-none-any.whl \ + /tmp/pg8000-candidate/python_dateutil-2.9.0.post0-py2.py3-none-any.whl \ + /tmp/pg8000-candidate/scramp-1.4.17-py3-none-any.whl \ + /tmp/pg8000-candidate/asn1crypto-1.5.1-py2.py3-none-any.whl \ + /tmp/pg8000-candidate/six-1.17.0-py2.py3-none-any.whl + done + - name: Verify pg8000 candidate Python 3.10 environment + run: uv pip check --python /tmp/pg8000-candidate-py310/bin/python + - name: Verify pg8000 candidate Python 3.12 environment + run: uv pip check --python /tmp/pg8000-candidate-py312/bin/python + - name: Verify pg8000 candidate Python 3.14 environment + run: uv pip check --python /tmp/pg8000-candidate-py314/bin/python - name: Start candidate PostgreSQL runtime shell: bash run: | @@ -202,8 +226,12 @@ jobs: done docker logs "$PG8000_CANDIDATE_CONTAINER" exit 1 - - name: Run real pg8000 candidate PostgreSQL smoke - run: uv run --no-sync python tests/smoke_pg8000_candidate_postgres.py + - name: Run real pg8000 candidate PostgreSQL smoke on Python 3.10 + run: PYTHONPATH=. /tmp/pg8000-candidate-py310/bin/python tests/smoke_pg8000_candidate_postgres.py + - name: Run real pg8000 candidate PostgreSQL smoke on Python 3.12 + run: PYTHONPATH=. /tmp/pg8000-candidate-py312/bin/python tests/smoke_pg8000_candidate_postgres.py + - name: Run real pg8000 candidate PostgreSQL smoke on Python 3.14 + run: PYTHONPATH=. /tmp/pg8000-candidate-py314/bin/python tests/smoke_pg8000_candidate_postgres.py - name: Tear down candidate PostgreSQL runtime if: ${{ always() }} shell: bash From 90cbaeb0391eb5a898f5b7ce5354434e5d394be2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 20:42:16 +0900 Subject: [PATCH 297/338] ci(postgres): install locked runtime graph for candidate matrix --- .github/workflows/ci.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c7fd776..2aaae5c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -137,7 +137,17 @@ jobs: with: prune-cache: true - name: Install locked project dependencies - run: uv sync --locked + shell: bash + run: | + uv sync --locked + for environment in \ + /tmp/pg8000-candidate-py310 \ + /tmp/pg8000-candidate-py312 \ + /tmp/pg8000-candidate-py314; do + UV_PROJECT_ENVIRONMENT="$environment" \ + uv sync --locked --no-dev --no-install-project \ + --python "$environment/bin/python" + done - name: Download exact pg8000 candidate dependency closure run: >- python -m pip download --no-deps --only-binary=:all: From 2a1569f5834356916270ce9dbf24f64ac82e7b97 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 20:46:56 +0900 Subject: [PATCH 298/338] test(postgres): align candidate license gate with runtime matrix --- tests/test_candidate_wheel_license_verifier.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_candidate_wheel_license_verifier.py b/tests/test_candidate_wheel_license_verifier.py index 8088a3c4..a9a9f0f3 100644 --- a/tests/test_candidate_wheel_license_verifier.py +++ b/tests/test_candidate_wheel_license_verifier.py @@ -175,7 +175,9 @@ def test_candidate_license_gate_runs_before_candidate_install() -> None: encoding="utf-8" ) verification_step = "- name: Verify pg8000 candidate dependency licenses" - install_step = "- name: Install exact candidate closure into the CI environment" + install_step = ( + "- name: Install exact candidate closure into release Python environments" + ) assert verification_step in workflow assert "python tools/verify_candidate_wheel_licenses.py /tmp/pg8000-candidate" in workflow From bb3ce277cd591988139ded8d299bf9536b3ec429 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 20:47:51 +0900 Subject: [PATCH 299/338] test(postgres): align immutable candidate install contract with matrix --- tests/test_workflow_contracts.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/test_workflow_contracts.py b/tests/test_workflow_contracts.py index deb21a8d..4d530430 100644 --- a/tests/test_workflow_contracts.py +++ b/tests/test_workflow_contracts.py @@ -250,8 +250,15 @@ def test_ci_pg8000_candidate_pins_and_hashes_full_dependency_closure() -> None: assert requirement in workflow assert digest in workflow + expected_interpreters = ( + "/tmp/pg8000-candidate-py310/bin/python", + "/tmp/pg8000-candidate-py312/bin/python", + "/tmp/pg8000-candidate-py314/bin/python", + ) assert "pip download --no-deps --only-binary=:all:" in workflow - assert "uv pip install --python .venv/bin/python --no-deps" in workflow + assert 'uv pip install --python "$interpreter" --no-deps' in workflow + for interpreter in expected_interpreters: + assert interpreter in workflow assert "/tmp/pg8000-candidate/pg8000-1.31.5-py3-none-any.whl" in workflow assert ( "/tmp/pg8000-candidate/python_dateutil-2.9.0.post0-py2.py3-none-any.whl" From 6c50b57a57c40b084a29de2bcc48e5612fe80d93 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 21:05:48 +0900 Subject: [PATCH 300/338] test(postgres): require pg8000 source-wheel parity evidence --- tests/test_candidate_source_wheel_parity.py | 130 ++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 tests/test_candidate_source_wheel_parity.py diff --git a/tests/test_candidate_source_wheel_parity.py b/tests/test_candidate_source_wheel_parity.py new file mode 100644 index 00000000..f7a189c6 --- /dev/null +++ b/tests/test_candidate_source_wheel_parity.py @@ -0,0 +1,130 @@ +"""Regression tests for immutable pg8000 source-to-wheel parity evidence. + +The commercial PostgreSQL-driver migration pins the published pg8000 wheel, but +a wheel digest alone does not show that its executable package sources match the +published source distribution. These tests require a bounded, non-executing +archive verifier and require CI to run it before candidate installation. +""" + +from __future__ import annotations + +import importlib.util +from io import BytesIO +from pathlib import Path +import tarfile +import zipfile + +import pytest + + +_REPOSITORY_ROOT = Path(__file__).parents[1] +_TOOL_PATH = _REPOSITORY_ROOT / "tools" / "verify_candidate_source_wheel_parity.py" + + +def _load_verifier(): + """Load the repository-owned parity verifier without making tools a package.""" + assert _TOOL_PATH.is_file(), "candidate source-wheel parity verifier is missing" + spec = importlib.util.spec_from_file_location("candidate_source_wheel_parity", _TOOL_PATH) + if spec is None or spec.loader is None: + raise AssertionError("candidate source-wheel parity verifier could not be loaded") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _write_sdist(path: Path, sources: dict[str, bytes]) -> None: + """Write a minimal pg8000 source distribution with executable package bytes.""" + with tarfile.open(path, "w:gz") as archive: + for relative_path, payload in sources.items(): + member = tarfile.TarInfo(f"pg8000-1.31.5/src/pg8000/{relative_path}") + member.size = len(payload) + archive.addfile(member, BytesIO(payload)) + documentation = b"candidate docs\n" + member = tarfile.TarInfo("pg8000-1.31.5/README.md") + member.size = len(documentation) + archive.addfile(member, BytesIO(documentation)) + + +def _write_wheel(path: Path, sources: dict[str, bytes]) -> None: + """Write a minimal pg8000 wheel carrying the supplied package source bytes.""" + with zipfile.ZipFile(path, "w") as archive: + for relative_path, payload in sources.items(): + archive.writestr(f"pg8000/{relative_path}", payload) + archive.writestr( + "pg8000-1.31.5.dist-info/METADATA", + "Metadata-Version: 2.4\nName: pg8000\nVersion: 1.31.5\n", + ) + + +def test_candidate_source_and_wheel_require_identical_python_payloads(tmp_path: Path) -> None: + """Exact executable Python sources must agree across the two published artifacts.""" + verifier = _load_verifier() + sources = { + "__init__.py": b"__version__ = '1.31.5'\n", + "core.py": b"def marker():\n return 'same'\n", + } + sdist_path = tmp_path / "pg8000-1.31.5.tar.gz" + wheel_path = tmp_path / "pg8000-1.31.5-py3-none-any.whl" + _write_sdist(sdist_path, sources) + _write_wheel(wheel_path, sources) + + verifier.verify_candidate_source_wheel_parity(sdist_path, wheel_path) + + +def test_candidate_source_wheel_parity_rejects_changed_executable_source(tmp_path: Path) -> None: + """A wheel-side source mutation must fail even when both archive names are expected.""" + verifier = _load_verifier() + sdist_path = tmp_path / "pg8000-1.31.5.tar.gz" + wheel_path = tmp_path / "pg8000-1.31.5-py3-none-any.whl" + _write_sdist(sdist_path, {"core.py": b"VALUE = 'source'\n"}) + _write_wheel(wheel_path, {"core.py": b"VALUE = 'wheel'\n"}) + + with pytest.raises( + verifier.CandidateSourceWheelParityError, + match="package payload differs", + ): + verifier.verify_candidate_source_wheel_parity(sdist_path, wheel_path) + + +def test_candidate_source_wheel_parity_rejects_extra_wheel_python_source(tmp_path: Path) -> None: + """The built wheel must not introduce executable Python absent from the sdist.""" + verifier = _load_verifier() + sdist_path = tmp_path / "pg8000-1.31.5.tar.gz" + wheel_path = tmp_path / "pg8000-1.31.5-py3-none-any.whl" + _write_sdist(sdist_path, {"core.py": b"VALUE = 1\n"}) + _write_wheel( + wheel_path, + { + "core.py": b"VALUE = 1\n", + "injected.py": b"VALUE = 'unexpected'\n", + }, + ) + + with pytest.raises( + verifier.CandidateSourceWheelParityError, + match="package payload differs", + ): + verifier.verify_candidate_source_wheel_parity(sdist_path, wheel_path) + + +def test_candidate_source_wheel_parity_runs_before_candidate_install() -> None: + """CI must hash and compare the source artifact before candidate code is installed.""" + workflow = (_REPOSITORY_ROOT / ".github" / "workflows" / "ci.yml").read_text( + encoding="utf-8" + ) + source_download_step = "- name: Download exact pg8000 candidate source distribution" + source_digest_step = "- name: Verify pg8000 candidate source digest" + parity_step = "- name: Verify pg8000 candidate source-wheel parity" + install_step = "- name: Install exact candidate closure into release Python environments" + + assert source_download_step in workflow + assert source_digest_step in workflow + assert parity_step in workflow + assert ( + "python tools/verify_candidate_source_wheel_parity.py " + "/tmp/pg8000-candidate-source/pg8000-1.31.5.tar.gz " + "/tmp/pg8000-candidate/pg8000-1.31.5-py3-none-any.whl" + ) in workflow + assert workflow.index(source_download_step) < workflow.index(source_digest_step) + assert workflow.index(source_digest_step) < workflow.index(parity_step) + assert workflow.index(parity_step) < workflow.index(install_step) From 565daadd45a3ef14e2051d7e6f81c4245746739c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 21:07:35 +0900 Subject: [PATCH 301/338] fix(postgres): verify pg8000 source-wheel parity --- tools/verify_candidate_source_wheel_parity.py | 194 ++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 tools/verify_candidate_source_wheel_parity.py diff --git a/tools/verify_candidate_source_wheel_parity.py b/tools/verify_candidate_source_wheel_parity.py new file mode 100644 index 00000000..833a9d78 --- /dev/null +++ b/tools/verify_candidate_source_wheel_parity.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 +"""Verify executable pg8000 sources agree between pinned sdist and wheel artifacts. + +PyPI publishes pg8000 1.31.5 as both a source distribution and a universal wheel, +but the release was uploaded without Trusted Publishing. The repository already +pins and license-checks the wheel used for candidate execution. This verifier +adds an independent, non-executing artifact-consistency check: every Python +source shipped under the pg8000 package in the exact wheel must have the same +path and bytes in the exact source distribution, and neither artifact may add a +Python module absent from the other. + +The verifier never extracts archives, imports candidate code, follows archive +links, or accepts alternate artifact names. Finite member, file-count, and total +payload limits keep malformed archives from turning provenance inspection into an +unbounded resource operation. +""" + +from __future__ import annotations + +from hashlib import sha256 +from pathlib import Path, PurePosixPath +import sys +import tarfile +import zipfile + + +_EXPECTED_SDIST_NAME = "pg8000-1.31.5.tar.gz" +_EXPECTED_WHEEL_NAME = "pg8000-1.31.5-py3-none-any.whl" +_SDIST_PACKAGE_PREFIX = "pg8000-1.31.5/src/pg8000/" +_WHEEL_PACKAGE_PREFIX = "pg8000/" +_MAX_PACKAGE_FILES = 512 +_MAX_MEMBER_BYTES = 2 * 1024 * 1024 +_MAX_PACKAGE_BYTES = 8 * 1024 * 1024 + + +class CandidateSourceWheelParityError(RuntimeError): + """Reject candidate artifacts that cannot prove source-to-wheel parity.""" + + +def _relative_python_path(member_name: str, *, prefix: str) -> str | None: + """Return one bounded package-relative Python path or ``None`` for other files.""" + if not member_name.startswith(prefix): + return None + relative = member_name[len(prefix) :] + if not relative or relative.endswith("/") or not relative.endswith(".py"): + return None + path = PurePosixPath(relative) + if path.is_absolute() or any(part in {"", ".", ".."} for part in path.parts): + raise CandidateSourceWheelParityError("candidate package member path is invalid") + return path.as_posix() + + +def _record_payload( + payloads: dict[str, str], + *, + relative_path: str, + payload: bytes, + total_bytes: int, +) -> int: + """Record one source digest while enforcing finite and unique package evidence.""" + if len(payload) > _MAX_MEMBER_BYTES: + raise CandidateSourceWheelParityError("candidate package member exceeds size limit") + if relative_path in payloads: + raise CandidateSourceWheelParityError("candidate package member identity is duplicated") + if len(payloads) >= _MAX_PACKAGE_FILES: + raise CandidateSourceWheelParityError("candidate package file count exceeds limit") + total_bytes += len(payload) + if total_bytes > _MAX_PACKAGE_BYTES: + raise CandidateSourceWheelParityError("candidate package payload exceeds size limit") + payloads[relative_path] = sha256(payload).hexdigest() + return total_bytes + + +def _sdist_python_payloads(sdist_path: Path) -> dict[str, str]: + """Read bounded Python-source digests from the exact pg8000 source distribution.""" + if sdist_path.name != _EXPECTED_SDIST_NAME or not sdist_path.is_file(): + raise CandidateSourceWheelParityError("candidate source artifact identity is invalid") + + payloads: dict[str, str] = {} + total_bytes = 0 + try: + with tarfile.open(sdist_path, mode="r:gz") as archive: + for member in archive.getmembers(): + relative = _relative_python_path( + member.name, + prefix=_SDIST_PACKAGE_PREFIX, + ) + if relative is None: + continue + if not member.isfile(): + raise CandidateSourceWheelParityError( + "candidate source package member is not a regular file" + ) + if member.size < 0 or member.size > _MAX_MEMBER_BYTES: + raise CandidateSourceWheelParityError( + "candidate package member exceeds size limit" + ) + stream = archive.extractfile(member) + if stream is None: + raise CandidateSourceWheelParityError( + "candidate source package member could not be inspected" + ) + payload = stream.read(_MAX_MEMBER_BYTES + 1) + if len(payload) != member.size: + raise CandidateSourceWheelParityError( + "candidate source package member size is inconsistent" + ) + total_bytes = _record_payload( + payloads, + relative_path=relative, + payload=payload, + total_bytes=total_bytes, + ) + except CandidateSourceWheelParityError: + raise + except (OSError, tarfile.TarError, EOFError): + raise CandidateSourceWheelParityError( + "candidate source artifact could not be inspected" + ) from None + + if not payloads: + raise CandidateSourceWheelParityError("candidate source package payload is empty") + return payloads + + +def _wheel_python_payloads(wheel_path: Path) -> dict[str, str]: + """Read bounded Python-source digests from the exact pg8000 universal wheel.""" + if wheel_path.name != _EXPECTED_WHEEL_NAME or not wheel_path.is_file(): + raise CandidateSourceWheelParityError("candidate wheel artifact identity is invalid") + + payloads: dict[str, str] = {} + total_bytes = 0 + try: + with zipfile.ZipFile(wheel_path) as archive: + for member in archive.infolist(): + relative = _relative_python_path( + member.filename, + prefix=_WHEEL_PACKAGE_PREFIX, + ) + if relative is None: + continue + if member.is_dir() or member.file_size > _MAX_MEMBER_BYTES: + raise CandidateSourceWheelParityError( + "candidate package member exceeds size limit" + ) + payload = archive.read(member) + if len(payload) != member.file_size: + raise CandidateSourceWheelParityError( + "candidate wheel package member size is inconsistent" + ) + total_bytes = _record_payload( + payloads, + relative_path=relative, + payload=payload, + total_bytes=total_bytes, + ) + except CandidateSourceWheelParityError: + raise + except (OSError, zipfile.BadZipFile, RuntimeError, ValueError): + raise CandidateSourceWheelParityError( + "candidate wheel artifact could not be inspected" + ) from None + + if not payloads: + raise CandidateSourceWheelParityError("candidate wheel package payload is empty") + return payloads + + +def verify_candidate_source_wheel_parity(sdist_path: Path, wheel_path: Path) -> None: + """Require exact Python package path and byte parity across pinned artifacts.""" + if not isinstance(sdist_path, Path) or not isinstance(wheel_path, Path): + raise CandidateSourceWheelParityError("candidate artifact path is invalid") + source_payloads = _sdist_python_payloads(sdist_path) + wheel_payloads = _wheel_python_payloads(wheel_path) + if source_payloads != wheel_payloads: + raise CandidateSourceWheelParityError("candidate source and wheel package payload differs") + + +def main(argv: list[str] | None = None) -> int: + """Run source-to-wheel parity verification for one exact pg8000 candidate pair.""" + arguments = sys.argv[1:] if argv is None else argv + if len(arguments) != 2: + raise SystemExit( + "usage: verify_candidate_source_wheel_parity.py SDIST_PATH WHEEL_PATH" + ) + try: + verify_candidate_source_wheel_parity(Path(arguments[0]), Path(arguments[1])) + except CandidateSourceWheelParityError as exc: + raise SystemExit(str(exc)) from None + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 10061307dd173884bfee58d7b9e83428dba74260 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 21:09:27 +0900 Subject: [PATCH 302/338] ci(postgres): verify pg8000 source-wheel parity --- .github/workflows/ci.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2aaae5c9..98acd347 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -167,6 +167,22 @@ jobs: db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67 /tmp/pg8000-candidate/asn1crypto-1.5.1-py2.py3-none-any.whl 4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 /tmp/pg8000-candidate/six-1.17.0-py2.py3-none-any.whl EOF + - name: Download exact pg8000 candidate source distribution + shell: bash + run: | + mkdir -p /tmp/pg8000-candidate-source + curl --fail --location --proto '=https' --tlsv1.2 \ + --output /tmp/pg8000-candidate-source/pg8000-1.31.5.tar.gz \ + https://files.pythonhosted.org/packages/c8/9a/077ab21e700051e03d8c5232b6bcb9a1a4d4b6242c9a0226df2cfa306414/pg8000-1.31.5.tar.gz + - name: Verify pg8000 candidate source digest + run: >- + echo "46ebb03be52b7a77c03c725c79da2ca281d6e8f59577ca66b17c9009618cae78 /tmp/pg8000-candidate-source/pg8000-1.31.5.tar.gz" + | sha256sum --check --strict + - name: Verify pg8000 candidate source-wheel parity + run: >- + python tools/verify_candidate_source_wheel_parity.py + /tmp/pg8000-candidate-source/pg8000-1.31.5.tar.gz + /tmp/pg8000-candidate/pg8000-1.31.5-py3-none-any.whl - name: Verify pg8000 candidate dependency licenses run: python tools/verify_candidate_wheel_licenses.py /tmp/pg8000-candidate - name: Install exact candidate closure into release Python environments From 4432ff56d259aa139afd4b04e9b0073c3c8e513d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 21:12:31 +0900 Subject: [PATCH 303/338] test(postgres): match folded parity workflow command --- tests/test_candidate_source_wheel_parity.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/test_candidate_source_wheel_parity.py b/tests/test_candidate_source_wheel_parity.py index f7a189c6..c84a41cd 100644 --- a/tests/test_candidate_source_wheel_parity.py +++ b/tests/test_candidate_source_wheel_parity.py @@ -116,15 +116,16 @@ def test_candidate_source_wheel_parity_runs_before_candidate_install() -> None: source_digest_step = "- name: Verify pg8000 candidate source digest" parity_step = "- name: Verify pg8000 candidate source-wheel parity" install_step = "- name: Install exact candidate closure into release Python environments" + verifier_command = "python tools/verify_candidate_source_wheel_parity.py" + source_path = "/tmp/pg8000-candidate-source/pg8000-1.31.5.tar.gz" + wheel_path = "/tmp/pg8000-candidate/pg8000-1.31.5-py3-none-any.whl" assert source_download_step in workflow assert source_digest_step in workflow assert parity_step in workflow - assert ( - "python tools/verify_candidate_source_wheel_parity.py " - "/tmp/pg8000-candidate-source/pg8000-1.31.5.tar.gz " - "/tmp/pg8000-candidate/pg8000-1.31.5-py3-none-any.whl" - ) in workflow + assert verifier_command in workflow + assert source_path in workflow + assert wheel_path in workflow assert workflow.index(source_download_step) < workflow.index(source_digest_step) assert workflow.index(source_digest_step) < workflow.index(parity_step) assert workflow.index(parity_step) < workflow.index(install_step) From abe5dfedb38b8bf28ffeccf03dda4fc836a4520b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 21:14:40 +0900 Subject: [PATCH 304/338] docs(postgres): record candidate source-wheel parity gate --- docs/product-technical-gap-baseline.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 60313f98..752c646a 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -16,9 +16,9 @@ The repository has no immutable GitHub release at the latest refresh. A release PR #233 remains the dependency-root delivery lane and must be judged from its live head and live base, not predecessor evidence. -PR #323 is the active Draft migration lane for issue #322. It establishes a driver-neutral PostgreSQL anti-corruption port, retains Psycopg only as the current baseline adapter, and evaluates pg8000 1.31.5 as candidate evidence without promoting it into the production manifest. The lane exercises parameter binding, tuple-row normalization, row-count semantics, transaction and cleanup precedence, forced-RLS tenant scope, JSONB/UUID/timestamp behavior, exact candidate dependency hashes and license metadata, URI/keyword/explicit-service selection, packaged restore-catalog inspection, thread-affinity rejection at the anti-corruption boundary, and real PostgreSQL candidate execution. Exact branch evidence now also terminates a live candidate backend from a second authenticated session, requires the severed capability to fail and become terminal, and proves recovery only by opening a fresh connection. That is candidate recovery evidence; it is not production-driver promotion. +PR #323 is the active Draft migration lane for issue #322. It establishes a driver-neutral PostgreSQL anti-corruption port, retains Psycopg only as the current baseline adapter, and evaluates pg8000 1.31.5 as candidate evidence without promoting it into the production manifest. The lane exercises parameter binding, tuple-row normalization, row-count semantics, transaction and cleanup precedence, forced-RLS tenant scope, JSONB/UUID/timestamp behavior, exact candidate dependency hashes and license metadata, URI/keyword/explicit-service selection, packaged restore-catalog acceptance, thread-affinity rejection at the anti-corruption boundary, and real PostgreSQL candidate execution. Exact branch evidence now also terminates a live candidate backend from a second authenticated session, requires the severed capability to fail and become terminal, and proves recovery only by opening a fresh connection. That is candidate recovery evidence; it is not production-driver promotion. -The current candidate supply-chain work verifies license metadata for the exact five-wheel pg8000 candidate closure before installation. The verifier reads bounded wheel `METADATA` without importing candidate code, rejects GPL/LGPL/AGPL-family declarations, requires positive reviewed permissive-license evidence for every exact package/version, and rejects an unexpected wheel set. This strengthens candidate admission but does not itself approve a production driver replacement. +Candidate supply-chain admission now verifies both the exact five-wheel pg8000 closure and the published pg8000 1.31.5 source distribution before candidate installation. CI pins the wheel and source-distribution SHA-256 digests, reads bounded wheel `METADATA` without importing candidate code, rejects GPL/LGPL/AGPL-family declarations, requires positive reviewed permissive-license evidence for every exact package/version, and compares every Python source path and byte digest under the pg8000 package between the pinned source distribution and universal wheel. The parity verifier does not extract archives, import candidate code, follow archive links, or execute a source build. This closes the published source-to-wheel executable-payload parity gap for the selected candidate artifacts; it does not itself approve a production driver replacement or provide an upstream build attestation. ## Highest-priority gaps @@ -26,7 +26,7 @@ The current candidate supply-chain work verifies license metadata for the exact | --- | --- | --- | | Commercial PostgreSQL runtime dependency | P0 / active | Complete issue #322: preserve shipped DB semantics while removing every disallowed GPL/LGPL/AGPL-family runtime package from the committed dependency graph. | | Candidate driver contract parity | Active Draft | Real server-terminated-session discard and fresh-session recovery are now proven on the candidate. Close remaining selector/conninfo compatibility, realistic concurrency beyond the deterministic anti-cross-thread guard, timeout/health, remaining schema/recovery surfaces, and package-installed behavior before production promotion. | -| Candidate supply-chain admission | Active / strengthened | Exact wheel hashes and license metadata are gated; complete vulnerability/SBOM/provenance and final runtime-graph evidence before promotion. | +| Candidate supply-chain admission | Active / strengthened | Exact wheel/source hashes, source-to-wheel Python payload parity, and closure license metadata are gated; complete vulnerability/SBOM/provenance and final runtime-graph evidence before promotion. | | Dependency-root governance | External owner paths / non-passing | #233 has leaf CI/release/security evidence but still requires authenticated current-head compatibility CodeQL/OpenCode/Noema settlement and a structurally satisfiable independent approval path before normal protected integration. | | Immutable product release | Not yet published | After the production dependency replacement and all gates pass on one integrated protected head, perform version/CHANGELOG/tag/package/SBOM/provenance/reproducibility/rollback publication and verify artifact identity. | | Context Graph / EA projection | Candidate-only until released authority exists | `context-graph-contracts` and `enterprise-architecture-core` currently expose no immutable GitHub release. Do not pin mutable producer heads. Continue pg-owned release-readiness seams and adopt only a verified released contract. | @@ -53,4 +53,4 @@ Prompt, response, batch-result, and user data remain pg/product-domain data and ## Evidence discipline -Queued, pending, skipped-required, cancelled, absent, predecessor-head, model-only, and status-only evidence is non-passing. A current blocker is the next work item at its actual owner: pg-owned causes require a realistic RED, the smallest causal repair, focused/full GREEN, and exact-head refetch; foreign-owned causes require advancement of the existing owner path followed by independent pg work. A report, comment, handoff, or documentation-only change is never completion while executable code/test/release work remains. \ No newline at end of file +Queued, pending, skipped-required, cancelled, absent, predecessor-head, model-only, and status-only evidence is non-passing. A current blocker is the next work item at its actual owner: pg-owned causes require a realistic RED, the smallest causal repair, focused/full GREEN, and exact-head refetch; foreign-owned causes require advancement of the existing owner path followed by independent pg work. A report, comment, handoff, or documentation-only change is never completion while executable code/test/release work remains. From d19b2d1b5c3a51b7a07445e2756f757c804b181c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 21:29:24 +0900 Subject: [PATCH 305/338] test(postgres): require installed-wheel candidate parity --- tests/test_pg8000_candidate_python_matrix.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/test_pg8000_candidate_python_matrix.py b/tests/test_pg8000_candidate_python_matrix.py index f54bfe5f..23277f92 100644 --- a/tests/test_pg8000_candidate_python_matrix.py +++ b/tests/test_pg8000_candidate_python_matrix.py @@ -9,7 +9,7 @@ def test_real_pg8000_postgres_smoke_covers_release_runtime_matrix() -> None: - """Run the real candidate/PostgreSQL contract on each release-critical minor.""" + """Run the installed package plus real candidate/PostgreSQL contract per minor.""" workflow = (ROOT / ".github/workflows/ci.yml").read_text(encoding="utf-8") expected_runtimes = { @@ -17,11 +17,19 @@ def test_real_pg8000_postgres_smoke_covers_release_runtime_matrix() -> None: "3.12": "/tmp/pg8000-candidate-py312/bin/python", "3.14": "/tmp/pg8000-candidate-py314/bin/python", } + assert "Build exact pg-llm-batch wheel for candidate parity" in workflow + assert "uv build --wheel --no-sources" in workflow + assert "/tmp/pg-llm-batch-candidate-wheel" in workflow + assert "Install exact pg-llm-batch wheel into candidate environments" in workflow + for python_version, interpreter in expected_runtimes.items(): assert f'python-version: "{python_version}"' in workflow - assert f"{interpreter} tests/smoke_pg8000_candidate_postgres.py" in workflow + assert f'uv pip install --python "{interpreter}" --no-deps' in workflow assert f"uv pip check --python {interpreter}" in workflow + assert f'cd "$RUNNER_TEMP" && {interpreter} ' in workflow + assert '"$GITHUB_WORKSPACE/tests/smoke_pg8000_candidate_postgres.py"' in workflow + assert "PYTHONPATH=." not in workflow assert workflow.count("tests/smoke_pg8000_candidate_postgres.py") == len( expected_runtimes ) From 298498c24bbd7c1958a3ae1d02f142ab5ee2a4d8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 21:31:44 +0900 Subject: [PATCH 306/338] ci(postgres): run candidate parity from built wheel --- .github/workflows/ci.yml | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98acd347..daf93c94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -148,6 +148,17 @@ jobs: uv sync --locked --no-dev --no-install-project \ --python "$environment/bin/python" done + - name: Build exact pg-llm-batch wheel for candidate parity + shell: bash + run: | + mkdir -p /tmp/pg-llm-batch-candidate-wheel + uv build --wheel --no-sources --out-dir /tmp/pg-llm-batch-candidate-wheel + mapfile -t product_wheels < <( + find /tmp/pg-llm-batch-candidate-wheel -maxdepth 1 -type f \ + -name 'pg_llm_batch-*.whl' -print + ) + test "${#product_wheels[@]}" -eq 1 + echo "PG_LLM_BATCH_CANDIDATE_WHEEL=${product_wheels[0]}" >> "$GITHUB_ENV" - name: Download exact pg8000 candidate dependency closure run: >- python -m pip download --no-deps --only-binary=:all: @@ -199,6 +210,16 @@ jobs: /tmp/pg8000-candidate/asn1crypto-1.5.1-py2.py3-none-any.whl \ /tmp/pg8000-candidate/six-1.17.0-py2.py3-none-any.whl done + - name: Install exact pg-llm-batch wheel into candidate environments + shell: bash + run: | + test -f "$PG_LLM_BATCH_CANDIDATE_WHEEL" + uv pip install --python "/tmp/pg8000-candidate-py310/bin/python" --no-deps \ + "$PG_LLM_BATCH_CANDIDATE_WHEEL" + uv pip install --python "/tmp/pg8000-candidate-py312/bin/python" --no-deps \ + "$PG_LLM_BATCH_CANDIDATE_WHEEL" + uv pip install --python "/tmp/pg8000-candidate-py314/bin/python" --no-deps \ + "$PG_LLM_BATCH_CANDIDATE_WHEEL" - name: Verify pg8000 candidate Python 3.10 environment run: uv pip check --python /tmp/pg8000-candidate-py310/bin/python - name: Verify pg8000 candidate Python 3.12 environment @@ -253,11 +274,11 @@ jobs: docker logs "$PG8000_CANDIDATE_CONTAINER" exit 1 - name: Run real pg8000 candidate PostgreSQL smoke on Python 3.10 - run: PYTHONPATH=. /tmp/pg8000-candidate-py310/bin/python tests/smoke_pg8000_candidate_postgres.py + run: cd "$RUNNER_TEMP" && /tmp/pg8000-candidate-py310/bin/python "$GITHUB_WORKSPACE/tests/smoke_pg8000_candidate_postgres.py" - name: Run real pg8000 candidate PostgreSQL smoke on Python 3.12 - run: PYTHONPATH=. /tmp/pg8000-candidate-py312/bin/python tests/smoke_pg8000_candidate_postgres.py + run: cd "$RUNNER_TEMP" && /tmp/pg8000-candidate-py312/bin/python "$GITHUB_WORKSPACE/tests/smoke_pg8000_candidate_postgres.py" - name: Run real pg8000 candidate PostgreSQL smoke on Python 3.14 - run: PYTHONPATH=. /tmp/pg8000-candidate-py314/bin/python tests/smoke_pg8000_candidate_postgres.py + run: cd "$RUNNER_TEMP" && /tmp/pg8000-candidate-py314/bin/python "$GITHUB_WORKSPACE/tests/smoke_pg8000_candidate_postgres.py" - name: Tear down candidate PostgreSQL runtime if: ${{ always() }} shell: bash From 354d08a86934bc04e1a5dd513ab5ba958e02c7fa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 21:35:05 +0900 Subject: [PATCH 307/338] docs(postgres): record installed-wheel candidate parity --- docs/product-technical-gap-baseline.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 752c646a..391a9131 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -18,6 +18,8 @@ PR #233 remains the dependency-root delivery lane and must be judged from its li PR #323 is the active Draft migration lane for issue #322. It establishes a driver-neutral PostgreSQL anti-corruption port, retains Psycopg only as the current baseline adapter, and evaluates pg8000 1.31.5 as candidate evidence without promoting it into the production manifest. The lane exercises parameter binding, tuple-row normalization, row-count semantics, transaction and cleanup precedence, forced-RLS tenant scope, JSONB/UUID/timestamp behavior, exact candidate dependency hashes and license metadata, URI/keyword/explicit-service selection, packaged restore-catalog acceptance, thread-affinity rejection at the anti-corruption boundary, and real PostgreSQL candidate execution. Exact branch evidence now also terminates a live candidate backend from a second authenticated session, requires the severed capability to fail and become terminal, and proves recovery only by opening a fresh connection. That is candidate recovery evidence; it is not production-driver promotion. +Candidate runtime parity now executes against the built pg-llm-batch wheel rather than repository import leakage. CI builds the exact source head with `uv build --wheel --no-sources`, installs that wheel without dependency resolution into each isolated Python 3.10, 3.12, and 3.14 candidate environment, runs `uv pip check`, changes out of the repository working tree, and runs the same real pg8000/PostgreSQL smoke without `PYTHONPATH`. This proves package-installed behavior for the candidate lane while the committed product metadata still intentionally retains the Psycopg baseline. + Candidate supply-chain admission now verifies both the exact five-wheel pg8000 closure and the published pg8000 1.31.5 source distribution before candidate installation. CI pins the wheel and source-distribution SHA-256 digests, reads bounded wheel `METADATA` without importing candidate code, rejects GPL/LGPL/AGPL-family declarations, requires positive reviewed permissive-license evidence for every exact package/version, and compares every Python source path and byte digest under the pg8000 package between the pinned source distribution and universal wheel. The parity verifier does not extract archives, import candidate code, follow archive links, or execute a source build. This closes the published source-to-wheel executable-payload parity gap for the selected candidate artifacts; it does not itself approve a production driver replacement or provide an upstream build attestation. ## Highest-priority gaps @@ -25,8 +27,8 @@ Candidate supply-chain admission now verifies both the exact five-wheel pg8000 c | Gap | Current state | Required next evidence | | --- | --- | --- | | Commercial PostgreSQL runtime dependency | P0 / active | Complete issue #322: preserve shipped DB semantics while removing every disallowed GPL/LGPL/AGPL-family runtime package from the committed dependency graph. | -| Candidate driver contract parity | Active Draft | Real server-terminated-session discard and fresh-session recovery are now proven on the candidate. Close remaining selector/conninfo compatibility, realistic concurrency beyond the deterministic anti-cross-thread guard, timeout/health, remaining schema/recovery surfaces, and package-installed behavior before production promotion. | -| Candidate supply-chain admission | Active / strengthened | Exact wheel/source hashes, source-to-wheel Python payload parity, and closure license metadata are gated; complete vulnerability/SBOM/provenance and final runtime-graph evidence before promotion. | +| Candidate driver contract parity | Active Draft | Real server-terminated-session recovery, the Python 3.10/3.12/3.14 matrix, and built-wheel execution are proven. Close remaining selector/conninfo compatibility, realistic concurrency beyond the deterministic anti-cross-thread guard, timeout/health, and remaining schema/recovery surfaces before production promotion. | +| Candidate supply-chain admission | Active / strengthened | Exact wheel/source hashes, source-to-wheel Python payload parity, closure license metadata, and installed-product-wheel execution are gated; complete vulnerability/SBOM/provenance and final production runtime-graph evidence before promotion. | | Dependency-root governance | External owner paths / non-passing | #233 has leaf CI/release/security evidence but still requires authenticated current-head compatibility CodeQL/OpenCode/Noema settlement and a structurally satisfiable independent approval path before normal protected integration. | | Immutable product release | Not yet published | After the production dependency replacement and all gates pass on one integrated protected head, perform version/CHANGELOG/tag/package/SBOM/provenance/reproducibility/rollback publication and verify artifact identity. | | Context Graph / EA projection | Candidate-only until released authority exists | `context-graph-contracts` and `enterprise-architecture-core` currently expose no immutable GitHub release. Do not pin mutable producer heads. Continue pg-owned release-readiness seams and adopt only a verified released contract. | From 91dcf6c3814421aa44a5b4037b0bf200c40d37e5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 22:04:23 +0900 Subject: [PATCH 308/338] test(postgres): require admitted pg8000 production loader --- tests/test_pg8000_driver_loader.py | 145 +++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 tests/test_pg8000_driver_loader.py diff --git a/tests/test_pg8000_driver_loader.py b/tests/test_pg8000_driver_loader.py new file mode 100644 index 00000000..d85655c1 --- /dev/null +++ b/tests/test_pg8000_driver_loader.py @@ -0,0 +1,145 @@ +"""Production-loader contract for the admitted pg8000 PostgreSQL driver.""" + +from __future__ import annotations + +from importlib.metadata import PackageNotFoundError +from types import ModuleType + +import pytest + +import pg_llm_batch.pg8000_driver_adapter as pg8000_driver_adapter +from pg_llm_batch.pg8000_driver_adapter import ( + PG8000_ADMITTED_VERSION, + Pg8000DriverAdapter, + Pg8000DriverUnavailableError, + load_pg8000_driver, +) + + +def _dbapi_module() -> ModuleType: + module = ModuleType("pg8000.dbapi") + module.apilevel = "2.0" + module.paramstyle = "format" + module.threadsafety = 1 + module.connect = lambda **kwargs: object() + return module + + +def test_loader_accepts_only_exact_admitted_distribution(monkeypatch) -> None: + imported: list[str] = [] + module = _dbapi_module() + monkeypatch.setattr( + pg8000_driver_adapter, + "distribution_version", + lambda package: PG8000_ADMITTED_VERSION, + ) + monkeypatch.setattr( + pg8000_driver_adapter, + "import_module", + lambda name: imported.append(name) or module, + ) + + driver = load_pg8000_driver() + + assert isinstance(driver, Pg8000DriverAdapter) + assert imported == ["pg8000.dbapi"] + + +def test_loader_rejects_unadmitted_version_before_import(monkeypatch) -> None: + monkeypatch.setattr( + pg8000_driver_adapter, + "distribution_version", + lambda package: "1.31.4", + ) + monkeypatch.setattr( + pg8000_driver_adapter, + "import_module", + lambda name: pytest.fail("unadmitted artifacts must not be imported"), + ) + + with pytest.raises( + Pg8000DriverUnavailableError, + match="^PostgreSQL driver version is not admitted$", + ): + load_pg8000_driver() + + +def test_loader_normalizes_missing_distribution_without_import(monkeypatch) -> None: + def missing_distribution(package: str) -> str: + raise PackageNotFoundError(package) + + monkeypatch.setattr( + pg8000_driver_adapter, + "distribution_version", + missing_distribution, + ) + monkeypatch.setattr( + pg8000_driver_adapter, + "import_module", + lambda name: pytest.fail("missing distributions must not be imported"), + ) + + with pytest.raises( + Pg8000DriverUnavailableError, + match="^PostgreSQL driver is unavailable$", + ): + load_pg8000_driver() + + +def test_loader_normalizes_missing_pg8000_module(monkeypatch) -> None: + monkeypatch.setattr( + pg8000_driver_adapter, + "distribution_version", + lambda package: PG8000_ADMITTED_VERSION, + ) + + def missing_module(name: str) -> ModuleType: + raise ModuleNotFoundError("pg8000 missing", name="pg8000.dbapi") + + monkeypatch.setattr(pg8000_driver_adapter, "import_module", missing_module) + + with pytest.raises( + Pg8000DriverUnavailableError, + match="^PostgreSQL driver is unavailable$", + ): + load_pg8000_driver() + + +def test_loader_preserves_unrelated_import_failure(monkeypatch) -> None: + monkeypatch.setattr( + pg8000_driver_adapter, + "distribution_version", + lambda package: PG8000_ADMITTED_VERSION, + ) + + def broken_dependency(name: str) -> ModuleType: + raise ModuleNotFoundError("dependency missing", name="scramp") + + monkeypatch.setattr(pg8000_driver_adapter, "import_module", broken_dependency) + + with pytest.raises(ModuleNotFoundError, match="dependency missing"): + load_pg8000_driver() + + +def test_loader_composes_only_explicit_service_file(monkeypatch, tmp_path) -> None: + module = _dbapi_module() + service_file = tmp_path / "pg_service.conf" + service_file.write_text( + "[runtime]\nuser=service_user\nhost=db.internal\nport=5433\ndbname=batch\n", + encoding="utf-8", + ) + monkeypatch.setattr( + pg8000_driver_adapter, + "distribution_version", + lambda package: PG8000_ADMITTED_VERSION, + ) + monkeypatch.setattr(pg8000_driver_adapter, "import_module", lambda name: module) + + driver = load_pg8000_driver(service_file=service_file) + + assert driver.parse_conninfo("service=runtime") == { + "user": "service_user", + "host": "db.internal", + "port": "5433", + "dbname": "batch", + } From 4bafeeffef81b6a2eacc43cbb0a4fa5ff0367c56 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 22:06:27 +0900 Subject: [PATCH 309/338] feat(postgres): add admitted pg8000 production loader --- pg_llm_batch/pg8000_driver_adapter.py | 89 +++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 pg_llm_batch/pg8000_driver_adapter.py diff --git a/pg_llm_batch/pg8000_driver_adapter.py b/pg_llm_batch/pg8000_driver_adapter.py new file mode 100644 index 00000000..7ee6a295 --- /dev/null +++ b/pg_llm_batch/pg8000_driver_adapter.py @@ -0,0 +1,89 @@ +"""Production construction boundary for the admitted pg8000 PostgreSQL driver. + +The underlying pg8000 semantics were proved incrementally behind candidate-only +adapters before production selection. This module adds the missing construction +boundary: it accepts only the exact admitted distribution, imports its DB-API +module lazily, and optionally composes the existing explicit service-file +resolver. It does not change the repository's default runtime selector or +manifest; those remain a separate atomic promotion with lock/SBOM evidence. +""" + +from __future__ import annotations + +from importlib import import_module +from importlib.metadata import PackageNotFoundError, version as distribution_version +from pathlib import Path + +from .pg8000_candidate_driver_port import Pg8000CandidateDriverAdapter +from .pg8000_candidate_service_file import Pg8000CandidateServiceFileResolver +from .postgres_driver_port import PostgresDriverPort + + +PG8000_ADMITTED_VERSION = "1.31.5" + + +class Pg8000DriverUnavailableError(RuntimeError): + """Report that the exact admitted PostgreSQL driver cannot be constructed. + + Diagnostics intentionally omit import paths, package metadata, selectors, + and credentials. Missing unrelated transitive imports are re-raised so a + broken product environment is not misclassified as ordinary driver absence. + """ + + +class Pg8000DriverAdapter(Pg8000CandidateDriverAdapter): + """Expose the fully proved pg8000 port semantics under the production name. + + The implementation deliberately inherits the already exercised cursor, + connection, selector, JSONB, SQLSTATE, and thread-affinity behavior instead + of copying that logic into a second concrete-driver authority. + """ + + +def load_pg8000_driver(*, service_file: Path | None = None) -> PostgresDriverPort: + """Construct only the exact admitted pg8000 artifact behind the canonical port. + + Distribution identity is checked before import so an unreviewed installed + version never executes as database-client authority. Service-file support is + opt-in through one caller-selected path; ambient ``PGSERVICEFILE`` discovery + remains outside the admitted contract. + + Args: + service_file: Optional explicit ``pg_service.conf`` path. When omitted, + ``service=`` selectors remain fail closed. + + Returns: + A canonical PostgreSQL driver port backed by exact pg8000 1.31.5. + + Raises: + Pg8000DriverUnavailableError: If pg8000 is absent, its DB-API module is + absent, or the installed distribution is not the admitted version. + ModuleNotFoundError: If importing pg8000 exposes an unrelated missing + dependency, preserving the packaging defect for root-cause repair. + """ + try: + installed_version = distribution_version("pg8000") + except PackageNotFoundError: + raise Pg8000DriverUnavailableError("PostgreSQL driver is unavailable") from None + + if installed_version != PG8000_ADMITTED_VERSION: + raise Pg8000DriverUnavailableError( + "PostgreSQL driver version is not admitted" + ) + + try: + dbapi_module = import_module("pg8000.dbapi") + except ModuleNotFoundError as exc: + if exc.name not in {"pg8000", "pg8000.dbapi"}: + raise + raise Pg8000DriverUnavailableError("PostgreSQL driver is unavailable") from None + + service_resolver = ( + None + if service_file is None + else Pg8000CandidateServiceFileResolver(service_file) + ) + return Pg8000DriverAdapter( + dbapi_module, + service_resolver=service_resolver, + ) From 2b13090fc5bda3e344f582e9a5328fffb85985db Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 22:09:25 +0900 Subject: [PATCH 310/338] docs(postgres): record admitted pg8000 loader boundary --- docs/product-technical-gap-baseline.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 391a9131..702c84fe 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -22,11 +22,14 @@ Candidate runtime parity now executes against the built pg-llm-batch wheel rathe Candidate supply-chain admission now verifies both the exact five-wheel pg8000 closure and the published pg8000 1.31.5 source distribution before candidate installation. CI pins the wheel and source-distribution SHA-256 digests, reads bounded wheel `METADATA` without importing candidate code, rejects GPL/LGPL/AGPL-family declarations, requires positive reviewed permissive-license evidence for every exact package/version, and compares every Python source path and byte digest under the pg8000 package between the pinned source distribution and universal wheel. The parity verifier does not extract archives, import candidate code, follow archive links, or execute a source build. This closes the published source-to-wheel executable-payload parity gap for the selected candidate artifacts; it does not itself approve a production driver replacement or provide an upstream build attestation. +The lane now also contains a production-construction boundary for the admitted pg8000 artifact. `load_pg8000_driver()` checks the installed distribution identity against exact `1.31.5` before importing `pg8000.dbapi`, exposes the already-proved port semantics through the production-named `Pg8000DriverAdapter`, and accepts service-file authority only through one explicit caller-selected path. Missing pg8000 and version mismatch fail with fixed non-content-bearing diagnostics, while unrelated missing transitive imports remain visible as packaging defects. This deliberately does not switch `retained_postgres_driver()` or the committed dependency graph; runtime promotion and manifest/lock/SBOM convergence remain one later acceptance step so an uninstalled or unreviewed driver cannot become default authority accidentally. + ## Highest-priority gaps | Gap | Current state | Required next evidence | | --- | --- | --- | | Commercial PostgreSQL runtime dependency | P0 / active | Complete issue #322: preserve shipped DB semantics while removing every disallowed GPL/LGPL/AGPL-family runtime package from the committed dependency graph. | +| Candidate-to-production construction | Active Draft | Exact-version lazy construction and explicit service-file composition are implemented behind `PostgresDriverPort`; prove the unchanged final runtime graph after switching the single selector and manifest/lock together. | | Candidate driver contract parity | Active Draft | Real server-terminated-session recovery, the Python 3.10/3.12/3.14 matrix, and built-wheel execution are proven. Close remaining selector/conninfo compatibility, realistic concurrency beyond the deterministic anti-cross-thread guard, timeout/health, and remaining schema/recovery surfaces before production promotion. | | Candidate supply-chain admission | Active / strengthened | Exact wheel/source hashes, source-to-wheel Python payload parity, closure license metadata, and installed-product-wheel execution are gated; complete vulnerability/SBOM/provenance and final production runtime-graph evidence before promotion. | | Dependency-root governance | External owner paths / non-passing | #233 has leaf CI/release/security evidence but still requires authenticated current-head compatibility CodeQL/OpenCode/Noema settlement and a structurally satisfiable independent approval path before normal protected integration. | @@ -55,4 +58,4 @@ Prompt, response, batch-result, and user data remain pg/product-domain data and ## Evidence discipline -Queued, pending, skipped-required, cancelled, absent, predecessor-head, model-only, and status-only evidence is non-passing. A current blocker is the next work item at its actual owner: pg-owned causes require a realistic RED, the smallest causal repair, focused/full GREEN, and exact-head refetch; foreign-owned causes require advancement of the existing owner path followed by independent pg work. A report, comment, handoff, or documentation-only change is never completion while executable code/test/release work remains. +Queued, pending, skipped-required, cancelled, absent, predecessor-head, model-only, and status-only evidence is non-passing. A current blocker is the next work item at its actual owner: pg-owned causes require a realistic RED, the smallest causal repair, focused/full GREEN, and exact-head refetch; foreign-owned causes require advancement of the existing owner path followed by independent pg work. A report, comment, handoff, or documentation-only change is never completion while executable code/test/release work remains. \ No newline at end of file From 34166b456d2df7fe23ebbf4979bc47ee1d2ed200 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 22:35:30 +0900 Subject: [PATCH 311/338] test(postgres): reject shadowed admitted driver origin --- tests/test_pg8000_driver_loader.py | 85 +++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 19 deletions(-) diff --git a/tests/test_pg8000_driver_loader.py b/tests/test_pg8000_driver_loader.py index d85655c1..01fe30af 100644 --- a/tests/test_pg8000_driver_loader.py +++ b/tests/test_pg8000_driver_loader.py @@ -3,7 +3,8 @@ from __future__ import annotations from importlib.metadata import PackageNotFoundError -from types import ModuleType +from pathlib import Path +from types import ModuleType, SimpleNamespace import pytest @@ -16,6 +17,18 @@ ) +_ADMITTED_PACKAGE_ROOT = Path("/opt/admitted/site-packages/pg8000") + + +class _AdmittedDistribution: + """Expose the package path belonging to the reviewed distribution fixture.""" + + def locate_file(self, path: str) -> Path: + """Resolve the package directory without importing candidate code.""" + assert str(path) == "pg8000" + return _ADMITTED_PACKAGE_ROOT + + def _dbapi_module() -> ModuleType: module = ModuleType("pg8000.dbapi") module.apilevel = "2.0" @@ -25,14 +38,34 @@ def _dbapi_module() -> ModuleType: return module -def test_loader_accepts_only_exact_admitted_distribution(monkeypatch) -> None: - imported: list[str] = [] - module = _dbapi_module() +def _install_admitted_distribution_metadata(monkeypatch) -> None: + """Model one exact installed distribution and its matching import origin.""" monkeypatch.setattr( pg8000_driver_adapter, "distribution_version", lambda package: PG8000_ADMITTED_VERSION, ) + monkeypatch.setattr( + pg8000_driver_adapter, + "distribution", + lambda package: _AdmittedDistribution(), + raising=False, + ) + monkeypatch.setattr( + pg8000_driver_adapter, + "find_spec", + lambda name: SimpleNamespace( + origin=str(_ADMITTED_PACKAGE_ROOT / "__init__.py"), + submodule_search_locations=[str(_ADMITTED_PACKAGE_ROOT)], + ), + raising=False, + ) + + +def test_loader_accepts_only_exact_admitted_distribution(monkeypatch) -> None: + imported: list[str] = [] + module = _dbapi_module() + _install_admitted_distribution_metadata(monkeypatch) monkeypatch.setattr( pg8000_driver_adapter, "import_module", @@ -45,6 +78,32 @@ def test_loader_accepts_only_exact_admitted_distribution(monkeypatch) -> None: assert imported == ["pg8000.dbapi"] +def test_loader_rejects_shadow_package_before_import(monkeypatch) -> None: + """A matching distribution version must not authorize shadow package bytes.""" + _install_admitted_distribution_metadata(monkeypatch) + shadow_root = Path("/tmp/untrusted-site-packages/pg8000") + monkeypatch.setattr( + pg8000_driver_adapter, + "find_spec", + lambda name: SimpleNamespace( + origin=str(shadow_root / "__init__.py"), + submodule_search_locations=[str(shadow_root)], + ), + raising=False, + ) + monkeypatch.setattr( + pg8000_driver_adapter, + "import_module", + lambda name: pytest.fail("shadow package code must not execute"), + ) + + with pytest.raises( + Pg8000DriverUnavailableError, + match="^PostgreSQL driver origin is not admitted$", + ): + load_pg8000_driver() + + def test_loader_rejects_unadmitted_version_before_import(monkeypatch) -> None: monkeypatch.setattr( pg8000_driver_adapter, @@ -87,11 +146,7 @@ def missing_distribution(package: str) -> str: def test_loader_normalizes_missing_pg8000_module(monkeypatch) -> None: - monkeypatch.setattr( - pg8000_driver_adapter, - "distribution_version", - lambda package: PG8000_ADMITTED_VERSION, - ) + _install_admitted_distribution_metadata(monkeypatch) def missing_module(name: str) -> ModuleType: raise ModuleNotFoundError("pg8000 missing", name="pg8000.dbapi") @@ -106,11 +161,7 @@ def missing_module(name: str) -> ModuleType: def test_loader_preserves_unrelated_import_failure(monkeypatch) -> None: - monkeypatch.setattr( - pg8000_driver_adapter, - "distribution_version", - lambda package: PG8000_ADMITTED_VERSION, - ) + _install_admitted_distribution_metadata(monkeypatch) def broken_dependency(name: str) -> ModuleType: raise ModuleNotFoundError("dependency missing", name="scramp") @@ -128,11 +179,7 @@ def test_loader_composes_only_explicit_service_file(monkeypatch, tmp_path) -> No "[runtime]\nuser=service_user\nhost=db.internal\nport=5433\ndbname=batch\n", encoding="utf-8", ) - monkeypatch.setattr( - pg8000_driver_adapter, - "distribution_version", - lambda package: PG8000_ADMITTED_VERSION, - ) + _install_admitted_distribution_metadata(monkeypatch) monkeypatch.setattr(pg8000_driver_adapter, "import_module", lambda name: module) driver = load_pg8000_driver(service_file=service_file) From 35731be7ab4ed86e9849ce12ddae117dd9c57de7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 22:42:19 +0900 Subject: [PATCH 312/338] test(postgres): cover admitted driver origin failures --- tests/test_pg8000_driver_loader.py | 43 ++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/test_pg8000_driver_loader.py b/tests/test_pg8000_driver_loader.py index 01fe30af..7210538b 100644 --- a/tests/test_pg8000_driver_loader.py +++ b/tests/test_pg8000_driver_loader.py @@ -104,6 +104,23 @@ def test_loader_rejects_shadow_package_before_import(monkeypatch) -> None: load_pg8000_driver() +def test_loader_rejects_missing_package_spec_before_import(monkeypatch) -> None: + """Missing import authority must fail closed before candidate code executes.""" + _install_admitted_distribution_metadata(monkeypatch) + monkeypatch.setattr(pg8000_driver_adapter, "find_spec", lambda name: None, raising=False) + monkeypatch.setattr( + pg8000_driver_adapter, + "import_module", + lambda name: pytest.fail("unresolved package code must not execute"), + ) + + with pytest.raises( + Pg8000DriverUnavailableError, + match="^PostgreSQL driver origin is not admitted$", + ): + load_pg8000_driver() + + def test_loader_rejects_unadmitted_version_before_import(monkeypatch) -> None: monkeypatch.setattr( pg8000_driver_adapter, @@ -145,6 +162,32 @@ def missing_distribution(package: str) -> str: load_pg8000_driver() +def test_loader_normalizes_distribution_disappearing_before_origin_check(monkeypatch) -> None: + """Metadata disappearance between version and origin checks stays content-free.""" + _install_admitted_distribution_metadata(monkeypatch) + + def missing_distribution(package: str): + raise PackageNotFoundError(package) + + monkeypatch.setattr( + pg8000_driver_adapter, + "distribution", + missing_distribution, + raising=False, + ) + monkeypatch.setattr( + pg8000_driver_adapter, + "import_module", + lambda name: pytest.fail("missing distributions must not be imported"), + ) + + with pytest.raises( + Pg8000DriverUnavailableError, + match="^PostgreSQL driver is unavailable$", + ): + load_pg8000_driver() + + def test_loader_normalizes_missing_pg8000_module(monkeypatch) -> None: _install_admitted_distribution_metadata(monkeypatch) From fa4ec33c9efde5eed6d708daca7e9ba15fbf016b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 22:43:04 +0900 Subject: [PATCH 313/338] fix(postgres): bind pg8000 import origin before execution --- pg_llm_batch/pg8000_driver_adapter.py | 63 ++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/pg_llm_batch/pg8000_driver_adapter.py b/pg_llm_batch/pg8000_driver_adapter.py index 7ee6a295..77c2986c 100644 --- a/pg_llm_batch/pg8000_driver_adapter.py +++ b/pg_llm_batch/pg8000_driver_adapter.py @@ -2,16 +2,23 @@ The underlying pg8000 semantics were proved incrementally behind candidate-only adapters before production selection. This module adds the missing construction -boundary: it accepts only the exact admitted distribution, imports its DB-API -module lazily, and optionally composes the existing explicit service-file -resolver. It does not change the repository's default runtime selector or -manifest; those remain a separate atomic promotion with lock/SBOM evidence. +boundary: it accepts only the exact admitted distribution, verifies that the +importable package resolves to that distribution before executing it, imports +its DB-API module lazily, and optionally composes the existing explicit +service-file resolver. It does not change the repository's default runtime +selector or manifest; those remain a separate atomic promotion with lock/SBOM +evidence. """ from __future__ import annotations from importlib import import_module -from importlib.metadata import PackageNotFoundError, version as distribution_version +from importlib.metadata import ( + PackageNotFoundError, + distribution, + version as distribution_version, +) +from importlib.util import find_spec from pathlib import Path from .pg8000_candidate_driver_port import Pg8000CandidateDriverAdapter @@ -40,13 +47,45 @@ class Pg8000DriverAdapter(Pg8000CandidateDriverAdapter): """ +def _require_admitted_pg8000_origin() -> None: + """Reject import-path shadowing before any pg8000 package code executes.""" + try: + installed_distribution = distribution("pg8000") + except PackageNotFoundError: + raise Pg8000DriverUnavailableError("PostgreSQL driver is unavailable") from None + + expected_root = Path(installed_distribution.locate_file("pg8000")).resolve() + package_spec = find_spec("pg8000") + if ( + package_spec is None + or package_spec.origin is None + or package_spec.submodule_search_locations is None + ): + raise Pg8000DriverUnavailableError( + "PostgreSQL driver origin is not admitted" + ) + + observed_origin = Path(package_spec.origin).resolve() + observed_roots = tuple( + Path(location).resolve() for location in package_spec.submodule_search_locations + ) + if ( + observed_origin != expected_root / "__init__.py" + or observed_roots != (expected_root,) + ): + raise Pg8000DriverUnavailableError( + "PostgreSQL driver origin is not admitted" + ) + + def load_pg8000_driver(*, service_file: Path | None = None) -> PostgresDriverPort: """Construct only the exact admitted pg8000 artifact behind the canonical port. - Distribution identity is checked before import so an unreviewed installed - version never executes as database-client authority. Service-file support is - opt-in through one caller-selected path; ambient ``PGSERVICEFILE`` discovery - remains outside the admitted contract. + Distribution identity and import origin are checked before package code is + executed so an unreviewed version or a shadow package cannot become database + client authority. Service-file support is opt-in through one caller-selected + path; ambient ``PGSERVICEFILE`` discovery remains outside the admitted + contract. Args: service_file: Optional explicit ``pg_service.conf`` path. When omitted, @@ -56,8 +95,8 @@ def load_pg8000_driver(*, service_file: Path | None = None) -> PostgresDriverPor A canonical PostgreSQL driver port backed by exact pg8000 1.31.5. Raises: - Pg8000DriverUnavailableError: If pg8000 is absent, its DB-API module is - absent, or the installed distribution is not the admitted version. + Pg8000DriverUnavailableError: If pg8000 is absent, its version or import + origin is not admitted, or its DB-API module is absent. ModuleNotFoundError: If importing pg8000 exposes an unrelated missing dependency, preserving the packaging defect for root-cause repair. """ @@ -71,6 +110,8 @@ def load_pg8000_driver(*, service_file: Path | None = None) -> PostgresDriverPor "PostgreSQL driver version is not admitted" ) + _require_admitted_pg8000_origin() + try: dbapi_module = import_module("pg8000.dbapi") except ModuleNotFoundError as exc: From 4f797e653a2b465c0bf78318613b4284bb392622 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 22:44:02 +0900 Subject: [PATCH 314/338] docs(postgres): record pre-import driver origin authority --- docs/product-technical-gap-baseline.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 702c84fe..248f6595 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -22,16 +22,16 @@ Candidate runtime parity now executes against the built pg-llm-batch wheel rathe Candidate supply-chain admission now verifies both the exact five-wheel pg8000 closure and the published pg8000 1.31.5 source distribution before candidate installation. CI pins the wheel and source-distribution SHA-256 digests, reads bounded wheel `METADATA` without importing candidate code, rejects GPL/LGPL/AGPL-family declarations, requires positive reviewed permissive-license evidence for every exact package/version, and compares every Python source path and byte digest under the pg8000 package between the pinned source distribution and universal wheel. The parity verifier does not extract archives, import candidate code, follow archive links, or execute a source build. This closes the published source-to-wheel executable-payload parity gap for the selected candidate artifacts; it does not itself approve a production driver replacement or provide an upstream build attestation. -The lane now also contains a production-construction boundary for the admitted pg8000 artifact. `load_pg8000_driver()` checks the installed distribution identity against exact `1.31.5` before importing `pg8000.dbapi`, exposes the already-proved port semantics through the production-named `Pg8000DriverAdapter`, and accepts service-file authority only through one explicit caller-selected path. Missing pg8000 and version mismatch fail with fixed non-content-bearing diagnostics, while unrelated missing transitive imports remain visible as packaging defects. This deliberately does not switch `retained_postgres_driver()` or the committed dependency graph; runtime promotion and manifest/lock/SBOM convergence remain one later acceptance step so an uninstalled or unreviewed driver cannot become default authority accidentally. +The lane now also contains a production-construction boundary for the admitted pg8000 artifact. `load_pg8000_driver()` checks the installed distribution version against exact `1.31.5`, resolves that distribution's installed `pg8000` package root, and compares the top-level import spec origin and package search location with that root before any pg8000 package code executes. A same-version distribution therefore cannot authorize an earlier shadow package on the import path. Missing pg8000, version mismatch, missing import authority, and origin mismatch fail with fixed non-content-bearing diagnostics, while unrelated missing transitive imports remain visible as packaging defects. Service-file authority remains available only through one explicit caller-selected path. This deliberately does not switch `retained_postgres_driver()` or the committed dependency graph; runtime promotion and manifest/lock/SBOM convergence remain one later acceptance step so an uninstalled or unreviewed driver cannot become default authority accidentally. ## Highest-priority gaps | Gap | Current state | Required next evidence | | --- | --- | --- | | Commercial PostgreSQL runtime dependency | P0 / active | Complete issue #322: preserve shipped DB semantics while removing every disallowed GPL/LGPL/AGPL-family runtime package from the committed dependency graph. | -| Candidate-to-production construction | Active Draft | Exact-version lazy construction and explicit service-file composition are implemented behind `PostgresDriverPort`; prove the unchanged final runtime graph after switching the single selector and manifest/lock together. | +| Candidate-to-production construction | Active Draft | Exact-version lazy construction, pre-execution distribution/import-origin binding, and explicit service-file composition are implemented behind `PostgresDriverPort`; prove the unchanged final runtime graph after switching the single selector and manifest/lock together. | | Candidate driver contract parity | Active Draft | Real server-terminated-session recovery, the Python 3.10/3.12/3.14 matrix, and built-wheel execution are proven. Close remaining selector/conninfo compatibility, realistic concurrency beyond the deterministic anti-cross-thread guard, timeout/health, and remaining schema/recovery surfaces before production promotion. | -| Candidate supply-chain admission | Active / strengthened | Exact wheel/source hashes, source-to-wheel Python payload parity, closure license metadata, and installed-product-wheel execution are gated; complete vulnerability/SBOM/provenance and final production runtime-graph evidence before promotion. | +| Candidate supply-chain admission | Active / strengthened | Exact wheel/source hashes, source-to-wheel Python payload parity, closure license metadata, installed-product-wheel execution, and pre-import origin binding are gated; complete vulnerability/SBOM/provenance and final production runtime-graph evidence before promotion. | | Dependency-root governance | External owner paths / non-passing | #233 has leaf CI/release/security evidence but still requires authenticated current-head compatibility CodeQL/OpenCode/Noema settlement and a structurally satisfiable independent approval path before normal protected integration. | | Immutable product release | Not yet published | After the production dependency replacement and all gates pass on one integrated protected head, perform version/CHANGELOG/tag/package/SBOM/provenance/reproducibility/rollback publication and verify artifact identity. | | Context Graph / EA projection | Candidate-only until released authority exists | `context-graph-contracts` and `enterprise-architecture-core` currently expose no immutable GitHub release. Do not pin mutable producer heads. Continue pg-owned release-readiness seams and adopt only a verified released contract. | @@ -58,4 +58,4 @@ Prompt, response, batch-result, and user data remain pg/product-domain data and ## Evidence discipline -Queued, pending, skipped-required, cancelled, absent, predecessor-head, model-only, and status-only evidence is non-passing. A current blocker is the next work item at its actual owner: pg-owned causes require a realistic RED, the smallest causal repair, focused/full GREEN, and exact-head refetch; foreign-owned causes require advancement of the existing owner path followed by independent pg work. A report, comment, handoff, or documentation-only change is never completion while executable code/test/release work remains. \ No newline at end of file +Queued, pending, skipped-required, cancelled, absent, predecessor-head, model-only, and status-only evidence is non-passing. A current blocker is the next work item at its actual owner: pg-owned causes require a realistic RED, the smallest causal repair, focused/full GREEN, and exact-head refetch; foreign-owned causes require advancement of the existing owner path followed by independent pg work. A report, comment, handoff, or documentation-only change is never completion while executable code/test/release work remains. From c12602155e5e9bac52691e091a814baad7975df6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 22:45:42 +0900 Subject: [PATCH 315/338] test(postgres): prove installed driver rejects path shadowing --- tests/smoke_pg8000_driver_loader_origin.py | 80 ++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 tests/smoke_pg8000_driver_loader_origin.py diff --git a/tests/smoke_pg8000_driver_loader_origin.py b/tests/smoke_pg8000_driver_loader_origin.py new file mode 100644 index 00000000..fd462150 --- /dev/null +++ b/tests/smoke_pg8000_driver_loader_origin.py @@ -0,0 +1,80 @@ +"""Prove the installed pg8000 loader rejects import-path shadowing pre-execution. + +CI runs this script outside the checkout in isolated Python environments that +contain the built pg-llm-batch wheel plus the exact admitted pg8000 closure. The +shadow package deliberately has no distribution metadata, so the real installed +distribution remains discoverable while Python's import path resolves the +package name to different bytes. The loader must reject that split authority +before the shadow package initializer executes. +""" + +from __future__ import annotations + +import os +from pathlib import Path +import subprocess +import sys +import tempfile + +from pg_llm_batch.pg8000_driver_adapter import Pg8000DriverAdapter, load_pg8000_driver + + +def _assert_shadow_package_rejected_before_execution() -> None: + """Reject a higher-priority package path without executing its initializer.""" + with tempfile.TemporaryDirectory(prefix="pg8000-shadow-") as directory: + shadow_root = Path(directory) + package_root = shadow_root / "pg8000" + package_root.mkdir() + marker = shadow_root / "shadow-executed" + package_root.joinpath("__init__.py").write_text( + "from pathlib import Path\n" + f"Path({str(marker)!r}).write_text('executed', encoding='utf-8')\n" + "raise RuntimeError('shadow package executed')\n", + encoding="utf-8", + ) + + environment = dict(os.environ) + previous_pythonpath = environment.get("PYTHONPATH") + environment["PYTHONPATH"] = ( + str(shadow_root) + if not previous_pythonpath + else str(shadow_root) + os.pathsep + previous_pythonpath + ) + probe = subprocess.run( + [ + sys.executable, + "-c", + ( + "from pg_llm_batch.pg8000_driver_adapter import " + "Pg8000DriverUnavailableError, load_pg8000_driver\n" + "try:\n" + " load_pg8000_driver()\n" + "except Pg8000DriverUnavailableError as exc:\n" + " assert str(exc) == 'PostgreSQL driver origin is not admitted'\n" + "else:\n" + " raise AssertionError('shadow package was admitted')\n" + ), + ], + env=environment, + cwd=shadow_root, + capture_output=True, + text=True, + check=False, + ) + if probe.returncode != 0: + raise AssertionError("shadow-origin probe did not fail closed") + if marker.exists(): + raise AssertionError("shadow package initializer executed before rejection") + + +def main() -> int: + """Run shadow rejection first, then prove the installed admitted path loads.""" + _assert_shadow_package_rejected_before_execution() + driver = load_pg8000_driver() + if not isinstance(driver, Pg8000DriverAdapter): + raise AssertionError("admitted installed driver did not load") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From e21bdbd1821d370a9fb89502226086bf0393e600 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 22:46:46 +0900 Subject: [PATCH 316/338] test(postgres): remove unhooked origin smoke --- tests/smoke_pg8000_driver_loader_origin.py | 80 ---------------------- 1 file changed, 80 deletions(-) delete mode 100644 tests/smoke_pg8000_driver_loader_origin.py diff --git a/tests/smoke_pg8000_driver_loader_origin.py b/tests/smoke_pg8000_driver_loader_origin.py deleted file mode 100644 index fd462150..00000000 --- a/tests/smoke_pg8000_driver_loader_origin.py +++ /dev/null @@ -1,80 +0,0 @@ -"""Prove the installed pg8000 loader rejects import-path shadowing pre-execution. - -CI runs this script outside the checkout in isolated Python environments that -contain the built pg-llm-batch wheel plus the exact admitted pg8000 closure. The -shadow package deliberately has no distribution metadata, so the real installed -distribution remains discoverable while Python's import path resolves the -package name to different bytes. The loader must reject that split authority -before the shadow package initializer executes. -""" - -from __future__ import annotations - -import os -from pathlib import Path -import subprocess -import sys -import tempfile - -from pg_llm_batch.pg8000_driver_adapter import Pg8000DriverAdapter, load_pg8000_driver - - -def _assert_shadow_package_rejected_before_execution() -> None: - """Reject a higher-priority package path without executing its initializer.""" - with tempfile.TemporaryDirectory(prefix="pg8000-shadow-") as directory: - shadow_root = Path(directory) - package_root = shadow_root / "pg8000" - package_root.mkdir() - marker = shadow_root / "shadow-executed" - package_root.joinpath("__init__.py").write_text( - "from pathlib import Path\n" - f"Path({str(marker)!r}).write_text('executed', encoding='utf-8')\n" - "raise RuntimeError('shadow package executed')\n", - encoding="utf-8", - ) - - environment = dict(os.environ) - previous_pythonpath = environment.get("PYTHONPATH") - environment["PYTHONPATH"] = ( - str(shadow_root) - if not previous_pythonpath - else str(shadow_root) + os.pathsep + previous_pythonpath - ) - probe = subprocess.run( - [ - sys.executable, - "-c", - ( - "from pg_llm_batch.pg8000_driver_adapter import " - "Pg8000DriverUnavailableError, load_pg8000_driver\n" - "try:\n" - " load_pg8000_driver()\n" - "except Pg8000DriverUnavailableError as exc:\n" - " assert str(exc) == 'PostgreSQL driver origin is not admitted'\n" - "else:\n" - " raise AssertionError('shadow package was admitted')\n" - ), - ], - env=environment, - cwd=shadow_root, - capture_output=True, - text=True, - check=False, - ) - if probe.returncode != 0: - raise AssertionError("shadow-origin probe did not fail closed") - if marker.exists(): - raise AssertionError("shadow package initializer executed before rejection") - - -def main() -> int: - """Run shadow rejection first, then prove the installed admitted path loads.""" - _assert_shadow_package_rejected_before_execution() - driver = load_pg8000_driver() - if not isinstance(driver, Pg8000DriverAdapter): - raise AssertionError("admitted installed driver did not load") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) From 52356d30b07d3cecdd25f94da8718ded0605c96d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 23:05:41 +0900 Subject: [PATCH 317/338] test(postgres): require admitted runtime driver selection --- .../test_postgres_driver_runtime_selection.py | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/tests/test_postgres_driver_runtime_selection.py b/tests/test_postgres_driver_runtime_selection.py index b7240c0a..9b66d396 100644 --- a/tests/test_postgres_driver_runtime_selection.py +++ b/tests/test_postgres_driver_runtime_selection.py @@ -1,13 +1,15 @@ -"""Regressions for the retained PostgreSQL driver selection boundary. +"""Regressions for the canonical PostgreSQL driver selection boundary. -The commercial driver migration must leave concrete Psycopg authority in one -infrastructure adapter rather than importing the package from each bounded -context. These tests exercise the default connection path through a lazy runtime -selector while preserving explicit driver injection. +Bounded contexts acquire PostgreSQL capability through one lazy runtime selector +rather than importing a concrete client. The commercial migration keeps explicit +driver injection available while requiring the default selector to construct the +exact admitted production adapter. """ from __future__ import annotations +import sys +from types import ModuleType from typing import Any import pg_llm_batch.checkpoint_store as checkpoint_store @@ -15,6 +17,7 @@ import pg_llm_batch.db as db import pg_llm_batch.health as health import pg_llm_batch.orchestrator as orchestrator +import pg_llm_batch.postgres_driver_runtime as postgres_driver_runtime import pg_llm_batch.token_counter as token_counter from pg_llm_batch.postgres_driver_runtime import retained_postgres_driver @@ -106,7 +109,7 @@ def test_token_counter_default_driver_uses_runtime_selector(monkeypatch) -> None def test_orchestrator_default_driver_uses_runtime_selector(monkeypatch) -> None: - """Batch assembly must not retain a direct concrete Psycopg authority path.""" + """Batch assembly must not retain a direct concrete-driver authority path.""" driver = _Driver() monkeypatch.setattr(orchestrator, "retained_postgres_driver", lambda: driver) @@ -115,6 +118,18 @@ def test_orchestrator_default_driver_uses_runtime_selector(monkeypatch) -> None: assert service._postgres_driver is driver +def test_runtime_selector_constructs_admitted_pg8000_loader(monkeypatch) -> None: + """The default selector must delegate to the admitted pg8000 loader only.""" + driver = _Driver() + module = ModuleType("pg_llm_batch.pg8000_driver_adapter") + module.load_pg8000_driver = lambda: driver # type: ignore[attr-defined] + monkeypatch.setitem(sys.modules, module.__name__, module) + + selected = postgres_driver_runtime.retained_postgres_driver() + + assert selected is driver + + def test_runtime_selector_returns_postgres_driver_port() -> None: """The retained selector must expose only the provider-neutral driver port.""" driver = retained_postgres_driver() From 457f2040c5bb360eb238a33ad9f9166e1d9e659b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 23:14:07 +0900 Subject: [PATCH 318/338] fix(postgres): select admitted pg8000 runtime adapter --- pg_llm_batch/postgres_driver_runtime.py | 39 ++++++++++++------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/pg_llm_batch/postgres_driver_runtime.py b/pg_llm_batch/postgres_driver_runtime.py index 586a7dcf..cfb17bda 100644 --- a/pg_llm_batch/postgres_driver_runtime.py +++ b/pg_llm_batch/postgres_driver_runtime.py @@ -1,10 +1,9 @@ -"""Runtime selection for the retained PostgreSQL driver implementation. +"""Runtime selection for the admitted PostgreSQL driver implementation. -Concrete database-client authority belongs at one infrastructure boundary while -pg-llm-batch migrates away from Psycopg. Bounded contexts consume only -:class:`PostgresDriverPort`; this module lazily constructs the retained adapter -until a commercially admitted replacement is ready. Keeping the import lazy -also preserves explicit driver injection for candidate and degraded-mode tests. +Concrete database-client authority belongs at one infrastructure boundary. +Bounded contexts consume only :class:`PostgresDriverPort`; this module lazily +constructs the exact admitted pg8000 adapter while preserving explicit driver +injection for tests and alternate infrastructure wiring. """ from __future__ import annotations @@ -13,28 +12,28 @@ class PostgresDriverUnavailableError(RuntimeError): - """Report that the retained PostgreSQL client cannot be constructed. + """Report that the admitted PostgreSQL client cannot be constructed. The fixed diagnostic deliberately omits import paths, environment details, - DSNs, and credentials. Import failures unrelated to Psycopg are re-raised so - packaging defects are not misclassified as an optional-client absence. + DSNs, credentials, distribution metadata, and package-selection details. + Unexpected package defects remain distinguishable from ordinary admitted + driver absence. """ def retained_postgres_driver() -> PostgresDriverPort: - """Return the currently retained concrete driver behind the neutral port. + """Return the single admitted concrete driver behind the neutral port. - Psycopg remains a temporary migration baseline only. The import lives here - so callers do not acquire a second concrete-driver dependency and the future - production replacement can be switched at one reviewed runtime boundary. + The production construction boundary verifies the exact pg8000 distribution + version and import origin before package code executes. Keeping construction + centralized prevents bounded contexts from acquiring a second concrete + database-client authority while the package graph is promoted. """ + from . import pg8000_driver_adapter + try: - from .psycopg_driver_adapter import PsycopgDriverAdapter - except ModuleNotFoundError as exc: - missing_name = exc.name or "" - if missing_name != "psycopg" and not missing_name.startswith("psycopg."): - raise + return pg8000_driver_adapter.load_pg8000_driver() + except pg8000_driver_adapter.Pg8000DriverUnavailableError: raise PostgresDriverUnavailableError( - "Retained PostgreSQL driver is unavailable" + "PostgreSQL driver is unavailable" ) from None - return PsycopgDriverAdapter() From 2d848007ed3cddede77688a5de2c36e54db5f123 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 23:40:24 +0900 Subject: [PATCH 319/338] build(postgres): stage admitted pg8000 package graph RED --- .github/workflows/lock-diagnostic.yml | 44 +++++++++++++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/lock-diagnostic.yml diff --git a/.github/workflows/lock-diagnostic.yml b/.github/workflows/lock-diagnostic.yml new file mode 100644 index 00000000..a9aad3d2 --- /dev/null +++ b/.github/workflows/lock-diagnostic.yml @@ -0,0 +1,44 @@ +name: Lock Diagnostic + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: lock-diagnostic-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + regenerate-lock: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Harden runner + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 + with: + egress-policy: audit + - name: Checkout exact PR head + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + - name: Set up uv + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 + with: + prune-cache: true + - name: Regenerate lock and emit compressed diagnostic + shell: bash + run: | + uv lock + uv lock --check + echo LOCK_GZIP_BASE64_BEGIN + gzip -9 -c uv.lock | base64 -w0 + echo + echo LOCK_GZIP_BASE64_END diff --git a/pyproject.toml b/pyproject.toml index 701c84e7..45fb30e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ license-files = ["LICENSE", "NOTICE"] authors = [{ name = "ContextualWisdomLab" }] keywords = ["postgres", "llm", "batch", "pg_tiktoken", "tokenization"] dependencies = [ - "psycopg[binary]>=3.1", + "pg8000==1.31.5", "aiohttp>=3.14.3", ] From 4991b54929d7b237c936dbecd3339ee0a81de09e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 23:41:41 +0900 Subject: [PATCH 320/338] test(postgres): isolate Psycopg to non-runtime verification --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 45fb30e4..25a4951c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,8 @@ test = [ "pytest>=7.4", "pytest-asyncio>=0.23", "cryptography>=50.0.0", + # Legacy Psycopg adapter verification only; never part of the default runtime graph. + "psycopg[binary]==3.3.4", ] # PEP 735 dependency groups. `uv run` installs the `dev` group by default, so the @@ -36,6 +38,8 @@ dev = [ "pytest-asyncio>=0.23", "cryptography>=50.0.0", "ruff==0.16.1", + # Legacy Psycopg adapter verification only; `--no-dev` production installs exclude it. + "psycopg[binary]==3.3.4", ] [project.scripts] From 8972ec9a1f1e94ad40b5490be88e5e53d1dd200b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 23:50:33 +0900 Subject: [PATCH 321/338] ci(lock): expose regenerated lock evidence --- .github/workflows/lock-diagnostic.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/lock-diagnostic.yml b/.github/workflows/lock-diagnostic.yml index a9aad3d2..377e1963 100644 --- a/.github/workflows/lock-diagnostic.yml +++ b/.github/workflows/lock-diagnostic.yml @@ -33,12 +33,14 @@ jobs: uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: prune-cache: true - - name: Regenerate lock and emit compressed diagnostic - shell: bash + - name: Regenerate lock run: | uv lock uv lock --check - echo LOCK_GZIP_BASE64_BEGIN - gzip -9 -c uv.lock | base64 -w0 - echo - echo LOCK_GZIP_BASE64_END + - name: Upload regenerated lock + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: regenerated-uv-lock + path: uv.lock + if-no-files-found: error + retention-days: 1 From ee46bce5db90a0b327127860e91eef359223a0c4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 00:08:55 +0900 Subject: [PATCH 322/338] ci(lock): finalize admitted PostgreSQL runtime graph --- .github/workflows/lock-diagnostic.yml | 113 ++++++++++++++++++++++---- 1 file changed, 95 insertions(+), 18 deletions(-) diff --git a/.github/workflows/lock-diagnostic.yml b/.github/workflows/lock-diagnostic.yml index 377e1963..179aefb5 100644 --- a/.github/workflows/lock-diagnostic.yml +++ b/.github/workflows/lock-diagnostic.yml @@ -1,46 +1,123 @@ -name: Lock Diagnostic +name: Finalize Commercial PostgreSQL Lock on: - pull_request: - workflow_dispatch: + push: + branches: + - feat/commercial-postgres-driver-port-b84f0c9 + paths: + - .github/workflows/lock-diagnostic.yml permissions: contents: read concurrency: - group: lock-diagnostic-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + group: finalize-commercial-postgres-lock + cancel-in-progress: false jobs: - regenerate-lock: + finalize-lock: runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 20 + permissions: + contents: write steps: - name: Harden runner uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 with: egress-policy: audit - - name: Checkout exact PR head + + - name: Checkout exact branch head uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} + ref: ${{ github.sha }} + fetch-depth: 0 persist-credentials: false + + - name: Verify exact source + shell: bash + run: test "$(git rev-parse HEAD)" = "${{ github.sha }}" + - name: Set up Python uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.14" - - name: Set up uv + + - name: Set up repository-pinned uv uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: + version: "0.12.3" prune-cache: true - - name: Regenerate lock + + - name: Regenerate and verify production runtime lock + shell: bash run: | + set -euo pipefail uv lock uv lock --check - - name: Upload regenerated lock - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: regenerated-uv-lock - path: uv.lock - if-no-files-found: error - retention-days: 1 + if git diff --quiet -- uv.lock; then + echo "Expected the stale committed lock to change" >&2 + exit 1 + fi + + python - <<'PY' + import tomllib + from pathlib import Path + + lock = tomllib.loads(Path("uv.lock").read_text()) + project = next(package for package in lock["package"] if package["name"] == "pg-llm-batch") + runtime = {dependency["name"] for dependency in project["dependencies"]} + assert "pg8000" in runtime + assert "psycopg" not in runtime + pg8000 = next(package for package in lock["package"] if package["name"] == "pg8000") + assert pg8000["version"] == "1.31.5" + PY + + uv sync --locked --no-dev + .venv/bin/python - <<'PY' + from importlib.metadata import PackageNotFoundError, version + + assert version("pg8000") == "1.31.5" + try: + version("psycopg") + except PackageNotFoundError: + pass + else: + raise AssertionError("Psycopg must not be installed in the default runtime graph") + + from pg_llm_batch.pg8000_driver_adapter import Pg8000DriverAdapter + from pg_llm_batch.postgres_driver_runtime import retained_postgres_driver + + assert isinstance(retained_postgres_driver(), Pg8000DriverAdapter) + PY + + uv build --no-sources + + - name: Commit verified lock and remove temporary finalizer + shell: bash + env: + GH_TOKEN: ${{ github.token }} + BRANCH_NAME: feat/commercial-postgres-driver-port-b84f0c9 + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + + authenticated_remote="https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + remote_head="$(git ls-remote "$authenticated_remote" "refs/heads/${BRANCH_NAME}" | awk '{print $1}')" + test "$remote_head" = "$GITHUB_SHA" + + rm -- .github/workflows/lock-diagnostic.yml + git diff --check + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add uv.lock .github/workflows/lock-diagnostic.yml + + actual_paths="$(git diff --cached --name-only | LC_ALL=C sort)" + expected_paths="$(printf '%s\n' \ + .github/workflows/lock-diagnostic.yml \ + uv.lock | LC_ALL=C sort)" + test "$actual_paths" = "$expected_paths" + + git commit -m "build(postgres): lock admitted pg8000 runtime graph" + git remote set-url origin "$authenticated_remote" + git push origin "HEAD:refs/heads/${BRANCH_NAME}" From e1045b6ed74e848cd99a50b02b42fe731fcc8b9b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:11:23 +0000 Subject: [PATCH 323/338] build(postgres): lock admitted pg8000 runtime graph --- .github/workflows/lock-diagnostic.yml | 123 -------------------------- uv.lock | 63 ++++++++++++- 2 files changed, 61 insertions(+), 125 deletions(-) delete mode 100644 .github/workflows/lock-diagnostic.yml diff --git a/.github/workflows/lock-diagnostic.yml b/.github/workflows/lock-diagnostic.yml deleted file mode 100644 index 179aefb5..00000000 --- a/.github/workflows/lock-diagnostic.yml +++ /dev/null @@ -1,123 +0,0 @@ -name: Finalize Commercial PostgreSQL Lock - -on: - push: - branches: - - feat/commercial-postgres-driver-port-b84f0c9 - paths: - - .github/workflows/lock-diagnostic.yml - -permissions: - contents: read - -concurrency: - group: finalize-commercial-postgres-lock - cancel-in-progress: false - -jobs: - finalize-lock: - runs-on: ubuntu-latest - timeout-minutes: 20 - permissions: - contents: write - steps: - - name: Harden runner - uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 - with: - egress-policy: audit - - - name: Checkout exact branch head - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ github.sha }} - fetch-depth: 0 - persist-credentials: false - - - name: Verify exact source - shell: bash - run: test "$(git rev-parse HEAD)" = "${{ github.sha }}" - - - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.14" - - - name: Set up repository-pinned uv - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 - with: - version: "0.12.3" - prune-cache: true - - - name: Regenerate and verify production runtime lock - shell: bash - run: | - set -euo pipefail - uv lock - uv lock --check - if git diff --quiet -- uv.lock; then - echo "Expected the stale committed lock to change" >&2 - exit 1 - fi - - python - <<'PY' - import tomllib - from pathlib import Path - - lock = tomllib.loads(Path("uv.lock").read_text()) - project = next(package for package in lock["package"] if package["name"] == "pg-llm-batch") - runtime = {dependency["name"] for dependency in project["dependencies"]} - assert "pg8000" in runtime - assert "psycopg" not in runtime - pg8000 = next(package for package in lock["package"] if package["name"] == "pg8000") - assert pg8000["version"] == "1.31.5" - PY - - uv sync --locked --no-dev - .venv/bin/python - <<'PY' - from importlib.metadata import PackageNotFoundError, version - - assert version("pg8000") == "1.31.5" - try: - version("psycopg") - except PackageNotFoundError: - pass - else: - raise AssertionError("Psycopg must not be installed in the default runtime graph") - - from pg_llm_batch.pg8000_driver_adapter import Pg8000DriverAdapter - from pg_llm_batch.postgres_driver_runtime import retained_postgres_driver - - assert isinstance(retained_postgres_driver(), Pg8000DriverAdapter) - PY - - uv build --no-sources - - - name: Commit verified lock and remove temporary finalizer - shell: bash - env: - GH_TOKEN: ${{ github.token }} - BRANCH_NAME: feat/commercial-postgres-driver-port-b84f0c9 - run: | - set -euo pipefail - test "$(git rev-parse HEAD)" = "$GITHUB_SHA" - - authenticated_remote="https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" - remote_head="$(git ls-remote "$authenticated_remote" "refs/heads/${BRANCH_NAME}" | awk '{print $1}')" - test "$remote_head" = "$GITHUB_SHA" - - rm -- .github/workflows/lock-diagnostic.yml - git diff --check - - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add uv.lock .github/workflows/lock-diagnostic.yml - - actual_paths="$(git diff --cached --name-only | LC_ALL=C sort)" - expected_paths="$(printf '%s\n' \ - .github/workflows/lock-diagnostic.yml \ - uv.lock | LC_ALL=C sort)" - test "$actual_paths" = "$expected_paths" - - git commit -m "build(postgres): lock admitted pg8000 runtime graph" - git remote set-url origin "$authenticated_remote" - git push origin "HEAD:refs/heads/${BRANCH_NAME}" diff --git a/uv.lock b/uv.lock index 47b9e2dc..f17d8819 100644 --- a/uv.lock +++ b/uv.lock @@ -161,6 +161,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, ] +[[package]] +name = "asn1crypto" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/cf/d547feed25b5244fcb9392e288ff9fdc3280b10260362fc45d37a798a6ee/asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c", size = 121080, upload-time = "2022-03-15T14:46:52.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67", size = 105045, upload-time = "2022-03-15T14:46:51.055Z" }, +] + [[package]] name = "async-timeout" version = "5.0.1" @@ -680,7 +689,7 @@ version = "0.1.0" source = { editable = "." } dependencies = [ { name = "aiohttp" }, - { name = "psycopg", extra = ["binary"] }, + { name = "pg8000" }, ] [package.optional-dependencies] @@ -692,6 +701,7 @@ secrets = [ ] test = [ { name = "cryptography" }, + { name = "psycopg", extra = ["binary"] }, { name = "pytest" }, { name = "pytest-asyncio" }, ] @@ -699,6 +709,7 @@ test = [ [package.dev-dependencies] dev = [ { name = "cryptography" }, + { name = "psycopg", extra = ["binary"] }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "ruff" }, @@ -710,7 +721,8 @@ requires-dist = [ { name = "cryptography", marker = "extra == 'secrets'", specifier = ">=50.0.0" }, { name = "cryptography", marker = "extra == 'test'", specifier = ">=50.0.0" }, { name = "opentelemetry-api", marker = "extra == 'observability'", specifier = ">=1.44,<2" }, - { name = "psycopg", extras = ["binary"], specifier = ">=3.1" }, + { name = "pg8000", specifier = "==1.31.5" }, + { name = "psycopg", extras = ["binary"], marker = "extra == 'test'", specifier = "==3.3.4" }, { name = "pytest", marker = "extra == 'test'", specifier = ">=7.4" }, { name = "pytest-asyncio", marker = "extra == 'test'", specifier = ">=0.23" }, ] @@ -719,11 +731,25 @@ provides-extras = ["observability", "secrets", "test"] [package.metadata.requires-dev] dev = [ { name = "cryptography", specifier = ">=50.0.0" }, + { name = "psycopg", extras = ["binary"], specifier = "==3.3.4" }, { name = "pytest", specifier = ">=7.4" }, { name = "pytest-asyncio", specifier = ">=0.23" }, { name = "ruff", specifier = "==0.16.1" }, ] +[[package]] +name = "pg8000" +version = "1.31.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "scramp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c8/9a/077ab21e700051e03d8c5232b6bcb9a1a4d4b6242c9a0226df2cfa306414/pg8000-1.31.5.tar.gz", hash = "sha256:46ebb03be52b7a77c03c725c79da2ca281d6e8f59577ca66b17c9009618cae78", size = 118933, upload-time = "2025-09-14T09:16:49.748Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/07/5fd183858dff4d24840f07fc845f213cd371a19958558607ba22035dadd7/pg8000-1.31.5-py3-none-any.whl", hash = "sha256:0af2c1926b153307639868d2ee5cef6cd3a7d07448e12736989b10e1d491e201", size = 57816, upload-time = "2025-09-14T09:16:47.798Z" }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -991,6 +1017,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, ] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + [[package]] name = "ruff" version = "0.16.1" @@ -1016,6 +1054,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/46/240ea004bf6dc4feb40e9832f2205a476a47dd5b8a3f8211a5fc5f95e20e/ruff-0.16.1-py3-none-win_arm64.whl", hash = "sha256:dbaadaac38c70239f056d306b7476f246b0bf000fa6b3876402acbf5b227eaf8", size = 11309414, upload-time = "2026-07-30T19:36:58.79Z" }, ] +[[package]] +name = "scramp" +version = "1.4.17" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asn1crypto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/76/6db02f36db58a7d009e90f51e961bcc3c44a1c930a744f026e30e791989b/scramp-1.4.17.tar.gz", hash = "sha256:28970f29ebc33df47f9975c805e5e5a360effe5b31045e607d64b3b60370dba1", size = 21291, upload-time = "2026-08-07T17:19:40.827Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/99/0e372781210cd36b2f2727e5be3ea93066edad7edd6fa2dfdec3b3e28845/scramp-1.4.17-py3-none-any.whl", hash = "sha256:a4e3fd2e8169461a28a13777a166d3da94274454f0714a7d3023fee124474ac8", size = 16131, upload-time = "2026-08-07T17:19:39.591Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + [[package]] name = "tomli" version = "2.4.1" From 35a99c4d0154502dbaf75b3edaef5894a2247a1f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 00:13:56 +0900 Subject: [PATCH 324/338] docs(postgres): converge promoted runtime authority --- docs/product-technical-gap-baseline.md | 45 ++++++++++++++------------ pg_llm_batch/pg8000_driver_adapter.py | 13 ++++---- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 248f6595..682af78e 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1,6 +1,6 @@ # Product and technical gap baseline -This document records shipped truth separately from active-PR evidence. Exact PR heads, checks, reviews, rulesets, and release identities must always be read live before merge or release decisions; this file is not a substitute for GitHub evidence. +This document separates protected/shipped truth from active-PR evidence. Exact PR heads, checks, reviews, rulesets, security results, and releases must always be read live before merge or release decisions; this file is not a substitute for GitHub evidence. ## Product boundary @@ -8,33 +8,37 @@ pg-llm-batch owns durable PostgreSQL-backed asynchronous LLM batch preparation, ## Protected-main truth -The protected integration branch is `main`. At the latest refresh it was `5913c4bad79d6bc29d7cc1c624abb7db2ea6a77c`. The package remains version `0.1.0`, and its production dependency graph still includes `psycopg[binary]>=3.1`. Therefore issue #322, replacement of the LGPL-family Psycopg runtime dependency, remains an open commercial-policy defect. No public release should claim that the current `pip install .` runtime graph is commercially clean while that defect remains. +The protected integration branch is `main`. At the latest refresh it was `5913c4bad79d6bc29d7cc1c624abb7db2ea6a77c`. The package remains version `0.1.0`, and protected main still carries the Psycopg runtime graph. Therefore issue #322 remains an open commercial-policy defect at shipped/release authority even though the active #323 migration branch has advanced beyond that graph. No public release may claim the replacement is shipped until the pg8000 graph integrates normally into a protected exact head and the corresponding package/license/vulnerability/SBOM/provenance/reproducibility evidence passes on that same authority. -The repository has no immutable GitHub release at the latest refresh. A release is not ready merely because a branch is green: one exact protected head must pass the repository's applicable CI, security, coverage/docstring, package, SBOM/provenance, reproducibility, migration/rollback/recovery, operability, and review gates before version/tag/publication evidence is promoted. +The repository has no immutable GitHub release at the latest refresh. A green Draft branch is not release authority: one exact protected head must pass the repository's applicable CI, security, coverage/docstring, package, migration/rollback/recovery, operability, SBOM/provenance, reproducibility, and review gates before version/tag/publication evidence is promoted. ## Active delivery lanes -PR #233 remains the dependency-root delivery lane and must be judged from its live head and live base, not predecessor evidence. +PR #233 remains the dependency-root integration lane and must be judged from its live head and base. Its repository-local deterministic lanes have been green on the unchanged current head, while required central CodeQL/OpenCode/Noema settlement and a structurally satisfiable independent approval path remain non-passing owner prerequisites. Leaf churn, synthetic status, self-approval, and routine administrator bypass are not substitutes. -PR #323 is the active Draft migration lane for issue #322. It establishes a driver-neutral PostgreSQL anti-corruption port, retains Psycopg only as the current baseline adapter, and evaluates pg8000 1.31.5 as candidate evidence without promoting it into the production manifest. The lane exercises parameter binding, tuple-row normalization, row-count semantics, transaction and cleanup precedence, forced-RLS tenant scope, JSONB/UUID/timestamp behavior, exact candidate dependency hashes and license metadata, URI/keyword/explicit-service selection, packaged restore-catalog acceptance, thread-affinity rejection at the anti-corruption boundary, and real PostgreSQL candidate execution. Exact branch evidence now also terminates a live candidate backend from a second authenticated session, requires the severed capability to fail and become terminal, and proves recovery only by opening a fresh connection. That is candidate recovery evidence; it is not production-driver promotion. +PR #323 is the active Draft migration lane for issue #322. It established `PostgresDriverPort`, retained the Psycopg implementation as a verification baseline, proved pg8000 1.31.5 behind the neutral port, and has now promoted the admitted pg8000 adapter into the branch's single centralized runtime selector. The branch manifest declares exact `pg8000==1.31.5` as the default runtime dependency; Psycopg 3.3.4 remains only in the `test` optional dependency and `dev` dependency group for legacy-baseline verification. The regenerated lock resolves pg8000 with `python-dateutil` and `scramp`/`asn1crypto` and no Psycopg dependency in the project default runtime edge. -Candidate runtime parity now executes against the built pg-llm-batch wheel rather than repository import leakage. CI builds the exact source head with `uv build --wheel --no-sources`, installs that wheel without dependency resolution into each isolated Python 3.10, 3.12, and 3.14 candidate environment, runs `uv pip check`, changes out of the repository working tree, and runs the same real pg8000/PostgreSQL smoke without `PYTHONPATH`. This proves package-installed behavior for the candidate lane while the committed product metadata still intentionally retains the Psycopg baseline. +The promotion preserves the existing candidate evidence: parameter binding, native no-parameter DB-API execution, tuple-row normalization, finite fetch budgets, exact/unknown row counts, transaction/context ownership, terminal connection state, thread-affine use, PostgreSQL RLS/session behavior, UUID/timestamp round-trip, SQLSTATE classification, cleanup precedence, JSONB adaptation, strict single-host URI/keyword conninfo parsing, explicit service-file resolution without ambient `PGSERVICEFILE` discovery, packaged restore-catalog acceptance, server-terminated-session recovery, exact dependency/license evidence, source-to-wheel Python payload parity, and package-installed execution outside the checkout across the supported Python matrix. Unsupported multi-host/socket/query/LDAP/ambient-service semantics remain fail closed rather than approximated. -Candidate supply-chain admission now verifies both the exact five-wheel pg8000 closure and the published pg8000 1.31.5 source distribution before candidate installation. CI pins the wheel and source-distribution SHA-256 digests, reads bounded wheel `METADATA` without importing candidate code, rejects GPL/LGPL/AGPL-family declarations, requires positive reviewed permissive-license evidence for every exact package/version, and compares every Python source path and byte digest under the pg8000 package between the pinned source distribution and universal wheel. The parity verifier does not extract archives, import candidate code, follow archive links, or execute a source build. This closes the published source-to-wheel executable-payload parity gap for the selected candidate artifacts; it does not itself approve a production driver replacement or provide an upstream build attestation. +The production construction boundary `load_pg8000_driver()` admits only exact pg8000 1.31.5, verifies the installed distribution identity and top-level import origin before package code executes, and optionally composes one caller-selected service file. The centralized `retained_postgres_driver()` now constructs that admitted adapter. This is a branch-level source/runtime fact, not protected-main or release authority. -The lane now also contains a production-construction boundary for the admitted pg8000 artifact. `load_pg8000_driver()` checks the installed distribution version against exact `1.31.5`, resolves that distribution's installed `pg8000` package root, and compares the top-level import spec origin and package search location with that root before any pg8000 package code executes. A same-version distribution therefore cannot authorize an earlier shadow package on the import path. Missing pg8000, version mismatch, missing import authority, and origin mismatch fail with fixed non-content-bearing diagnostics, while unrelated missing transitive imports remain visible as packaging defects. Service-file authority remains available only through one explicit caller-selected path. This deliberately does not switch `retained_postgres_driver()` or the committed dependency graph; runtime promotion and manifest/lock/SBOM convergence remain one later acceptance step so an uninstalled or unreviewed driver cannot become default authority accidentally. +### Runtime-graph RED and causal repair + +The selector and `pyproject.toml` were promoted before the committed `uv.lock` had converged. Exact head `8972ec9a1f1e94ad40b5490be88e5e53d1dd200b` therefore produced a useful reality RED: frozen/default container installation still followed the stale lock and installed Psycopg rather than pg8000, while the production selector required pg8000. The PostgreSQL/container smoke failed through `retained_postgres_driver()` with the fixed unavailable-driver boundary, and the locked-dependency unit lane also failed before product tests. The failure was not a log-routing defect and did not justify a selector fallback or relaxed frozen install. + +A bounded exact-head lock finalizer regenerated and verified the lock, proved that the project default runtime edge contains pg8000 and excludes Psycopg, ran `uv sync --locked --no-dev`, proved exact pg8000 1.31.5 is installed while Psycopg is absent from that production environment, constructed `Pg8000DriverAdapter` through the centralized selector, built the package, committed only `uv.lock`, and removed its own temporary workflow in the same descendant. Commit `e1045b6ed74e848cd99a50b02b42fe731fcc8b9b` is the resulting graph repair. Because that commit was authored by the workflow token, its automatically materialized pull-request CI/Release runs reported `action_required` without jobs; they are not GREEN evidence. A normal user-authored descendant must therefore re-run the full exact-head acceptance on the unchanged repaired graph. ## Highest-priority gaps | Gap | Current state | Required next evidence | | --- | --- | --- | -| Commercial PostgreSQL runtime dependency | P0 / active | Complete issue #322: preserve shipped DB semantics while removing every disallowed GPL/LGPL/AGPL-family runtime package from the committed dependency graph. | -| Candidate-to-production construction | Active Draft | Exact-version lazy construction, pre-execution distribution/import-origin binding, and explicit service-file composition are implemented behind `PostgresDriverPort`; prove the unchanged final runtime graph after switching the single selector and manifest/lock together. | -| Candidate driver contract parity | Active Draft | Real server-terminated-session recovery, the Python 3.10/3.12/3.14 matrix, and built-wheel execution are proven. Close remaining selector/conninfo compatibility, realistic concurrency beyond the deterministic anti-cross-thread guard, timeout/health, and remaining schema/recovery surfaces before production promotion. | -| Candidate supply-chain admission | Active / strengthened | Exact wheel/source hashes, source-to-wheel Python payload parity, closure license metadata, installed-product-wheel execution, and pre-import origin binding are gated; complete vulnerability/SBOM/provenance and final production runtime-graph evidence before promotion. | -| Dependency-root governance | External owner paths / non-passing | #233 has leaf CI/release/security evidence but still requires authenticated current-head compatibility CodeQL/OpenCode/Noema settlement and a structurally satisfiable independent approval path before normal protected integration. | +| Commercial PostgreSQL runtime dependency | P0 / active Draft | Re-prove the repaired pg8000 default graph on one exact current #323 head, then carry it through normal protected integration and immutable release evidence. | +| Production driver contract parity | Active / promoted on branch | Run real PostgreSQL/RLS/recovery/health/migration/package-installed acceptance through the production selector and fail closed on any pg8000 semantic mismatch. | +| Supply-chain admission | Active / strengthened | Bind exact pg8000 closure hashes, permissive-license evidence, vulnerability results, built package, SBOM, provenance, and reproducibility to the same final artifact/head. | +| Public commercial-license surface | Child lane #321 | Keep README/docs public wording conservative until #323's pg8000 graph is protected and accepted; then non-force restack #321 and update only its owned public files. | +| Dependency-root governance | External owner paths / non-passing | #233 still requires authenticated current-head central CodeQL/OpenCode/Noema settlement and a satisfiable independent approval path before normal protected integration. | | Immutable product release | Not yet published | After the production dependency replacement and all gates pass on one integrated protected head, perform version/CHANGELOG/tag/package/SBOM/provenance/reproducibility/rollback publication and verify artifact identity. | -| Context Graph / EA projection | Candidate-only until released authority exists | `context-graph-contracts` and `enterprise-architecture-core` currently expose no immutable GitHub release. Do not pin mutable producer heads. Continue pg-owned release-readiness seams and adopt only a verified released contract. | +| Context Graph / EA projection | Candidate-only until released authority exists | Do not pin mutable producer heads. Continue pg-owned release-readiness seams and adopt only verified released contracts from canonical owners. | ## Commercial acceptance for issue #322 @@ -44,18 +48,19 @@ Completion requires all of the following on the final production graph, not only - commit, rollback, context-manager, cleanup-error precedence, cancellation/recovery, and connection lifecycle remain deterministic; - tenant authority and transaction-local `set_config` behavior remain correct under forced RLS and restricted roles; - JSON/JSONB, UUID, timestamp, row, row-count, and relevant PostgreSQL error semantics remain compatible; -- DSN parsing/rendering preserves the repository's supported URI, keyword, and service-selector contract without credential leakage into argv or logs; -- concurrency, idempotency, checkpoint, schema application, logical restore, health, and finite-connect behavior pass realistic PostgreSQL tests; -- the committed runtime graph and built artifacts contain no disallowed GPL/LGPL/AGPL-family package; +- DSN parsing/rendering preserves the supported URI, keyword, and explicit-service-selector contract without credential leakage into argv or logs; +- concurrency, idempotency, checkpoint, schema application, logical restore, health, and finite-connect behavior pass realistic PostgreSQL tests through the production selector; +- the committed default runtime graph and built artifacts contain no disallowed GPL/LGPL/AGPL-family package; +- retained Psycopg verification dependencies remain outside production/default installation and release runtime evidence; - package, license, vulnerability, SBOM, provenance, and reproducibility evidence bind the same immutable artifacts; -- the final unchanged head passes exact-source required checks and then-live review/ruleset requirements without self-approval or gate weakening. +- the final unchanged protected head passes exact-source required checks and then-live review/ruleset requirements without self-approval or gate weakening. ## Context Fabric boundary -`context-graph-contracts` remains a contract-only Shared Kernel for canonical object/authority references, truth origin/status, bitemporal semantics, provenance, Context Assertion, and CloudEvents/schema/conformance/admission contracts. `enterprise-architecture-core` remains the EA Decision Plane. While their dedicated Context Fabric writer is active, pg-llm-batch treats both repositories as read-only source dependencies and advances their existing owner paths with exact consumer RED/GREEN criteria instead of creating competing writers. +`context-graph-contracts` remains a contract-only Shared Kernel for canonical object/authority references, truth origin/status, bitemporal semantics, provenance, Context Assertion, and CloudEvents/schema/conformance/admission contracts. `enterprise-architecture-core` remains the EA Decision Plane. pg-llm-batch treats those repositories as foreign canonical owners and consumes only released contracts through explicit anti-corruption boundaries. Prompt, response, batch-result, and user data remain pg/product-domain data and are not copied into EA authoritative architecture tables. Deployable service/API/worker/database/runtime/provider/version and lifecycle/risk/ownership/remediation changes may be projected only through a verified released Context Graph contract with provenance. ## Evidence discipline -Queued, pending, skipped-required, cancelled, absent, predecessor-head, model-only, and status-only evidence is non-passing. A current blocker is the next work item at its actual owner: pg-owned causes require a realistic RED, the smallest causal repair, focused/full GREEN, and exact-head refetch; foreign-owned causes require advancement of the existing owner path followed by independent pg work. A report, comment, handoff, or documentation-only change is never completion while executable code/test/release work remains. +Queued, pending, skipped-required, `action_required`, cancelled, absent, predecessor-head, model-only, and status-only evidence is non-passing. A current blocker is the next work item at its actual owner: pg-owned causes require a realistic RED, the smallest causal repair, focused/full GREEN, and exact-head refetch; foreign-owned causes require advancement of the existing owner path followed by independent pg work. A report, comment, handoff, or documentation-only change is never completion while executable code/test/release work remains. diff --git a/pg_llm_batch/pg8000_driver_adapter.py b/pg_llm_batch/pg8000_driver_adapter.py index 77c2986c..d68bff0d 100644 --- a/pg_llm_batch/pg8000_driver_adapter.py +++ b/pg_llm_batch/pg8000_driver_adapter.py @@ -1,13 +1,12 @@ """Production construction boundary for the admitted pg8000 PostgreSQL driver. The underlying pg8000 semantics were proved incrementally behind candidate-only -adapters before production selection. This module adds the missing construction -boundary: it accepts only the exact admitted distribution, verifies that the -importable package resolves to that distribution before executing it, imports -its DB-API module lazily, and optionally composes the existing explicit -service-file resolver. It does not change the repository's default runtime -selector or manifest; those remain a separate atomic promotion with lock/SBOM -evidence. +adapters before production selection. This module accepts only the exact admitted +distribution, verifies that the importable package resolves to that distribution +before executing it, imports its DB-API module lazily, and optionally composes +the explicit service-file resolver. The centralized runtime selector constructs +this adapter; release authority still requires the matching manifest, lock, +package, SBOM, provenance, and protected-head acceptance evidence. """ from __future__ import annotations From 6f27fb56cd1e761c6edc1ea189eb3a647811ba74 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 01:15:46 +0900 Subject: [PATCH 325/338] fix(postgres): preserve lazy runtime driver authority --- pg_llm_batch/postgres_driver_runtime.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pg_llm_batch/postgres_driver_runtime.py b/pg_llm_batch/postgres_driver_runtime.py index cfb17bda..1cb75f19 100644 --- a/pg_llm_batch/postgres_driver_runtime.py +++ b/pg_llm_batch/postgres_driver_runtime.py @@ -8,6 +8,8 @@ from __future__ import annotations +from importlib import import_module + from .postgres_driver_port import PostgresDriverPort @@ -25,11 +27,12 @@ def retained_postgres_driver() -> PostgresDriverPort: """Return the single admitted concrete driver behind the neutral port. The production construction boundary verifies the exact pg8000 distribution - version and import origin before package code executes. Keeping construction - centralized prevents bounded contexts from acquiring a second concrete - database-client authority while the package graph is promoted. + version and import origin before package code executes. Importing that + boundary through :mod:`importlib` preserves genuinely lazy construction and + lets tests replace the construction module without a package-level cached + attribute becoming a second authority. """ - from . import pg8000_driver_adapter + pg8000_driver_adapter = import_module("pg_llm_batch.pg8000_driver_adapter") try: return pg8000_driver_adapter.load_pg8000_driver() From dce282fd55511c8cb856d1e0e02c07996e6795d5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 01:16:56 +0900 Subject: [PATCH 326/338] test(postgres): align runtime edge contract with pg8000 --- tests/test_postgres_driver_edge_coverage.py | 37 ++++++++++----------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/tests/test_postgres_driver_edge_coverage.py b/tests/test_postgres_driver_edge_coverage.py index 53eb542a..91a2a130 100644 --- a/tests/test_postgres_driver_edge_coverage.py +++ b/tests/test_postgres_driver_edge_coverage.py @@ -2,7 +2,6 @@ from __future__ import annotations -import builtins from types import ModuleType from typing import Any @@ -212,7 +211,7 @@ def __len__(self) -> int: def test_psycopg_cursor_fail_closed_edges() -> None: - """Retained adapter normalizes no-row evidence and rejects malformed driver output.""" + """Legacy test adapter normalizes no-row evidence and rejects malformed output.""" raw = _RawPsycopgCursor() cursor = PsycopgCursorAdapter(raw) assert cursor.fetchone() is None @@ -259,30 +258,30 @@ def fail_render(**_params: str) -> str: adapter.make_conninfo({"host": "bad"}) -def test_runtime_selector_distinguishes_missing_psycopg_from_other_import_failures( +def test_runtime_selector_redacts_admitted_absence_but_propagates_other_failures( monkeypatch: pytest.MonkeyPatch, ) -> None: - """Optional-client absence is redacted while unrelated package defects propagate.""" - original_import = builtins.__import__ + """Admitted-driver absence is redacted while unrelated defects propagate.""" + module = ModuleType("pg_llm_batch.pg8000_driver_adapter") - def missing_psycopg(name: str, *args: Any, **kwargs: Any) -> Any: - if name.endswith("psycopg_driver_adapter"): - error = ModuleNotFoundError("missing psycopg") - error.name = "psycopg" - raise error - return original_import(name, *args, **kwargs) + class DriverUnavailable(RuntimeError): + pass + + module.Pg8000DriverUnavailableError = DriverUnavailable + + def unavailable() -> object: + raise DriverUnavailable("internal driver detail") - monkeypatch.setattr(builtins, "__import__", missing_psycopg) + module.load_pg8000_driver = unavailable + monkeypatch.setattr(runtime, "import_module", lambda _name: module) with pytest.raises(runtime.PostgresDriverUnavailableError, match="unavailable"): runtime.retained_postgres_driver() - def missing_other(name: str, *args: Any, **kwargs: Any) -> Any: - if name.endswith("psycopg_driver_adapter"): - error = ModuleNotFoundError("missing other") - error.name = "other_dependency" - raise error - return original_import(name, *args, **kwargs) + def unrelated_failure() -> object: + error = ModuleNotFoundError("missing other") + error.name = "other_dependency" + raise error - monkeypatch.setattr(builtins, "__import__", missing_other) + module.load_pg8000_driver = unrelated_failure with pytest.raises(ModuleNotFoundError): runtime.retained_postgres_driver() From 7a0da9e19df81f2185ff9696d43b381ed73c206a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 01:17:45 +0900 Subject: [PATCH 327/338] test(postgres): run checkpoint smoke through admitted driver --- tests/smoke_checkpoint_store_concurrency.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/smoke_checkpoint_store_concurrency.py b/tests/smoke_checkpoint_store_concurrency.py index 0831044b..b6a7e387 100644 --- a/tests/smoke_checkpoint_store_concurrency.py +++ b/tests/smoke_checkpoint_store_concurrency.py @@ -7,12 +7,11 @@ from concurrent.futures import ThreadPoolExecutor from threading import Barrier -import psycopg - from pg_llm_batch.checkpoint_store import ( CheckpointConflictError, PostgresBatchResultCheckpointStore, ) +from pg_llm_batch.postgres_driver_runtime import retained_postgres_driver from pg_llm_batch.result_streaming import BatchResultCheckpoint DSN = os.environ.get( @@ -117,8 +116,9 @@ def assert_caller_transaction_contract(store: PostgresBatchResultCheckpointStore """Prove business effect and checkpoint share caller commit/rollback authority.""" rolled_back = checkpoint("batch-transaction-rollback", line_count=1, digest_character="4") committed = checkpoint("batch-transaction-commit", line_count=1, digest_character="5") + driver = retained_postgres_driver() - with psycopg.connect(DSN) as connection: + with driver.connect(DSN) as connection: with connection.cursor() as cursor: cursor.execute( "CREATE TEMP TABLE checkpoint_acceptance_effects (" @@ -160,7 +160,7 @@ def assert_caller_transaction_contract(store: PostgresBatchResultCheckpointStore def main() -> None: - """Run the live checkpoint-store acceptance contract.""" + """Run the live checkpoint-store acceptance contract through the admitted driver.""" store = PostgresBatchResultCheckpointStore(DSN) assert_initial_race_contract(store) assert_compare_and_swap_contract(store) From 3e0103fcf0a94327828b62137666f52fa12b6561 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 01:22:23 +0900 Subject: [PATCH 328/338] test(postgres): satisfy promoted runtime lint gate --- tests/test_postgres_driver_edge_coverage.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_postgres_driver_edge_coverage.py b/tests/test_postgres_driver_edge_coverage.py index 91a2a130..6c73fb33 100644 --- a/tests/test_postgres_driver_edge_coverage.py +++ b/tests/test_postgres_driver_edge_coverage.py @@ -3,7 +3,6 @@ from __future__ import annotations from types import ModuleType -from typing import Any import pytest from psycopg import ProgrammingError From 2afd5be12847b51c8d476c59f5b328c697069780 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 02:17:18 +0900 Subject: [PATCH 329/338] fix(postgres): decouple CLI DSN policy from runtime driver --- docs/doctoring/bootstrap-dsn-precedence.md | 41 ++++--- pg_llm_batch/cli.py | 108 +++++++++++++++--- .../smoke_restore_catalog_index_semantics.py | 10 +- tests/test_cli_dsn_argv_security.py | 56 ++++++++- tests/test_workflow_contracts.py | 9 +- 5 files changed, 183 insertions(+), 41 deletions(-) diff --git a/docs/doctoring/bootstrap-dsn-precedence.md b/docs/doctoring/bootstrap-dsn-precedence.md index 6ab0198c..06886a0a 100644 --- a/docs/doctoring/bootstrap-dsn-precedence.md +++ b/docs/doctoring/bootstrap-dsn-precedence.md @@ -4,10 +4,12 @@ `PG_LLM_BATCH_DSN` and `PG_LLM_BATCH_SECRET_KEY` are bootstrap transports used only when a caller omits the corresponding explicit value. The prior implementation selected both with Python boolean truthiness (`explicit or environment_value`). That conflated omission with explicit false-valued input and could silently transfer database-target or decryption authority to ambient process state. -For the required Postgres DSN, an explicitly empty or whitespace-only value must not be replaced by `PG_LLM_BATCH_DSN` or passed down to libpq defaults. Non-string explicit values must also fail at the package boundary rather than reaching unrelated lower-layer behavior. For the optional Fernet bootstrap key, an explicit empty string is a deliberate statement that no key was supplied for this invocation and must not inherit an ambient key. +For the required Postgres DSN, an explicitly empty or whitespace-only value must not be replaced by `PG_LLM_BATCH_DSN` or passed down to driver defaults. Non-string explicit values must also fail at the package boundary rather than reaching unrelated lower-layer behavior. For the optional Fernet bootstrap key, an explicit empty string is a deliberate statement that no key was supplied for this invocation and must not inherit an ambient key. A separate CLI confidentiality boundary applies before bootstrap resolution. PostgreSQL connection information can carry passwords, password-file locations, TLS private-key material, TLS key passwords, and OAuth client secrets. Accepting those values through `--dsn` copies credential material or credential-bearing locations into process invocation state, where operating-system process inspection and shell history can expose them. The CLI therefore needs to retain explicit database targeting without making credential-bearing conninfo a normal argv transport. +The production PostgreSQL client is selected behind `PostgresDriverPort`, but argv confidentiality is not a concrete-driver connectability decision. A driver may intentionally support only a bounded connection subset while operators still need to express a credential-free selector such as `service=` or `sslmode=` for another admitted deployment adapter. Using the current concrete driver's `parse_conninfo()` as the CLI security classifier therefore couples secret detection to backend compatibility and can reject safe selectors before the runtime owner has a chance to apply its own connection contract. + ## Contract `resolve_dsn()` distinguishes source absence, source type, and source value: @@ -15,7 +17,7 @@ A separate CLI confidentiality boundary applies before bootstrap resolution. Pos - the environment is consulted only when the explicit argument is `None`; - an explicit Postgres DSN must be an exact `str`; - explicit and environment-selected DSNs must be non-empty after whitespace inspection; -- invalid explicit values fail with bounded `ConfigError` before environment fallback or libpq target selection; and +- invalid explicit values fail with bounded `ConfigError` before environment fallback or database target selection; and - valid nonblank DSNs are returned unchanged rather than normalized or rewritten. `resolve_secret_key()` uses the same source-precedence rule while preserving its optional-value semantics: @@ -27,13 +29,16 @@ A separate CLI confidentiality boundary applies before bootstrap resolution. Pos The standalone CLI adds a narrower transport rule for explicit `--dsn` values: -- parse the supplied value with Psycopg/libpq-compatible `conninfo_to_dict()` rather than ad-hoc URI or keyword matching; -- permit credential-free selectors such as password-free PostgreSQL URIs, keyword conninfo, and `service=` selectors; -- reject conninfo that explicitly contains `password`, `passfile`, `sslkey`, `sslpassword`, or `oauth_client_secret` before bootstrap resolution or database connection work; -- reject malformed conninfo with a fixed parser diagnostic that does not reproduce the rejected argv value; and -- preserve the exact accepted selector string so downstream source-precedence and libpq semantics remain unchanged. +- classify PostgreSQL URI and libpq-style keyword parameter names at the CLI boundary without constructing a concrete PostgreSQL client; +- recognize URI user-info passwords and percent-decoded URI query parameter names, and recognize quoted, escaped, or whitespace-separated keyword assignments; +- permit credential-free selectors such as password-free PostgreSQL URIs, keyword conninfo, `service=` selectors, and options such as `sslmode=` even when the currently selected runtime adapter cannot connect with that selector; +- reject selectors that explicitly contain `password`, `passfile`, `sslkey`, `sslpassword`, or `oauth_client_secret` before bootstrap resolution or database connection work; +- reject malformed lexical selector forms with a fixed parser diagnostic that does not reproduce the rejected argv value; and +- preserve the exact accepted selector string so downstream source precedence and the selected runtime adapter retain connection-semantics authority. + +The classifier is intentionally not a second PostgreSQL connection parser. Passing CLI admission proves only that the selector does not place a prohibited credential parameter in argv and that its outer URI/keyword framing is parseable. The admitted `PostgresDriverPort` remains responsible for deciding whether a credential-free selector is connectable, whether service resolution is configured, and which transport options are supported. Unsupported runtime semantics must still fail closed there rather than being approximated by the CLI. -The CLI restriction does not prohibit standard libpq authentication. Operators may keep password/private-key material outside argv using reviewed libpq mechanisms such as the default password file, `PGPASSFILE`, a connection service file, default or environment-selected TLS key material, or deployment-owned secret injection. `PG_LLM_BATCH_DSN` remains a bootstrap transport and is not claimed to be a universal secrets manager; deployments should select an appropriate secret mechanism for their threat model. +The CLI restriction does not prohibit standard PostgreSQL authentication. Operators may keep password/private-key material outside argv using reviewed password files, service files, environment-selected deployment secrets, or another secret transport admitted by their concrete PostgreSQL adapter. `PG_LLM_BATCH_DSN` remains a bootstrap transport and is not claimed to be a universal secrets manager; deployments should select an appropriate secret mechanism for their threat model. This boundary does not make secret persistence, serialization, transport, TLS, or server identity safe by itself. It prevents two specific authority/confidentiality failures: ambient bootstrap state silently replacing explicit caller intent, and credential-bearing explicit CLI conninfo becoming process-argument data. @@ -41,17 +46,23 @@ This boundary does not make secret persistence, serialization, transport, TLS, o `tests/test_bootstrap_source_precedence.py` proves the replacement behavior against the public bootstrap helpers. The regressions populate ambient environment values while passing explicit invalid values so a rejected caller value cannot be confused with ordinary omitted-input fallback. They also prove that an omitted whitespace-only DSN is rejected, a valid explicit DSN retains exact text, and an explicit empty secret key remains explicit. -`tests/test_cli_dsn_argv_security.py` defines the CLI transport contract. It requires password-bearing PostgreSQL URIs, keyword `password=`, and explicit `passfile=` values to fail without reflecting a unique secret sentinel; it separately requires malformed conninfo to fail without reflection and confirms that credential-free URI, keyword, and service selectors retain exact text. +`tests/test_cli_dsn_argv_security.py` defines the CLI transport contract. It requires password-bearing PostgreSQL URIs, keyword `password=`, explicit credential-file/private-key parameters, percent-encoded sensitive URI query names, and case variants to fail without reflecting a unique secret sentinel. It separately requires malformed URI/keyword framing to fail without reflection and confirms that credential-free URI, keyword, service, quoted/escaped, whitespace-separated, and option-bearing selectors retain exact text. Those tests intentionally do not claim that every accepted selector is connectable by pg8000 or any other concrete adapter. + +`tests/test_cli_postgres_driver_port.py` preserves the migration seam for explicitly injected driver doubles. When a caller supplies a `PostgresDriverPort` to `build_parser()` for adapter verification, that exact injected parser and invalid-conninfo classifier are still used. Production CLI construction, however, does not instantiate the runtime PostgreSQL client merely to classify argv confidentiality. + +The fail-first bootstrap replacement head demonstrated that protected-main truthiness selected ambient values or admitted the wrong type before the production repair. The CLI fail-first branch independently demonstrated that protected main accepted credential-bearing `--dsn` values unchanged. During pg8000 production promotion, exact-head CI then demonstrated the opposite coupling failure: credential-free selectors accepted by the CLI contract were rejected because CLI admission delegated to pg8000's deliberately bounded connectability parser. The repair separates those authorities without weakening either one. -The fail-first bootstrap replacement head demonstrated that protected-main truthiness selected ambient values or admitted the wrong type before the production repair. The CLI fail-first branch independently demonstrated that protected main accepted credential-bearing `--dsn` values unchanged. The CLI production repair uses libpq-compatible parsing only to classify whether argv contains prohibited credential parameters; it does not rewrite accepted connection information or change bootstrap precedence. Final acceptance still requires the repository's complete exact-head Python 3.10/3.12/3.14, 100% owned production statement/branch coverage, public docstrings, package, security, SAST, required-workflow, review-thread, and live ruleset evidence on one unchanged final source. +Final acceptance still requires the repository's complete exact-head supported-Python matrix, 100% owned production statement/branch coverage, public docstrings, package, security, SAST, required-workflow, review-thread, and live ruleset evidence on one unchanged final source. ## Compatibility and rollback Bootstrap helper call shapes remain unchanged. Callers that intentionally depended on explicit empty/non-string values falling through to environment state must now omit the argument to request environment fallback. Valid explicit DSNs and keys retain their original string values. -The CLI keeps `--dsn` for explicit database selection but no longer accepts credentials or credential-file/private-key parameters in that process argument. Existing automation that embeds such material in `--dsn` must move authentication data to a standard libpq mechanism outside argv while preserving its database selector. This is an intentional confidentiality hardening, not silent credential removal. +The CLI keeps `--dsn` for explicit database selection but does not accept credentials or credential-file/private-key parameters in that process argument. Existing automation that embeds such material in `--dsn` must move authentication data to a reviewed PostgreSQL mechanism outside argv while preserving its database selector. This is an intentional confidentiality hardening, not silent credential removal. -Rollback is an ordinary Git revert of the bounded change. Rolling back the bootstrap rule reintroduces ambiguous authority selection; rolling back the CLI rule reintroduces credential-bearing process arguments. Either rollback should occur only with a documented compatibility requirement and a safer replacement contract. +Credential-free CLI selectors are no longer rejected solely because the currently selected runtime driver supports a smaller connection grammar. This does not expand that driver's runtime capabilities: a selector outside its admitted connection subset still fails at the driver boundary with that driver's bounded diagnostic. Deployments that require additional PostgreSQL selector semantics must admit them at the driver/ACL boundary rather than relying on CLI parsing as compatibility evidence. + +Rollback is an ordinary Git revert of the bounded change. Rolling back the bootstrap rule reintroduces ambiguous authority selection; rolling back the CLI confidentiality rule reintroduces either credential-bearing process arguments or concrete-driver coupling at the argv boundary. Either rollback should occur only with a documented compatibility requirement and a safer replacement contract. ## References @@ -61,8 +72,8 @@ PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: Datab PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: The password file*. https://www.postgresql.org/docs/18/libpq-pgpass.html -The Psycopg Team. (2026). *Psycopg 3 documentation: `conninfo` — manipulate connection strings*. https://www.psycopg.org/psycopg3/docs/api/conninfo.html - Python Software Foundation. (2026). *argparse — Parser for command-line options, arguments and subcommands*. Python 3.14 documentation. https://docs.python.org/3.14/library/argparse.html -Python Software Foundation. (2026). *os — Miscellaneous operating system interfaces*. Python 3.14 documentation. https://docs.python.org/3.14/library/os.html +Python Software Foundation. (2026). *shlex — Simple lexical analysis*. Python 3.14 documentation. https://docs.python.org/3.14/library/shlex.html + +Python Software Foundation. (2026). *urllib.parse — Parse URLs into components*. Python 3.14 documentation. https://docs.python.org/3.14/library/urllib.parse.html diff --git a/pg_llm_batch/cli.py b/pg_llm_batch/cli.py index 0333235f..5be46b00 100644 --- a/pg_llm_batch/cli.py +++ b/pg_llm_batch/cli.py @@ -18,7 +18,7 @@ The DSN is resolved from --dsn or the PG_LLM_BATCH_DSN bootstrap env var only. Command-line DSNs may select a database but may not carry password/private-key -credentials; use standard libpq secret mechanisms outside process argv. All +credentials; use standard PostgreSQL secret mechanisms outside process argv. All other config/secrets come from the database KV stores. Secret plaintext and count-tokens prompt content are never accepted as command-line arguments. """ @@ -30,11 +30,13 @@ import getpass import json import re +import shlex import sys import warnings from contextlib import ExitStack from functools import partial from typing import List, Optional +from urllib.parse import unquote_plus, urlsplit from . import db, postgres_driver_runtime from .batch_api_client import BatchAPIClient, config_credentials_provider @@ -57,6 +59,8 @@ "oauth_client_secret", } ) +_CLI_POSTGRES_URI_SCHEMES = frozenset({"postgres", "postgresql"}) +_CLI_CONNINFO_KEY = re.compile(r"[A-Za-z_][A-Za-z0-9_]*\Z") class _RedactingArgumentParser(argparse.ArgumentParser): @@ -72,35 +76,107 @@ def error(self, message: str) -> None: super().error(redacted_message) +class _CliDsnSyntaxError(ValueError): + """Identify malformed CLI selector syntax without retaining rejected content.""" + + def _default_postgres_driver() -> PostgresDriverPort: """Delegate concrete-driver construction to the canonical runtime selector. - CLI parsing owns credential-safe argument validation, not the concrete - PostgreSQL client choice. A single selector keeps migration cutover atomic - across CLI, service, persistence, and Compose surfaces. + Runtime connection ownership remains centralized even though CLI argv + confidentiality is intentionally classified without concrete-driver + connectability rules. """ return postgres_driver_runtime.retained_postgres_driver() +def _default_cli_dsn_parameter_names(value: str) -> frozenset[str]: + """Classify PostgreSQL selector keys without choosing a concrete DB client. + + This parser is deliberately narrower than a connection parser: it recognizes + parameter names needed for argv confidentiality policy while leaving backend + compatibility and service resolution to the admitted runtime driver. Values + are never normalized into a replacement selector or returned to callers. + """ + stripped = value.strip() + if not stripped: + raise _CliDsnSyntaxError + + scheme_match = re.match(r"(?i)^([a-z][a-z0-9+.-]*):", stripped) + if scheme_match is not None: + scheme = scheme_match.group(1).casefold() + if scheme not in _CLI_POSTGRES_URI_SCHEMES or not stripped[ + len(scheme) : + ].startswith("://"): + raise _CliDsnSyntaxError + try: + parsed = urlsplit(stripped) + _ = parsed.hostname + except ValueError: + raise _CliDsnSyntaxError from None + + names: set[str] = set() + if parsed.password is not None: + names.add("password") + for query_item in parsed.query.split("&"): + if not query_item: + continue + encoded_key = query_item.split("=", 1)[0] + names.add(unquote_plus(encoded_key).casefold()) + return frozenset(names) + + lexer = shlex.shlex(value, posix=True) + lexer.whitespace_split = True + lexer.commenters = "" + try: + tokens = list(lexer) + except ValueError: + raise _CliDsnSyntaxError from None + + names: set[str] = set() + token_index = 0 + while token_index < len(tokens): + token = tokens[token_index] + if "=" in token: + key, _ignored_value = token.split("=", 1) + token_index += 1 + elif token_index + 1 < len(tokens) and tokens[token_index + 1] == "=": + key = token + token_index += 2 + if token_index < len(tokens) and "=" not in tokens[token_index]: + token_index += 1 + else: + raise _CliDsnSyntaxError + if _CLI_CONNINFO_KEY.fullmatch(key) is None: + raise _CliDsnSyntaxError + names.add(key.casefold()) + if not names: + raise _CliDsnSyntaxError + return frozenset(names) + + def _validate_cli_dsn( value: str, *, postgres_driver: PostgresDriverPort | None = None, ) -> str: - """Accept valid PostgreSQL selectors without concrete-driver coupling.""" - driver = ( - postgres_driver - if postgres_driver is not None - else _default_postgres_driver() - ) - try: - parameters = driver.parse_conninfo(value) - except Exception as exc: - if driver.is_invalid_conninfo(exc): + """Accept credential-free selectors without concrete-driver coupling.""" + if postgres_driver is None: + try: + parameters = _default_cli_dsn_parameter_names(value) + except _CliDsnSyntaxError: raise argparse.ArgumentTypeError( "Postgres DSN must be valid connection information" ) from None - raise + else: + try: + parameters = postgres_driver.parse_conninfo(value) + except Exception as exc: + if postgres_driver.is_invalid_conninfo(exc): + raise argparse.ArgumentTypeError( + "Postgres DSN must be valid connection information" + ) from None + raise if CLI_DSN_SENSITIVE_PARAMETERS.intersection(parameters): raise argparse.ArgumentTypeError( "Credential-bearing Postgres DSNs are not accepted in --dsn; " @@ -456,4 +532,4 @@ async def _go() -> int: if __name__ == "__main__": # pragma: no cover - sys.exit(main()) \ No newline at end of file + sys.exit(main()) diff --git a/tests/smoke_restore_catalog_index_semantics.py b/tests/smoke_restore_catalog_index_semantics.py index 411a8bfe..0692e76d 100644 --- a/tests/smoke_restore_catalog_index_semantics.py +++ b/tests/smoke_restore_catalog_index_semantics.py @@ -5,8 +5,7 @@ import os -import psycopg - +from pg_llm_batch.postgres_driver_runtime import retained_postgres_driver from pg_llm_batch.postgres_restore_acceptance import ( PostgresRestoreAcceptanceError, inspect_postgres_restore_catalog, @@ -38,8 +37,9 @@ def _require_incomplete_catalog(connection: object) -> None: def main() -> None: """Prove packaged indexes pass and same-name wrong-shape indexes fail closed.""" - with psycopg.connect(DSN) as connection: - connection.autocommit = True + connection = retained_postgres_driver().connect(DSN) + try: + connection.set_autocommit(True) _require_complete_catalog(connection) with connection.cursor() as cursor: cursor.execute( @@ -87,6 +87,8 @@ def main() -> None: "UNIQUE (tenant_scope, endpoint_alias, remote_batch_id)" ) _require_complete_catalog(connection) + finally: + connection.close() if __name__ == "__main__": diff --git a/tests/test_cli_dsn_argv_security.py b/tests/test_cli_dsn_argv_security.py index c10ef31e..1d11ceaa 100644 --- a/tests/test_cli_dsn_argv_security.py +++ b/tests/test_cli_dsn_argv_security.py @@ -8,6 +8,17 @@ from pg_llm_batch import cli +class _EmptyLexer: + """Model an unexpected lexer result for the CLI fail-closed guard.""" + + whitespace_split = False + commenters = "" + + def __iter__(self): + """Yield no fields from a non-empty selector.""" + return iter(()) + + @pytest.mark.parametrize( "credential_dsn", [ @@ -18,6 +29,8 @@ "host=db.example dbname=batch user=app sslkey=/tmp/secret-sentinel.key", "host=db.example dbname=batch user=app sslpassword=secret-sentinel", "host=db.example dbname=batch user=app oauth_client_secret=secret-sentinel", + "postgresql://db.example/batch?%70assword=secret-sentinel", + "host = db.example PASSWORD = secret-sentinel", ], ) def test_cli_rejects_credential_bearing_dsn_arguments_without_reflection( @@ -35,37 +48,70 @@ def test_cli_rejects_credential_bearing_dsn_arguments_without_reflection( assert "secret-sentinel" not in captured.out +@pytest.mark.parametrize( + "malformed_dsn", + [ + "host=db.example password=secret-sentinel broken", + "postgresql:db.example/batch", + "mysql://db.example/batch", + "postgresql://[broken/batch", + "host='unterminated", + "=missing-key", + " ", + ], +) def test_cli_rejects_malformed_dsn_without_reflection( + malformed_dsn: str, capsys: pytest.CaptureFixture[str], ) -> None: - """Malformed conninfo fails with a fixed parser diagnostic.""" + """Malformed selector syntax fails with a fixed non-reflecting diagnostic.""" parser = cli.build_parser() with pytest.raises(SystemExit): - parser.parse_args( - ["health", "--dsn", "host=db.example password=secret-sentinel broken"] - ) + parser.parse_args(["health", "--dsn", malformed_dsn]) captured = capsys.readouterr() assert "secret-sentinel" not in captured.err assert "secret-sentinel" not in captured.out + assert "unterminated" not in captured.err + assert "missing-key" not in captured.err @pytest.mark.parametrize( "selector", [ "postgresql://db.example/batch?sslmode=verify-full", + "postgresql:///postgres", + "postgresql://db.example/batch?&sslmode=verify-full", "host=db.example dbname=batch sslmode=verify-full", + "host = db.example dbname = 'batch reporting' sslmode=verify-full", + "host=db\\ example dbname=batch", "service=pg-llm-batch", + "service = pg-llm-batch", ], ) def test_cli_retains_credential_free_explicit_database_selectors(selector: str) -> None: - """Explicit database targeting remains usable when argv contains no secret.""" + """Credential-free targeting survives policy checks independent of connectability.""" args = cli.build_parser().parse_args(["health", "--dsn", selector]) assert args.dsn == selector +def test_cli_keyword_policy_accepts_an_explicit_empty_value() -> None: + """Lexical policy must not invent a connection-validity rule for empty values.""" + args = cli.build_parser().parse_args(["health", "--dsn", "host ="]) + + assert args.dsn == "host =" + + +def test_cli_policy_fails_closed_when_lexer_returns_no_fields(monkeypatch) -> None: + """Unexpected lexical emptiness must not bypass the selector syntax boundary.""" + monkeypatch.setattr(cli.shlex, "shlex", lambda *_args, **_kwargs: _EmptyLexer()) + + with pytest.raises(cli._CliDsnSyntaxError): + cli._default_cli_dsn_parameter_names("host=db.example") + + def test_serve_healthz_cli_defaults_to_loopback() -> None: """Direct CLI readiness serving must not bind every host interface by default.""" args = cli.build_parser().parse_args( diff --git a/tests/test_workflow_contracts.py b/tests/test_workflow_contracts.py index 4d530430..68ac8d27 100644 --- a/tests/test_workflow_contracts.py +++ b/tests/test_workflow_contracts.py @@ -177,7 +177,14 @@ def test_ci_pg8000_candidate_parity_is_immutable_and_queue_conservative() -> Non assert "PG_LLM_BATCH_POSTGRES_PASSWORD=$candidate_password" not in workflow assert "PG8000_CANDIDATE_PASSWORD_FILE" in workflow assert "Tear down candidate PostgreSQL runtime" in workflow - assert '"pg8000' not in project.casefold() + project_section = re.search( + r"(?ms)^\[project\]\n(?P.*?)(?=^\[|\Z)", + project, + ) + assert project_section is not None + production_dependencies = project_section.group("body").casefold() + assert '"pg8000==1.31.5"' in production_dependencies + assert '"psycopg' not in production_dependencies def test_ci_pg8000_candidate_keeps_0600_secrets_for_both_runtime_identities() -> None: From 42899baba1009e9e4b75f73c8e038c6f8c730c84 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 02:40:01 +0900 Subject: [PATCH 330/338] docs(postgres): align production driver and TLS gap evidence --- docs/product-technical-gap-baseline.md | 28 +++++++++++++++---- .../pg8000_driver_candidate_errors.py | 13 +++++---- .../pg8000_thread_affine_candidate_adapter.py | 20 ++++++------- pg_llm_batch/postgres_driver_port.py | 7 +++-- 4 files changed, 44 insertions(+), 24 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 682af78e..9bdd8fb6 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -14,7 +14,7 @@ The repository has no immutable GitHub release at the latest refresh. A green Dr ## Active delivery lanes -PR #233 remains the dependency-root integration lane and must be judged from its live head and base. Its repository-local deterministic lanes have been green on the unchanged current head, while required central CodeQL/OpenCode/Noema settlement and a structurally satisfiable independent approval path remain non-passing owner prerequisites. Leaf churn, synthetic status, self-approval, and routine administrator bypass are not substitutes. +PR #233 remains the dependency-root integration lane and must be judged from its live head and base. Its repository-local deterministic lanes have been green on the unchanged current head, while required central CodeQL/OpenCode/Noema settlement and a structurally satisfiable independent approval path remain non-passing owner prerequisites. Leaf churn, synthetic status, self-approval, and routine administrator bypass are not substitutes. The central CodeQL repair has continued to move through its own ordinary owner lineage; pg-llm-batch must consume only protected central behavior and must not copy that mutable workflow source locally. PR #323 is the active Draft migration lane for issue #322. It established `PostgresDriverPort`, retained the Psycopg implementation as a verification baseline, proved pg8000 1.31.5 behind the neutral port, and has now promoted the admitted pg8000 adapter into the branch's single centralized runtime selector. The branch manifest declares exact `pg8000==1.31.5` as the default runtime dependency; Psycopg 3.3.4 remains only in the `test` optional dependency and `dev` dependency group for legacy-baseline verification. The regenerated lock resolves pg8000 with `python-dateutil` and `scramp`/`asn1crypto` and no Psycopg dependency in the project default runtime edge. @@ -26,14 +26,25 @@ The production construction boundary `load_pg8000_driver()` admits only exact pg The selector and `pyproject.toml` were promoted before the committed `uv.lock` had converged. Exact head `8972ec9a1f1e94ad40b5490be88e5e53d1dd200b` therefore produced a useful reality RED: frozen/default container installation still followed the stale lock and installed Psycopg rather than pg8000, while the production selector required pg8000. The PostgreSQL/container smoke failed through `retained_postgres_driver()` with the fixed unavailable-driver boundary, and the locked-dependency unit lane also failed before product tests. The failure was not a log-routing defect and did not justify a selector fallback or relaxed frozen install. -A bounded exact-head lock finalizer regenerated and verified the lock, proved that the project default runtime edge contains pg8000 and excludes Psycopg, ran `uv sync --locked --no-dev`, proved exact pg8000 1.31.5 is installed while Psycopg is absent from that production environment, constructed `Pg8000DriverAdapter` through the centralized selector, built the package, committed only `uv.lock`, and removed its own temporary workflow in the same descendant. Commit `e1045b6ed74e848cd99a50b02b42fe731fcc8b9b` is the resulting graph repair. Because that commit was authored by the workflow token, its automatically materialized pull-request CI/Release runs reported `action_required` without jobs; they are not GREEN evidence. A normal user-authored descendant must therefore re-run the full exact-head acceptance on the unchanged repaired graph. +A bounded exact-head lock finalizer regenerated and verified the lock, proved that the project default runtime edge contains pg8000 and excludes Psycopg, ran `uv sync --locked --no-dev`, proved exact pg8000 1.31.5 is installed while Psycopg is absent from that production environment, constructed `Pg8000DriverAdapter` through the centralized selector, built the package, committed only `uv.lock`, and removed its own temporary workflow in the same descendant. Commit `e1045b6ed74e848cd99a50b02b42fe731fcc8b9b` is the resulting graph repair. Because that commit was authored by the workflow token, its automatically materialized pull-request CI/Release runs reported `action_required` without jobs; they are not GREEN evidence. + +The later production-promotion RED at `3e0103fcf0a94327828b62137666f52fa12b6561` then exposed three post-cutover defects: the CLI confidentiality classifier had become coupled to pg8000's intentionally narrower connection grammar, a workflow contract still asserted the pre-promotion dependency state, and the packaged restore smoke directly imported removed Psycopg. Minimal causal repair `2afd5be12847b51c8d476c59f5b328c697069780` separated argv confidentiality from concrete-driver connectability, updated the production dependency assertion, and routed restore acceptance through `retained_postgres_driver()`. + +On unchanged `2afd5be12847b51c8d476c59f5b328c697069780`, CI `34256203888` and Release Acceptance `34256203857` both completed successfully. The CI generation covered Python 3.10/3.11/3.12/3.13/3.14 plus the container/PostgreSQL runtime lane; its quality job reported 100% public docstrings, 100% owned production statement/branch coverage (`4632/4632`, `1318/1318`), `1649 passed, 5 deselected`, lock verification, and package build success. This proves the Draft branch's then-exact source, not protected integration or immutable release. + +### Transport-security boundary remains separate + +Issue #322 is a dependency-license migration and does not close PostgreSQL transport-security issue #123. pg8000 1.31.5 documents `ssl_context=None` as attempting SSL and falling back to a plain socket when the server rejects SSL. The current #323 adapter does not supply an explicit verified `SSLContext`, so its successful PostgreSQL smokes are not evidence that remote transport encryption or server identity is mandatory. PostgreSQL 18 recommends `verify-full` in security-sensitive environments because it requires encryption, CA validation, and hostname matching. + +Issue #123 remains the canonical owner for the package-wide transport policy. Its acceptance must cover package-created remote TCP connections, deliberate local/embedding-host exceptions, trusted CA/hostname success, wrong CA and hostname mismatch, downgrade/plaintext refusal, recovery, and caller-owned connection authority. #323 must not race that broad policy or describe the driver migration as TLS/server-identity completion. ## Highest-priority gaps | Gap | Current state | Required next evidence | | --- | --- | --- | -| Commercial PostgreSQL runtime dependency | P0 / active Draft | Re-prove the repaired pg8000 default graph on one exact current #323 head, then carry it through normal protected integration and immutable release evidence. | -| Production driver contract parity | Active / promoted on branch | Run real PostgreSQL/RLS/recovery/health/migration/package-installed acceptance through the production selector and fail closed on any pg8000 semantic mismatch. | +| Commercial PostgreSQL runtime dependency | P0 / active Draft / exact branch GREEN observed | Preserve the proven pg8000 default graph through normal prerequisite integration, non-force reconciliation, one unchanged final #323 head, protected merge, and immutable release evidence. Any new #323 commit must reacquire exact-head acceptance. | +| PostgreSQL transport encryption / server identity | P0 security / canonical issue #123 | Complete the existing owner lane with realistic TLS-enabled PostgreSQL acceptance, explicit downgrade refusal and server-identity verification; do not infer this from #322 or pg8000's opportunistic default. | +| Production driver contract parity | Active / promoted on branch | Preserve real PostgreSQL/RLS/recovery/health/migration/package-installed acceptance through the production selector and fail closed on any newly proven pg8000 semantic mismatch. | | Supply-chain admission | Active / strengthened | Bind exact pg8000 closure hashes, permissive-license evidence, vulnerability results, built package, SBOM, provenance, and reproducibility to the same final artifact/head. | | Public commercial-license surface | Child lane #321 | Keep README/docs public wording conservative until #323's pg8000 graph is protected and accepted; then non-force restack #321 and update only its owned public files. | | Dependency-root governance | External owner paths / non-passing | #233 still requires authenticated current-head central CodeQL/OpenCode/Noema settlement and a satisfiable independent approval path before normal protected integration. | @@ -53,7 +64,8 @@ Completion requires all of the following on the final production graph, not only - the committed default runtime graph and built artifacts contain no disallowed GPL/LGPL/AGPL-family package; - retained Psycopg verification dependencies remain outside production/default installation and release runtime evidence; - package, license, vulnerability, SBOM, provenance, and reproducibility evidence bind the same immutable artifacts; -- the final unchanged protected head passes exact-source required checks and then-live review/ruleset requirements without self-approval or gate weakening. +- the final unchanged protected head passes exact-source required checks and then-live review/ruleset requirements without self-approval or gate weakening; and +- issue #322 completion is not represented as transport-security completion: remote TLS/server-identity policy remains issue #123 authority until that separate acceptance is integrated and released. ## Context Fabric boundary @@ -64,3 +76,9 @@ Prompt, response, batch-result, and user data remain pg/product-domain data and ## Evidence discipline Queued, pending, skipped-required, `action_required`, cancelled, absent, predecessor-head, model-only, and status-only evidence is non-passing. A current blocker is the next work item at its actual owner: pg-owned causes require a realistic RED, the smallest causal repair, focused/full GREEN, and exact-head refetch; foreign-owned causes require advancement of the existing owner path followed by independent pg work. A report, comment, handoff, or documentation-only change is never completion while executable code/test/release work remains. + +## References + +PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: SSL support*. https://www.postgresql.org/docs/18/libpq-ssl.html + +Locke, T. (2025). *pg8000 1.31.5: Python interface to PostgreSQL*. PyPI. https://pypi.org/project/pg8000/1.31.5/ diff --git a/pg_llm_batch/pg8000_driver_candidate_errors.py b/pg_llm_batch/pg8000_driver_candidate_errors.py index 0319985b..f1ccee21 100644 --- a/pg_llm_batch/pg8000_driver_candidate_errors.py +++ b/pg_llm_batch/pg8000_driver_candidate_errors.py @@ -1,10 +1,11 @@ -"""Candidate-only pg8000 PostgreSQL error classification. +"""pg8000 PostgreSQL error classification shared by admission and runtime use. -The production package still uses Psycopg while the commercial driver migration -is incomplete. This module proves only narrow pg8000 error semantics needed by -``PostgresDriverPort`` without importing pg8000 into the committed runtime -dependency graph. Callers must inject the exact admitted DB-API module from the -candidate environment; message text is never used as authority. +The exact pg8000 1.31.5 production adapter now reuses this narrow classifier +that was originally proved in the candidate lane. Callers inject the admitted +DB-API module; classification relies on exact exception type plus SQLSTATE and +never on message text. Keeping the already reviewed classifier avoids a second +runtime error-authority implementation while protected integration and release +evidence remain pending. """ from __future__ import annotations diff --git a/pg_llm_batch/pg8000_thread_affine_candidate_adapter.py b/pg_llm_batch/pg8000_thread_affine_candidate_adapter.py index 92861e6e..1758f9e2 100644 --- a/pg_llm_batch/pg8000_thread_affine_candidate_adapter.py +++ b/pg_llm_batch/pg8000_thread_affine_candidate_adapter.py @@ -1,15 +1,15 @@ -"""Thread-affine pg8000 candidate adapters for concurrency admission. +"""Thread-affine pg8000 adapters for concurrency admission and runtime reuse. pg8000 1.31.5 declares DB-API ``threadsafety == 1``: threads may share the -module, but not connections. The portable candidate adapters intentionally do -not invent a stronger guarantee. This layer binds each candidate connection and -cursor to the thread that created it and fails before raw driver access when a -capability crosses that boundary. - -The layer remains candidate-only. It is exercised by the exact-artifact -PostgreSQL smoke test and must not be treated as production dependency approval -until the remaining conninfo, error, recovery, package, SBOM, and provenance -gates pass on one immutable artifact. +module, but not connections. These adapters intentionally do not invent a +stronger guarantee. They bind each connection and cursor to the thread that +created it and fail before raw driver access when a capability crosses that +boundary. + +This layer began as candidate evidence and is now reused by the admitted +production ``Pg8000DriverAdapter``. The candidate class names preserve review +lineage; production authority still belongs to the exact-version loader plus the +repository's protected integration, package, SBOM, provenance, and release gates. """ from __future__ import annotations diff --git a/pg_llm_batch/postgres_driver_port.py b/pg_llm_batch/postgres_driver_port.py index 61a432fd..65ef746a 100644 --- a/pg_llm_batch/postgres_driver_port.py +++ b/pg_llm_batch/postgres_driver_port.py @@ -1,8 +1,9 @@ """Provider-neutral PostgreSQL driver contracts for runtime decoupling. -The package currently has direct Psycopg coupling at several infrastructure -boundaries. These abstract ports describe the database capabilities those -callers actually need without choosing a concrete PostgreSQL driver. Concrete +The active production-migration lane selects an admitted pg8000 adapter behind +these ports, while Psycopg remains only an optional development/test baseline on +that lane. These abstract ports describe the database capabilities bounded +contexts actually need without choosing a concrete PostgreSQL driver. Concrete adapters remain infrastructure concerns and must preserve parameterized SQL, transaction semantics, connection-string handling, JSONB adaptation, and PostgreSQL error classification. From c83cbcee04a49771cce7b1b1575bcf3d54bf0af3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 02:48:46 +0900 Subject: [PATCH 331/338] docs(postgres): remove stale candidate-only JSONB claims --- pg_llm_batch/pg8000_driver_candidate_jsonb.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/pg_llm_batch/pg8000_driver_candidate_jsonb.py b/pg_llm_batch/pg8000_driver_candidate_jsonb.py index f8128d72..74317fb9 100644 --- a/pg_llm_batch/pg8000_driver_candidate_jsonb.py +++ b/pg_llm_batch/pg8000_driver_candidate_jsonb.py @@ -1,11 +1,11 @@ -"""Candidate-only JSONB adaptation for the pg8000 migration lane. - -pg8000 1.31.5's DB-API contract sends JSON as serialized text and returns JSON -values deserialized. The production runtime still uses Psycopg; this module only -proves the JSONB parameter adaptation needed before a permissively licensed -candidate can implement ``PostgresDriverPort.jsonb``. It does not promote -pg8000 into the runtime dependency graph or bypass the remaining conninfo, RLS, -recovery, package, SBOM, and provenance gates. +"""JSONB adaptation shared by pg8000 admission and production runtime. + +pg8000 1.31.5 sends JSON as serialized text and returns JSON values +deserialized. ``Pg8000DriverAdapter`` reuses this serializer after exact-artifact +admission so JSONB semantics stay in one reviewed anti-corruption boundary rather +than being copied into a second production implementation. The historical +``candidate`` names preserve the migration evidence lineage; protected +integration and immutable release remain separate authorities. """ from __future__ import annotations @@ -14,11 +14,11 @@ class Pg8000CandidateJsonbError(RuntimeError): - """Report an invalid candidate JSONB value without reflecting payload content. + """Report an invalid pg8000 JSONB value without reflecting payload content. The error intentionally contains no serialized value because batch payloads may contain purpose-bound user or provider content. Callers can classify the - candidate contract failure without turning diagnostics into a content leak. + adaptation failure without turning diagnostics into a content leak. """ @@ -27,11 +27,11 @@ def adapt_pg8000_jsonb(value: object) -> str: PostgreSQL JSON/JSONB does not admit IEEE non-finite numeric literals, and an isolated Unicode surrogate cannot be encoded as the UTF-8 client text sent to - PostgreSQL. The candidate therefore fails closed before database I/O for - either case and for objects that Python's JSON encoder cannot represent. - Non-ASCII text is retained as Unicode rather than escaped so the adapter can - exercise the same client-encoding boundary used by real multilingual batch - payloads. + PostgreSQL. The adapter therefore fails closed before database I/O for either + case and for objects that Python's JSON encoder cannot represent. Non-ASCII + text is retained as Unicode rather than escaped so production and admission + paths exercise the same client-encoding boundary used by real multilingual + batch payloads. Args: value: A caller-validated JSON-compatible Python value. From 565d60a0de56b82dda2d1786db17c44e4ba5a1f9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 03:25:48 +0900 Subject: [PATCH 332/338] docs(product): refresh commercial gap evidence --- docs/product-technical-gap-baseline.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 9bdd8fb6..441e2c52 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -14,7 +14,7 @@ The repository has no immutable GitHub release at the latest refresh. A green Dr ## Active delivery lanes -PR #233 remains the dependency-root integration lane and must be judged from its live head and base. Its repository-local deterministic lanes have been green on the unchanged current head, while required central CodeQL/OpenCode/Noema settlement and a structurally satisfiable independent approval path remain non-passing owner prerequisites. Leaf churn, synthetic status, self-approval, and routine administrator bypass are not substitutes. The central CodeQL repair has continued to move through its own ordinary owner lineage; pg-llm-batch must consume only protected central behavior and must not copy that mutable workflow source locally. +PR #233 remains the dependency-root integration lane and must be judged from its live head and base. Its repository-local deterministic lanes remain green on exact `01d231fde23b82e2ced258d7bfcb4721ed75706d`, while required compatibility CodeQL, OpenCode, Noema, and a structurally satisfiable independent approval path remain non-passing owner prerequisites. The current canonical central CodeQL repair is `.github#2040` at exact `6706c231ab06a3c91c43fdb5b989cfcd79fff593`; its Security Scan, Semgrep, Python Security, and Agent Review Runtime Quality runs are successful but CodeQL PR `34251822255` is terminal failure. Leaf churn, synthetic status, self-approval, and routine administrator bypass are not substitutes. pg-llm-batch must consume only protected central behavior and must not copy mutable central workflow source locally. PR #323 is the active Draft migration lane for issue #322. It established `PostgresDriverPort`, retained the Psycopg implementation as a verification baseline, proved pg8000 1.31.5 behind the neutral port, and has now promoted the admitted pg8000 adapter into the branch's single centralized runtime selector. The branch manifest declares exact `pg8000==1.31.5` as the default runtime dependency; Psycopg 3.3.4 remains only in the `test` optional dependency and `dev` dependency group for legacy-baseline verification. The regenerated lock resolves pg8000 with `python-dateutil` and `scramp`/`asn1crypto` and no Psycopg dependency in the project default runtime edge. @@ -30,23 +30,29 @@ A bounded exact-head lock finalizer regenerated and verified the lock, proved th The later production-promotion RED at `3e0103fcf0a94327828b62137666f52fa12b6561` then exposed three post-cutover defects: the CLI confidentiality classifier had become coupled to pg8000's intentionally narrower connection grammar, a workflow contract still asserted the pre-promotion dependency state, and the packaged restore smoke directly imported removed Psycopg. Minimal causal repair `2afd5be12847b51c8d476c59f5b328c697069780` separated argv confidentiality from concrete-driver connectability, updated the production dependency assertion, and routed restore acceptance through `retained_postgres_driver()`. -On unchanged `2afd5be12847b51c8d476c59f5b328c697069780`, CI `34256203888` and Release Acceptance `34256203857` both completed successfully. The CI generation covered Python 3.10/3.11/3.12/3.13/3.14 plus the container/PostgreSQL runtime lane; its quality job reported 100% public docstrings, 100% owned production statement/branch coverage (`4632/4632`, `1318/1318`), `1649 passed, 5 deselected`, lock verification, and package build success. This proves the Draft branch's then-exact source, not protected integration or immutable release. +That repair stayed intact through ordinary descendants. Exact #323 head `c83cbcee04a49771cce7b1b1575bcf3d54bf0af3` reached CI `34259353013` and Release Acceptance `34259353130` terminal success. All seven CI jobs succeeded: Python 3.10/3.11/3.12/3.13/3.14, coverage/docstrings/lint/package, and the container/PostgreSQL runtime-smoke lane. The container lane re-verified exact pg8000 dependency/source digests and permissive-license evidence, release-Python installation, and real PostgreSQL pg8000 smokes on Python 3.10/3.12/3.14. This proves the Draft branch's exact source at that head, not protected integration or immutable release. + +### Public-surface descendant RED and repair + +PR #321 owns only `README.md` and `docs/index.md` relative to #323. After it was reconciled onto exact parent `c83cbcee04a49771cce7b1b1575bcf3d54bf0af3`, child head `d546f6d8106cbf41bf5d72fa8e595363c4e7febe` exposed a real documentation RED in CI `34260943035`: five current-parent operator/security contracts had been dropped from README while production coverage and public-docstring gates remained satisfied. + +Minimum causal repair `224ed124b675eaf0ec1f558a458286610387500b` changed only README versus that RED head. It restored the explicit 1 MiB `count-tokens` stdin limit, canonical retirement wording for the old SQL provider retriever, the closed transient GET retry-status set, the `source_superusers_trusted` logical-restore trust/rollback boundary, and explicit non-retry rules for TLS handshake/certificate and fingerprint failures. Exact-head CI `34262344110` and Release Acceptance `34262344236` then completed successfully. #321 remains Draft because parent integration, qualifying approval, central required checks, and immutable release authority remain unsatisfied; its GREEN is not shipped truth. ### Transport-security boundary remains separate -Issue #322 is a dependency-license migration and does not close PostgreSQL transport-security issue #123. pg8000 1.31.5 documents `ssl_context=None` as attempting SSL and falling back to a plain socket when the server rejects SSL. The current #323 adapter does not supply an explicit verified `SSLContext`, so its successful PostgreSQL smokes are not evidence that remote transport encryption or server identity is mandatory. PostgreSQL 18 recommends `verify-full` in security-sensitive environments because it requires encryption, CA validation, and hostname matching. +Issue #322 is a dependency-license migration and does not close PostgreSQL transport-security issue #123. The current #323 adapter does not yet establish a package-wide mandatory verified remote-TLS/server-identity policy, so its successful PostgreSQL smokes are not evidence that remote transport encryption or server identity is mandatory. PostgreSQL 18 recommends `verify-full` in security-sensitive environments because it requires encryption, CA validation, and hostname matching. -Issue #123 remains the canonical owner for the package-wide transport policy. Its acceptance must cover package-created remote TCP connections, deliberate local/embedding-host exceptions, trusted CA/hostname success, wrong CA and hostname mismatch, downgrade/plaintext refusal, recovery, and caller-owned connection authority. #323 must not race that broad policy or describe the driver migration as TLS/server-identity completion. +Issue #123 remains the canonical owner for the package-wide transport policy. Its acceptance must cover package-created remote TCP connections, deliberate local/embedding-host exceptions, trusted CA/hostname success, wrong CA and hostname mismatch, downgrade/plaintext refusal, recovery, and caller-owned connection authority. #323 and #321 must not race that broad policy or describe the driver migration as TLS/server-identity completion. ## Highest-priority gaps | Gap | Current state | Required next evidence | | --- | --- | --- | | Commercial PostgreSQL runtime dependency | P0 / active Draft / exact branch GREEN observed | Preserve the proven pg8000 default graph through normal prerequisite integration, non-force reconciliation, one unchanged final #323 head, protected merge, and immutable release evidence. Any new #323 commit must reacquire exact-head acceptance. | -| PostgreSQL transport encryption / server identity | P0 security / canonical issue #123 | Complete the existing owner lane with realistic TLS-enabled PostgreSQL acceptance, explicit downgrade refusal and server-identity verification; do not infer this from #322 or pg8000's opportunistic default. | +| PostgreSQL transport encryption / server identity | P0 security / canonical issue #123 | Complete the existing owner lane with realistic TLS-enabled PostgreSQL acceptance, explicit downgrade refusal and server-identity verification; do not infer this from #322 or a successful pg8000 connection. | | Production driver contract parity | Active / promoted on branch | Preserve real PostgreSQL/RLS/recovery/health/migration/package-installed acceptance through the production selector and fail closed on any newly proven pg8000 semantic mismatch. | | Supply-chain admission | Active / strengthened | Bind exact pg8000 closure hashes, permissive-license evidence, vulnerability results, built package, SBOM, provenance, and reproducibility to the same final artifact/head. | -| Public commercial-license surface | Child lane #321 | Keep README/docs public wording conservative until #323's pg8000 graph is protected and accepted; then non-force restack #321 and update only its owned public files. | +| Public commercial-license surface | Child lane #321 / exact child GREEN observed | Preserve its two-file semantic delta across future parent movement with ordinary non-force reconciliation; do not present it as shipped until #323 integrates and protected release evidence exists. | | Dependency-root governance | External owner paths / non-passing | #233 still requires authenticated current-head central CodeQL/OpenCode/Noema settlement and a satisfiable independent approval path before normal protected integration. | | Immutable product release | Not yet published | After the production dependency replacement and all gates pass on one integrated protected head, perform version/CHANGELOG/tag/package/SBOM/provenance/reproducibility/rollback publication and verify artifact identity. | | Context Graph / EA projection | Candidate-only until released authority exists | Do not pin mutable producer heads. Continue pg-owned release-readiness seams and adopt only verified released contracts from canonical owners. | From 86892562f37c59bf54c2d708e4bae8d61dd6b032 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 04:06:05 +0900 Subject: [PATCH 333/338] test(postgres): reject service-file symlink authority --- ...t_pg8000_service_file_symlink_authority.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/test_pg8000_service_file_symlink_authority.py diff --git a/tests/test_pg8000_service_file_symlink_authority.py b/tests/test_pg8000_service_file_symlink_authority.py new file mode 100644 index 00000000..813847e7 --- /dev/null +++ b/tests/test_pg8000_service_file_symlink_authority.py @@ -0,0 +1,34 @@ +"""Final-component authority regressions for explicit pg8000 service files.""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from pg_llm_batch.pg8000_candidate_driver_port import Pg8000CandidateInvalidConninfoError +from pg_llm_batch.pg8000_candidate_service_file import Pg8000CandidateServiceFileResolver + + +def test_candidate_service_file_rejects_final_symlink_authority(tmp_path: Path) -> None: + """Reject a final symlink before it can redirect connection-selector authority.""" + target = tmp_path / "actual.conf" + target.write_text( + "[analytics]\n" + "host=redirected.example\n" + "port=5432\n" + "dbname=batch\n" + "user=batch\n", + encoding="utf-8", + ) + service_file = tmp_path / "pg_service.conf" + try: + service_file.symlink_to(target) + except (NotImplementedError, OSError): + pytest.skip("filesystem does not support symlinks") + + with pytest.raises( + Pg8000CandidateInvalidConninfoError, + match="PostgreSQL connection selector is invalid", + ): + Pg8000CandidateServiceFileResolver(service_file)("analytics") From 89757e4396b796a1dd620b9bbba111f80d86a3ae Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 04:07:08 +0900 Subject: [PATCH 334/338] test(postgres): prove service-file path substitution RED --- ...t_pg8000_service_file_symlink_authority.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/test_pg8000_service_file_symlink_authority.py b/tests/test_pg8000_service_file_symlink_authority.py index 813847e7..5cd3989b 100644 --- a/tests/test_pg8000_service_file_symlink_authority.py +++ b/tests/test_pg8000_service_file_symlink_authority.py @@ -2,6 +2,7 @@ from __future__ import annotations +import os from pathlib import Path import pytest @@ -32,3 +33,41 @@ def test_candidate_service_file_rejects_final_symlink_authority(tmp_path: Path) match="PostgreSQL connection selector is invalid", ): Pg8000CandidateServiceFileResolver(service_file)("analytics") + + +def test_candidate_service_file_rejects_path_substitution_before_open( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Retain the selected inode when pathname resolution changes before open.""" + service_file = tmp_path / "pg_service.conf" + service_file.write_text( + "[analytics]\nhost=selected.example\nport=5432\ndbname=batch\nuser=batch\n", + encoding="utf-8", + ) + replacement = tmp_path / "replacement.conf" + replacement.write_text( + "[analytics]\nhost=redirected.example\nport=5432\ndbname=batch\nuser=batch\n", + encoding="utf-8", + ) + real_open = os.open + + def substituted_open( + path: str | bytes | os.PathLike[str] | os.PathLike[bytes], + flags: int, + mode: int = 0o777, + *, + dir_fd: int | None = None, + ) -> int: + selected = replacement if Path(path) == service_file else path + if dir_fd is None: + return real_open(selected, flags, mode) + return real_open(selected, flags, mode, dir_fd=dir_fd) + + monkeypatch.setattr(os, "open", substituted_open) + + with pytest.raises( + Pg8000CandidateInvalidConninfoError, + match="PostgreSQL connection selector is invalid", + ): + Pg8000CandidateServiceFileResolver(service_file)("analytics") From 63ef822c2b48cf4ff2d9dddcf8641ba0ca652ff3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 04:07:44 +0900 Subject: [PATCH 335/338] fix(postgres): retain service-file final-component authority --- pg_llm_batch/pg8000_candidate_service_file.py | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/pg_llm_batch/pg8000_candidate_service_file.py b/pg_llm_batch/pg8000_candidate_service_file.py index 0eb664ce..b94e0a87 100644 --- a/pg_llm_batch/pg8000_candidate_service_file.py +++ b/pg_llm_batch/pg8000_candidate_service_file.py @@ -84,12 +84,21 @@ def _close_descriptor(descriptor: int, *, preserve_primary_error: bool) -> None: def _read_bounded_utf8(path: Path) -> str: """Read one explicit regular service file under a finite UTF-8 byte budget. - The caller-selected path is opened nonblocking where the platform supports - it, then the retained descriptor is required to name one stable regular - file before and after the bounded read. This prevents a FIFO/device path or - in-place mutation from becoming connection-selector authority while bytes - are being inspected. + The caller-selected final path component must itself be a regular file. Its + device/inode identity is captured before opening and must match the retained + descriptor, so a symlink or pathname substitution cannot redirect database + connection authority. The descriptor is opened nonblocking where the + platform supports it and remains metadata-stable before and after the + bounded read. """ + try: + selected = os.lstat(path) + except (OSError, ValueError): + raise _invalid_service_file() from None + if stat.S_ISLNK(selected.st_mode) or not stat.S_ISREG(selected.st_mode): + raise _invalid_service_file() + selected_identity = (selected.st_dev, selected.st_ino) + flags = ( os.O_RDONLY | getattr(os, "O_BINARY", 0) @@ -104,7 +113,10 @@ def _read_bounded_utf8(path: Path) -> str: primary_error: BaseException | None = None try: before = os.fstat(descriptor) - if not stat.S_ISREG(before.st_mode): + if ( + not stat.S_ISREG(before.st_mode) + or (before.st_dev, before.st_ino) != selected_identity + ): raise _invalid_service_file() before_snapshot = _service_file_snapshot(before) From 251cdec97c95f2e842b0fcbeacdd43cd86395c0d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 04:11:55 +0900 Subject: [PATCH 336/338] test(postgres): cover service-file preflight open failure --- ...t_pg8000_service_file_symlink_authority.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/test_pg8000_service_file_symlink_authority.py b/tests/test_pg8000_service_file_symlink_authority.py index 5cd3989b..f6aa1f7b 100644 --- a/tests/test_pg8000_service_file_symlink_authority.py +++ b/tests/test_pg8000_service_file_symlink_authority.py @@ -71,3 +71,33 @@ def substituted_open( match="PostgreSQL connection selector is invalid", ): Pg8000CandidateServiceFileResolver(service_file)("analytics") + + +def test_candidate_service_file_normalizes_open_failure_after_preflight( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Fail closed if the selected regular path cannot be opened after preflight.""" + service_file = tmp_path / "pg_service.conf" + service_file.write_text( + "[analytics]\nhost=selected.example\nport=5432\ndbname=batch\nuser=batch\n", + encoding="utf-8", + ) + + def failing_open( + _path: str | bytes | os.PathLike[str] | os.PathLike[bytes], + _flags: int, + _mode: int = 0o777, + *, + dir_fd: int | None = None, + ) -> int: + del dir_fd + raise OSError("synthetic path-open failure") + + monkeypatch.setattr(os, "open", failing_open) + + with pytest.raises( + Pg8000CandidateInvalidConninfoError, + match="PostgreSQL connection selector is invalid", + ): + Pg8000CandidateServiceFileResolver(service_file)("analytics") From 868e4f531108dea68196fd21695e66ed1572a952 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 04:18:53 +0900 Subject: [PATCH 337/338] docs(postgres): correct pg8000 production dependency boundary --- pg_llm_batch/pg8000_candidate_driver_port.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pg_llm_batch/pg8000_candidate_driver_port.py b/pg_llm_batch/pg8000_candidate_driver_port.py index 22a5262c..8291f9bc 100644 --- a/pg_llm_batch/pg8000_candidate_driver_port.py +++ b/pg_llm_batch/pg8000_candidate_driver_port.py @@ -10,10 +10,11 @@ pg8000. Query options, multi-host/socket forms, and other libpq-only semantics remain fail closed until they have separate compatibility evidence. -The module does not import pg8000. An exact candidate DB-API module must be -injected after artifact, license, integrity, and environment admission, keeping -pg8000 out of the committed production dependency graph while issue #322 remains -open. +The module does not import pg8000. The runtime loader injects the exact admitted +DB-API module only after artifact, license, integrity, and environment admission. +pg8000 is now the pinned production runtime driver on this migration branch; +issue #322 remains open until that graph reaches protected main and an immutable +release carries the required license, SBOM, provenance, and rollback evidence. """ from __future__ import annotations From 47e5db087cb59670c7800dc9d67f7c08ce2b9e67 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 04:20:00 +0900 Subject: [PATCH 338/338] docs(gap): record service-file authority repair evidence --- docs/product-technical-gap-baseline.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 441e2c52..6fed85e7 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -20,7 +20,7 @@ PR #323 is the active Draft migration lane for issue #322. It established `Postg The promotion preserves the existing candidate evidence: parameter binding, native no-parameter DB-API execution, tuple-row normalization, finite fetch budgets, exact/unknown row counts, transaction/context ownership, terminal connection state, thread-affine use, PostgreSQL RLS/session behavior, UUID/timestamp round-trip, SQLSTATE classification, cleanup precedence, JSONB adaptation, strict single-host URI/keyword conninfo parsing, explicit service-file resolution without ambient `PGSERVICEFILE` discovery, packaged restore-catalog acceptance, server-terminated-session recovery, exact dependency/license evidence, source-to-wheel Python payload parity, and package-installed execution outside the checkout across the supported Python matrix. Unsupported multi-host/socket/query/LDAP/ambient-service semantics remain fail closed rather than approximated. -The production construction boundary `load_pg8000_driver()` admits only exact pg8000 1.31.5, verifies the installed distribution identity and top-level import origin before package code executes, and optionally composes one caller-selected service file. The centralized `retained_postgres_driver()` now constructs that admitted adapter. This is a branch-level source/runtime fact, not protected-main or release authority. +The production construction boundary `load_pg8000_driver()` admits only exact pg8000 1.31.5, verifies the installed distribution identity and top-level import origin before package code executes, and optionally composes one caller-selected service file. The centralized `retained_postgres_driver()` now constructs that admitted adapter. This is a branch-level source/runtime fact, not protected-main or release authority. The candidate driver-port module deliberately does not import pg8000 itself; the runtime loader injects the admitted DB-API module. pg8000 is nevertheless a pinned production dependency on this migration branch, so issue #322 remains open because protected integration and immutable-release evidence are incomplete, not because pg8000 is absent from the production graph. ### Runtime-graph RED and causal repair @@ -32,11 +32,19 @@ The later production-promotion RED at `3e0103fcf0a94327828b62137666f52fa12b6561` That repair stayed intact through ordinary descendants. Exact #323 head `c83cbcee04a49771cce7b1b1575bcf3d54bf0af3` reached CI `34259353013` and Release Acceptance `34259353130` terminal success. All seven CI jobs succeeded: Python 3.10/3.11/3.12/3.13/3.14, coverage/docstrings/lint/package, and the container/PostgreSQL runtime-smoke lane. The container lane re-verified exact pg8000 dependency/source digests and permissive-license evidence, release-Python installation, and real PostgreSQL pg8000 smokes on Python 3.10/3.12/3.14. This proves the Draft branch's exact source at that head, not protected integration or immutable release. +### Service-file authority RED and causal repair + +The explicit service-file resolver originally validated only the descriptor obtained after `os.open()`. A caller-selected final symlink, or a pathname substitution between selection and open, could therefore redirect the supposedly explicit `pg_service.conf` capability to another regular file and change database host/user/database/password authority without failing admission. This is a connection-authority defect even though ambient `PGSERVICEFILE` discovery remains disabled. + +Real final-symlink and deterministic path-substitution regressions were introduced at `86892562f37c59bf54c2d708e4bae8d61dd6b032` and `89757e4396b796a1dd620b9bbba111f80d86a3ae`. Subsequent descendant pushes cancelled their hosted runs before terminal test execution, so those commits are test-first source evidence rather than falsely reported hosted RED. Minimal production repair `63ef822c2b48cf4ff2d9dddcf8641ba0ca652ff3` now performs `lstat()` on the caller-selected final component, requires that component itself to be a regular file, retains its `(st_dev, st_ino)` identity, opens the file, and requires the descriptor's `fstat()` identity to match before reading. The existing bounded byte budget, regular-file check, before/after metadata stability, strict UTF-8 decoding, and generic non-content-bearing errors remain intact. + +Exact `63ef822...` then produced a separate quality RED in CI `34267147130`: all functional/unit/Python/container behavior passed, but the 100% owned-production coverage gate found the newly added `os.open()` failure normalization unexercised. Commit `251cdec97c95f2e842b0fcbeacdd43cd86395c0d` added the focused preflight-open-failure regression without changing production behavior. Exact CI `34267560647` and Release Acceptance `34267560557` both completed successfully. The quality lane reported `1652 passed, 5 deselected`, public docstrings 100%, and production coverage exactly 100% across 4,639 statements and 1,320 branches with zero misses/partials. The current documentation descendants must reacquire exact-head acceptance rather than inheriting that predecessor GREEN by assertion. + ### Public-surface descendant RED and repair PR #321 owns only `README.md` and `docs/index.md` relative to #323. After it was reconciled onto exact parent `c83cbcee04a49771cce7b1b1575bcf3d54bf0af3`, child head `d546f6d8106cbf41bf5d72fa8e595363c4e7febe` exposed a real documentation RED in CI `34260943035`: five current-parent operator/security contracts had been dropped from README while production coverage and public-docstring gates remained satisfied. -Minimum causal repair `224ed124b675eaf0ec1f558a458286610387500b` changed only README versus that RED head. It restored the explicit 1 MiB `count-tokens` stdin limit, canonical retirement wording for the old SQL provider retriever, the closed transient GET retry-status set, the `source_superusers_trusted` logical-restore trust/rollback boundary, and explicit non-retry rules for TLS handshake/certificate and fingerprint failures. Exact-head CI `34262344110` and Release Acceptance `34262344236` then completed successfully. #321 remains Draft because parent integration, qualifying approval, central required checks, and immutable release authority remain unsatisfied; its GREEN is not shipped truth. +Minimum causal repair `224ed124b675eaf0ec1f558a458286610387500b` changed only README versus that RED head. It restored the explicit 1 MiB `count-tokens` stdin limit, canonical retirement wording for the old SQL provider retriever, the closed transient GET retry-status set, the `source_superusers_trusted` logical-restore trust/rollback boundary, and explicit non-retry rules for TLS handshake/certificate and fingerprint failures. Exact-head CI `34262344110` and Release Acceptance `34262344236` then completed successfully. #321 remains Draft because parent integration, qualifying approval, central required checks, and immutable release authority remain unsatisfied. Since #323 has moved again for the service-file authority repair, #321 must be non-force reconciled onto the final current parent before its prior GREEN can be treated as current child evidence. ### Transport-security boundary remains separate @@ -48,11 +56,12 @@ Issue #123 remains the canonical owner for the package-wide transport policy. It | Gap | Current state | Required next evidence | | --- | --- | --- | -| Commercial PostgreSQL runtime dependency | P0 / active Draft / exact branch GREEN observed | Preserve the proven pg8000 default graph through normal prerequisite integration, non-force reconciliation, one unchanged final #323 head, protected merge, and immutable release evidence. Any new #323 commit must reacquire exact-head acceptance. | +| Commercial PostgreSQL runtime dependency | P0 / active Draft / exact predecessor GREEN observed | Preserve the proven pg8000 default graph through exact-head revalidation of current documentation descendants, normal prerequisite integration, non-force child reconciliation, one unchanged final #323 head, protected merge, and immutable release evidence. | | PostgreSQL transport encryption / server identity | P0 security / canonical issue #123 | Complete the existing owner lane with realistic TLS-enabled PostgreSQL acceptance, explicit downgrade refusal and server-identity verification; do not infer this from #322 or a successful pg8000 connection. | +| Explicit service-file authority | Active / repaired on #323 | Preserve final-component identity retention and bounded parsing through final exact-head CI, package-installed/runtime acceptance, protected integration, and release. Parent-directory race hardening is a separate finding unless a realistic authority-changing RED proves it necessary. | | Production driver contract parity | Active / promoted on branch | Preserve real PostgreSQL/RLS/recovery/health/migration/package-installed acceptance through the production selector and fail closed on any newly proven pg8000 semantic mismatch. | | Supply-chain admission | Active / strengthened | Bind exact pg8000 closure hashes, permissive-license evidence, vulnerability results, built package, SBOM, provenance, and reproducibility to the same final artifact/head. | -| Public commercial-license surface | Child lane #321 / exact child GREEN observed | Preserve its two-file semantic delta across future parent movement with ordinary non-force reconciliation; do not present it as shipped until #323 integrates and protected release evidence exists. | +| Public commercial-license surface | Child lane #321 / stale after parent movement | Preserve its two-file semantic delta with ordinary non-force reconciliation onto the final #323 head; reacquire exact child CI/Release and do not present it as shipped until protected release evidence exists. | | Dependency-root governance | External owner paths / non-passing | #233 still requires authenticated current-head central CodeQL/OpenCode/Noema settlement and a satisfiable independent approval path before normal protected integration. | | Immutable product release | Not yet published | After the production dependency replacement and all gates pass on one integrated protected head, perform version/CHANGELOG/tag/package/SBOM/provenance/reproducibility/rollback publication and verify artifact identity. | | Context Graph / EA projection | Candidate-only until released authority exists | Do not pin mutable producer heads. Continue pg-owned release-readiness seams and adopt only verified released contracts from canonical owners. | @@ -66,6 +75,7 @@ Completion requires all of the following on the final production graph, not only - tenant authority and transaction-local `set_config` behavior remain correct under forced RLS and restricted roles; - JSON/JSONB, UUID, timestamp, row, row-count, and relevant PostgreSQL error semantics remain compatible; - DSN parsing/rendering preserves the supported URI, keyword, and explicit-service-selector contract without credential leakage into argv or logs; +- explicit service-file capability selection cannot be redirected by a final-component symlink or a different inode substituted before open; - concurrency, idempotency, checkpoint, schema application, logical restore, health, and finite-connect behavior pass realistic PostgreSQL tests through the production selector; - the committed default runtime graph and built artifacts contain no disallowed GPL/LGPL/AGPL-family package; - retained Psycopg verification dependencies remain outside production/default installation and release runtime evidence;