KB-186D

dot-iu-cutter v0.4 — RealPostgresAdapter Transaction Lifecycle Fix Completion Report (2026-05-17)

6 min read Revision 1
dot-iu-cutterv0.4db-adaptertxn-lifecycle-fixcompletion-reportdieu44

dot-iu-cutter v0.4 — RealPostgresAdapter Transaction Lifecycle Fix — Completion Report

Date: 2026-05-17 · Phase: bounded code-authoring — fix the RealPostgresAdapter transaction/pool defect that the PG-backed dry-run found at S4. Code SSOT = VPS /opt/incomex/dot. No PG-backed dry-run rerun, no production connection/secret/CUT/VERIFY, no deploy, no schema/index/JSONB/vector work.

1. Defect (from the dry-run FAIL) & fix

phases.py calls adapter.find() outside a transaction before with adapter.transaction(). The accepted RealPostgresAdapter.find() ran that SELECT on a pooled connection with autocommit=False and returned it to _ConnPool without commit/rollback, leaving an open implicit transaction; the next phase _begin then could not issue SET TRANSACTION ISOLATION LEVEL as the first statement (psycopg3 25001). Latent because the unit suite's FakeConn did not model that rule.

Fix (db_adapter.py only):

  1. find() out-of-transaction (owns == False)finally now conn.rollback() (clears the implicit read txn and any failed txn) then releases to the pool; if rollback itself fails the connection is discard()-ed. In-phase reads (owns == True) are untouched (the phase owns its single atomic txn).
  2. _begin() defensive cleanconn.rollback() (a method call, not a query, so SET TRANSACTION ISOLATION LEVEL remains the first executed statement) before SET; if the SET / SELECT current_user / identity check fails, the connection is rolled back and discard()-ed and self._conn cleared before propagating (never leaves a dirty connection in the pool).
  3. No global autocommit change; one-atomic-transaction-per-phase, principal routing, and the no DELETE/TRUNCATE/DDL/GRANT runtime surface all preserved.

2. Fix-requirement compliance

Req Status
1 out-of-txn find() must not leave pooled conn in open txn ✅ rollback before release
2 begin can SET ISOLATION as first statement ✅ defensive rollback() (method) precedes SET; regression-tested
3 pooled conn returns clean ✅ find() + _begin both clean/discard
4 query exception rolls back before pool return ✅ find() finally rollback (discard on rollback failure); _begin failure path rolls back+discards
5 no global autocommit change autocommit=False unchanged; only per-conn rollback added
6 one atomic txn per phase ✅ in-phase find() (owns) untouched; defensive rollback is pre-first-statement
7 principal routing preserved ✅ unchanged
8 no DELETE/TRUNCATE/DDL/GRANT surface ✅ none added (rollback/commit are txn control, not data DDL)
9 no hardcoded DSN/password/IP/container ✅ none introduced (see §4)
10 no production connection/secret in tests ✅ injected FakeConn/fixture env only; no psycopg3 needed

3. Tests

  • Enhanced FakeConn (tests/test_real_postgres_adapter.py) now models psycopg3: autocommit-off queries open an implicit txn; SET TRANSACTION ISOLATION LEVEL raises SQLSTATE 25001 if a txn is already open; commit/rollback close it.
  • New tests/test_pg_transaction_lifecycle.py (9 tests): fake models the 25001 rule; out-of-txn find() then transaction() does not fail; find() exception rolls back before pool return; pooled connection clean/reusable after out-of-txn find(); transaction still sets requested isolation first; construction does not connect; fixture env is fake/no-DSN.
  • Command & result: python3 -m unittest discover -s testsRan 110 tests … OK on VPS py3.12 (101 prior + 9 new; all existing pass).

4. No-hardcode static grep (honest)

grep -nE 'postgres://|PGPASSWORD|password=|<ipv4>|incomex-*|qdrant|collection_name' db_adapter.py → 3 hits, all pre-existing benign redaction code, none a literal secret/IP/container:

  • 545: self._password = password (assignment into _Secret holder)
  • 569: f"… password=<redacted-secret>)" (the redacting repr)
  • 605: password = _Secret(_req(pwd_key)) (wraps the env value in the redactor)

No IP / container / DSN / vector-collection literal matched. My edits added only rollback()/discard() calls and comments — zero literals. The accepted security regression test_no_secret_pattern_in_module_source (forbids PGPASSWORD / postgres://) passes within 110/110.

5. Git SSOT proof

  • Branch: main
  • Parent commit: 84c52c57aa296de921998910d85b0d4a85ad0746
  • New commit: 6060e1ae8b958fcb8a61ed45b597dc553b8688be
  • Files changed (scoped, 3): cutter_agent/db_adapter.py (M), tests/test_real_postgres_adapter.py (M), tests/test_pg_transaction_lifecycle.py (A) — 3 files changed, 206 insertions(+), 8 deletions(-). No change to ledger.py/phases.py/schema_binding.py/state_machine.py/idempotency.py/signing.py/signal.py/cli.py (none needed → no STOP required).
  • Scoped add: explicit 3 paths; no git add -A; no unrelated WIP.
  • git status --short -- iu-cutter (excluding the throwaway dry-run WD): empty (clean).
  • Test command & result: python3 -m unittest discover -s testsRan 110 tests … OK.

6. Limitations / next gate

  • No PG-backed dry-run rerun performed (forbidden until this fix PASSes review).
  • No production connection/secret/row write/CUT/VERIFY/deploy; no schema migration / index DDL / JSONB normalization / vector integration.
  • The earlier honest-FAIL artefacts/logs remain under the throwaway WD (secrets shredded at that run's teardown); the DR env from the failed run was torn down exact-name; prod + 3 protected envs untouched.

Next gate: GPT review of commit 6060e1a + this report. On PASS, the PG-backed dry-run may be re-authorised (command-review r1 / verification-plan r3 unchanged; binding count-invariant). No self-advance.

Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.4-db-adapter-dry-run/dot-iu-cutter-v0.4-realpostgresadapter-txn-lifecycle-fix-completion-report-2026-05-17.md