KB-6275
dot-iu-cutter v0.4 — DB Adapter Error & Rollback Design (design only)
11 min read Revision 1
dot-iu-cutterdieu44v0.4db-adapterdesign-onlyerror-handlingrollbacksqlstate
dot-iu-cutter v0.4 — DB Adapter Error & Rollback Design
document_path: knowledge/dev/laws/dieu44-trien-khai/v0.4-db-adapter-design/dot-iu-cutter-v0.4-db-adapter-error-and-rollback-design-2026-05-17.md
revision: r1
date: 2026-05-17
author: Agent (Claude Code CLI, Opus 4.7 1M)
phase: v0.4 — REAL DB ADAPTER DESIGN (companion to design-master)
status: design_only_pending_gpt_review
⛔ DESIGN ONLY. No connection, no error actually raised, no rollback run. The SQLSTATE policy below is a target classification, not executed.
§1 — Failure Taxonomy → Disposition (authoritative)
SQLSTATE / condition class disposition
─────────────────────────────────────────────────────────────────────────────
42501 insufficient_privilege PRIVILEGE STOP immediately. ROLLBACK whole
phase. Emit a high-severity signal
(local/test signal channel, doc:
skeleton OD-4). NEVER retry. NEVER
attempt privilege escalation /
alternate role / GRANT. This means
the agent tried a write outside the
matrix → routing/design bug
(principal-routing doc R-3). Human/
GPT must fix the adapter, not the
grants-at-runtime.
23505 unique_violation IDEMPOTENCY Treat as CONVERGENCE, not failure
(on an idempotency/natural COLLISION IF the colliding key is a known
key) idempotency key: ROLLBACK, then
SELECT the existing row and RETURN
it (dedup / resume). Phase outcome =
success (no duplicate, no double-
apply). If the unique collision is
NOT on a recognised idempotency key
→ treat as design bug → STOP.
23503 foreign_key_violation STRUCTURAL STOP. ROLLBACK whole phase. This is
23502 not_null_violation DESIGN BUG a design bug or invalid input
23514 check_violation / BAD INPUT (no CHECK exists in schema, but
22xxx data_exception defensive). NO retry. NEEDS_HUMAN
escalation. Do NOT auto-mutate
input to "make it fit".
40001 serialization_failure TRANSIENT Bounded retry of the WHOLE phase
40P01 deadlock_detected (RETRYABLE) txn (idempotent → safe). Exponential
55P03 lock_not_available backoff + jitter; max_attempts
57014 query_canceled (timeout) configurable; attempt_no recorded
in existing payload/body column.
On exhaustion → escalate (see §4).
53300 too_many_connections BACKPRESSURE Bounded retry with longer backoff
53400 conn limit reached (respect CONNECTION_LIMIT 2; never
open a 3rd). On exhaustion →
escalate; do NOT widen the limit.
08xxx connection_exception CONNECTION The txn is already aborted server-
(08000/08003/08006/08001/ side → NO partial commit possible
08004) (P-7). Discard the connection,
bounded retry the WHOLE phase from
its last committed boundary. On
persistent failure → fail closed +
escalate (no half-written phase).
28P01 invalid_password CREDENTIAL STOP. Do NOT retry (retry won't fix
28000 invalid_authorization a bad/rotated credential and may
lock/alarm). Fail closed, emit
signal referencing the KEY NAME
only (never the value). Likely a
rotation/desync → human/GPT + a
separate credential cycle.
missing/empty required env CONFIG Fail closed BEFORE any connect
key (FAIL- (doc 2 L-3). Typed ConfigMissing
CLOSED) naming the missing KEY. No default,
no fallback host/password. No
partial work. Not retryable.
semantic verification NEEDS_HUMAN NOT a DB error. This is VERIFY's
mismatch (outcome=fail) designed FAIL path: compensating
change set + escalation entry +
status verify_failed_escalated
(transaction-mapping §5). A human/
GPT loop owns the escalation entry.
unknown / unmapped SQLSTATE CONSERVATIVE Treat as STOP (not retry): ROLLBACK,
escalate, surface the SQLSTATE.
Fail safe, never guess-retry.
§2 — STOP vs RETRY vs RESUME vs ESCALATE (decision rule)
RETRY (bounded) : ONLY transient classes (40001/40P01/55P03/57014/53300/
53400/08xxx). Whole-txn replay; idempotency keys guarantee
convergence; bounded attempts; backoff+jitter.
RESUME / DEDUP : 23505 on a recognised idempotency/natural key → return the
existing row; phase succeeds without a new write.
STOP (no retry) : 42501, 23503, 23502, 23514, 22xxx, 28xxx, ConfigMissing,
unknown SQLSTATE, any guard-precondition failure. ROLLBACK,
surface, do not loop.
ESCALATE : retries exhausted, or a STOP that represents stuck work →
write an escalation decision_backlog_entry (kind=
'escalation', status='marked') + its history so a human/
GPT loop owns it. The agent NEVER silently drops work.
NEEDS_HUMAN : semantic verify mismatch + every ESCALATE → flagged for
the human/GPT loop; no autonomous "fix and continue".
§3 — Rollback Semantics (two distinct layers)
DB-LEVEL (within a phase):
- any exception inside transaction() → adapter issues ROLLBACK; the phase
leaves ZERO trace (P-3/P-7; mirrors skeleton transaction() except-path).
- connection loss mid-txn → server aborts the txn; client treats it as a
clean ROLLBACK (no partial commit is physically possible — single
BEGIN…COMMIT, no intermediate visibility).
- the adapter NEVER issues DELETE/TRUNCATE/DDL to "clean up" — it has no
such surface (AppendOnlyViolation, skeleton-enforced).
SEMANTIC-LEVEL (a committed CUT later judged wrong by VERIFY):
- NO physical undo. A FORWARD compensating cut_change_set (+ affected_row)
is INSERTed; verify_result.rollback_change_set_id_triggered points at it;
an escalation entry is created; status → verify_failed_escalated.
- rationale: cutter_governance is an append-only ledger — truth is "what
happened", corrected by further recorded events, never erased.
EMERGENCY (operational, NOT an adapter action):
- credential kill switch is the EXISTING rollback artefact from the
credential cycle (ALTER ROLE … NOLOGIN, sha fcba5629…2b14) — an
out-of-band ops action, NOT something the adapter performs. The adapter
has no role-altering capability.
no_partial_commit_guarantee: because each phase is exactly one txn (txn
doc TX-1/TX-4), a retry always restarts from a consistent committed
boundary; there is never a "half phase" to reconcile.
§4 — Escalation Path (no silent drop)
trigger: retries exhausted | STOP-class on stuck work | semantic verify fail.
action: INSERT a decision_backlog_entry(kind='escalation', status='marked')
+ its ∅→marked history (under the principal that owns that phase per
routing doc — VERIFY's fail-path escalation is cutter_verify, granted).
The original entry's status moves to its *_failed / verify_failed_escalated
state with a history row recording reason + the SQLSTATE class (NOT the
secret, NOT raw credential text).
ownership: the escalation entry re-enters the backlog and is swept like any
work item; a human/GPT loop decides the next move. The agent does not
self-resolve an escalation.
signal: a typed local/test signal (skeleton OD-4 channel) is emitted for
PRIVILEGE / CREDENTIAL / unknown-SQLSTATE / exhausted-retry so operators
see it promptly. Signal payload carries class + entry_id, never secrets.
§5 — No-Leak Discipline In Error Paths (hard)
- exception messages, log lines, signal payloads, escalation reason text and
history rows reference: SQLSTATE, error CLASS, table/phase, entry_id, and
env KEY NAMES — NEVER a password value, NEVER a full DSN, NEVER connection
string bytes.
- the adapter's repr()/str() and any captured stack frame must not render
the password (doc 2 L-4); connect kwargs are passed discretely, not as a
URL, so no single loggable secret token exists.
- a 28xxx credential error logs "auth failed for principal cutter_exec
(key DOT_CUTTER_EXEC_DB_PASSWORD)" — name only.
§6 — Open Decisions (this doc)
DA-13 retry bounds defaults: max_attempts + backoff/jitter values (config,
not hardcoded) — propose 5 attempts, base 200ms, cap 5s, full jitter.
DA-14 53300 backpressure: retry-with-long-backoff (recommended) vs immediate
escalate — recommend bounded retry then escalate.
DA-15 escalation entry under VERIFY fail uses cutter_verify (granted) —
confirm acceptable that the verifier creates the escalation entry (it
CANNOT author its review/manifest, SoD intact — principal-routing SoD-3).
DA-16 unknown-SQLSTATE policy = STOP+escalate (recommended) vs one bounded
retry then STOP.
End of DB adapter error & rollback design (design only; no error raised; no rollback run; no connection).