What uncommitted database or filesystem states must be rolled back before exit?
**Date of Record:** 2026 October 25 **Scope:** Applies when an autonomous instance, transactional tool runner, or process-termination handler prepares to exit a session—whether via normal completion, exception abort, or timeout—after staging mutations across local filesystems or database connections. Does not apply to immutable read-only inspection sessions or external append-only audit logs where partial event emission is intentionally preserved for forensic tracing.
Core Distinction
Process termination does not automatically restore transactional atomicity across heterogeneous storage layers. A critical architectural failure occurs when an instance exits while leaving behind uncommitted database transactions, half-written staging files, or orphaned advisory lockfiles, polluting the environment with torn state that deadlocks subsequent runners or gets falsely ingested as committed truth.
What Was Observed
In multi-step execution environments combining relational/embedded databases (such as SQLite or PostgreSQL) with direct filesystem mutations, exiting without an explicit pre-exit rollback sweep produces four distinct corruption modes:
- **Orphaned Write-Ahead & Transaction Locks:** Exiting with an open `BEGIN IMMEDIATE` or uncommitted cursor leaves shared-memory lock frames or idle-in-transaction server states active until OS/connection timeouts expire, blocking immediate follow-up processes.
- **Torn Staging & `.tmp` Artifact Bleed:** An interrupted file-generation sequence leaves partial `.part` or `.tmp` files in target directories; naive downstream glob scanners (`*.json`, `*.md`) ingest the truncated fragments as valid records.
- **Cross-Store Split-Brain State:** A database row is inserted in an uncommitted transaction while its corresponding blob is written directly to disk; if the process aborts and the DB rolls back automatically on socket close while the filesystem blob remains, storage parity fractures permanently.
- **Stale PID & Advisory Lockfile Persistence:** Failing to unlink application-level lockfiles (`.lock`, `.pid`) inside a deterministic `finally` teardown barrier prevents legitimate successor instances from acquiring execution authority.
Recorded Elements
- **Active Transaction Ledger:** Tracking every opened database connection handle that has entered a mutating transaction state without an explicit `COMMIT` confirmation.
- **Ephemeral Staging Manifest:** Recording the absolute paths of all temporary files, unrenamed atomic-write buffers, and newly created directories pending final commit promotion.
- **Advisory Lock Registry:** Maintaining the exact set of filesystem lock paths and file descriptors acquired exclusively by the current process ID.
Condition of Validity
* **Applies when:** Executing teardown, exception unwinding, signal handling (`SIGINT`/`SIGTERM`), or session-exit barriers after any mutating database or filesystem operation has been initiated. * **Does not apply when:** Operating inside an ephemeral container sandbox scheduled for immediate block-level destruction upon process exit.
Decision Boundary
- **Are any database connections holding uncommitted transactions at the exit boundary?** Execute an explicit `ROLLBACK` on every unfinalized connection before closing handles; never rely on implicit socket closure to clean up transactional locks promptly.
- **Do any unpromoted staging files (`.tmp`, `.staging`) or unreferenced side-effect blobs exist on disk?** Unlink every unpromoted path recorded in the session staging manifest before returning control.
- **Did any step of a multi-store atomic unit (DB + filesystem) fail prior to the final commit barrier?** Roll back the database transaction and purge all newly written filesystem artifacts from that unit, raising a fail-closed teardown exception if any artifact resists removal.
Operational Effect
- **Strict All-or-Nothing Exit Atomicity:** Every session either promotes its complete, verified state across both database and filesystem layers or leaves zero residual mutations behind.
- **Zero Lock Starvation Across Restarts:** Immediate successor instances encounter clean lock tables and uncorrupted directory trees upon launch.
from dataclasses import dataclass, field
from typing import Final, FrozenSet, Tuple
import os
from pathlib import Path
@dataclass(frozen=True)
class SessionExitState:
session_id: str
committed: bool
uncommitted_db_transactions: Tuple[str, ...]
staged_temp_files: Tuple[str, ...]
acquired_lockfiles: Tuple[str, ...]
class UncleanExitStateViolation(Exception):
"""Raised when uncommitted database transactions, temp files, or lockfiles remain at process exit."""
pass
class PreExitRollbackGate:
def __init__(self, allowed_staging_root: str = "/var/www/corpusfetch") -> None:
self.allowed_staging_root: Final[str] = allowed_staging_root
def enforce_clean_exit(
self,
state: SessionExitState,
rolled_back_dbs: FrozenSet[str],
removed_paths: FrozenSet[str],
) -> bool:
if not state.committed:
# 1. Verify all uncommitted DB transactions were explicitly rolled back
for tx_id in state.uncommitted_db_transactions:
if tx_id not in rolled_back_dbs:
raise UncleanExitStateViolation(
f"Session {state.session_id} exiting with un-rolled-back DB transaction: {tx_id}"
)
# 2. Verify all unpromoted staging files were unlinked
for temp_path in state.staged_temp_files:
if not temp_path.startswith(self.allowed_staging_root):
raise UncleanExitStateViolation(
f"Staging path '{temp_path}' escapes allowed root '{self.allowed_staging_root}'."
)
if temp_path not in removed_paths or Path(temp_path).exists():
raise UncleanExitStateViolation(
f"Session {state.session_id} left unlinked staging file on disk: {temp_path}"
)
# 3. Lockfiles must ALWAYS be released prior to exit, whether committed or aborted
for lock_path in state.acquired_lockfiles:
if lock_path not in removed_paths or os.path.exists(lock_path):
raise UncleanExitStateViolation(
f"Session {state.session_id} failed to release advisory lockfile before exit: {lock_path}"
)
return True