PG proof: fix task_attempts migration on real Postgres 16.15
ci / test (push) Failing after 33m23s

- attempt_ledger.ensure: relax gap-3 outcome NOT NULL on Postgres
  (ALTER COLUMN outcome DROP NOT NULL, probe-gated); without it
  every resume write on a gap-3-migrated PG table fails with
  NotNullViolation. SQLite keeps NOT NULL (no such ALTER).
- db.PgCursor: PRAGMA table_info fake rows had the notnull flag
  inverted (NO -> 0); now matches sqlite semantics (1 = NOT NULL).
  No caller read row[3] before, so this was latent.
- ledger_onboarding: stale 'PG path unverified' notes updated to
  point at the real-PG proof.
- tests: pragma_info notnull pin (fake driver) + _column_notnull
  probe pins (fresh + gap-3 SQLite).

Proof: 28/28 checks on real Postgres 16.15 (see verification/
codex-task-attempts-pg-proof-20261106.md).
This commit is contained in:
host committed 2026-11-05 21:11:56 +00:00
1 parent 698398dafb
commit 2fcde375a4
5 files changed
+104 -16

No files matched your search

+19
View File
@@ -250,6 +250,25 @@ class AdapterFakeDriverTestCase(unittest.TestCase):
self.assertIn("%s", fake.conn.cursor_obj.executed[0][0])
self.assertNotIn("?", fake.conn.cursor_obj.executed[0][0])
def test_pragma_info_notnull_flag_matches_sqlite(self):
# PG 实证(2026-11-06)抓到的真 bug:适配层把
# information_schema.is_nullable 翻反了(NO -> 0),
# 与 sqlite PRAGMA table_info 的 notnull 语义相反。
# 此前无调用方读 row[3],故只潜伏未炸;ensure 的
# outcome 放宽依赖该标志为真,此处钉住。
rows = [(1, "task_id", "text", "NO", None, 1),
(2, "outcome", "text", "YES", None, 0)]
fake = _FakeDriver(_FakeCursor(rows=rows))
db = _make_db(fake)
conn = db.connect()
info = conn.execute(
"PRAGMA table_info(task_attempts)").fetchall()
by_name = {row[1]: row for row in info}
self.assertEqual(by_name["task_id"][3], 1)
self.assertEqual(by_name["outcome"][3], 0)
self.assertEqual([row[1] for row in info],
["task_id", "outcome"])
def test_unique_violation_maps_to_integrity_error(self):
import sqlite3
fake = _FakeDriver(_FakeCursor(fail_sequence=[_FakeUnique("dup")]))