Observability
Execution history
Store and query lifecycle events and run history from Python, with SQLite locally or PostgreSQL when deployed.
LifecycleEventLogStorage stores canonical immutable lifecycle facts. RunStorage serves an atomically
published, rebuildable run projection. Diagnostic and compute logs are never accepted by either
contract. The public types, filters, pages, records, errors, and helper functions are exported from
sqlbuild.execution_history.
Public contracts
Section titled “Public contracts”LifecycleEventLogStorage provides append_event, append_events, get_events, get_schema_version,
upgrade_schema, close, dispose, and context management. RunStorage provides get_run,
get_runs, project, rebuild_from_events, the schema/lifecycle methods, and context management.
The concrete SQLite and PostgreSQL classes additionally provide transactional
append_and_project, reconcile, and check_health operations.
The deprecated EventLogStorage alias has been removed; use LifecycleEventLogStorage. Storage
tables use the lifecycle-specific names lifecycle_event_log in SQLite and
sqlbuild_lifecycle_event_log in PostgreSQL; the former event_log and sqlbuild_event_log names
are retired.
Each newly stored event receives a positive, increasing storage_order, opaque stable cursor,
and UTC received_at. Pages ascend in storage order. after_cursor is exclusive,
next_cursor identifies the last returned record and is None for an empty page, and has_more
states whether another page exists. Limits default to 100 and cannot exceed 1000. Cursors are
backend- and storage-instance-owned; persist them verbatim and never parse them. They identify a
global position and remain valid if a later call changes filters.
EventFilter supports invocation ID, run ID, event types, event family, producer, and inclusive
event-time bounds. Event types and family are mutually exclusive. EventFamily contains
invocation, run, resource-attempt, operation, and statement; select retry facts with
EventFilter(event_types=("retry_scheduled",)). RunFilter supports invocation, status, and
inclusive projection-creation bounds. A run cursor identifies the global
(created_at, run_id) order and is also filter-independent.
Projection applies durable facts in storage_order, never occurred_at order. created_at is the
first run fact’s received_at; start/end times retain event time. A run with no terminal is
RunStatus.UNKNOWN and is_complete=False. The last durable terminal wins if conflicting terminal
facts exist. Incremental projection and a full rebuild produce the same result, and one projection
call publishes all changes or none.
Local SQLite
Section titled “Local SQLite”The CLI does not open local history implicitly. Construct and subscribe this backend only when an application has a concrete durable-history consumer.
from pathlib import Path
from sqlbuild.execution_history import EventFilter, RunFilterfrom sqlbuild.sqlite_history import SQLiteExecutionHistory
with SQLiteExecutionHistory(project_dir=Path(".")) as history: assert history.check_health()
cursor = None while True: page = history.get_events( event_filter=EventFilter(run_id="RUN_ID"), after_cursor=cursor, ) for stored in page.records: print(stored.storage_order, stored.event) if not page.has_more: break cursor = page.next_cursor
for run in history.get_runs(run_filter=RunFilter()).records: print(run.run_id, run.status, run.is_complete)With no arguments, SQLiteExecutionHistory() uses the current directory. Passing project_dir
uses <project_dir>/.sqlbuild/history.sqlite3; passing path selects an exact file, and path may
be ":memory:". Do not pass both. Startup creates/migrates schema version 1 and reconciles the run
projection from all event facts.
Schema v1 has execution_history_metadata, append-only lifecycle_event_log, and disposable
run_projection tables plus run, invocation, type, and created-time indexes. SQLite uses WAL mode,
foreign keys, a configurable 5000 ms default busy timeout, a process-local reentrant lock, and
transactions for append/projection publication. Multiple processes may use SQLite subject to normal
SQLite/WAL host-filesystem constraints and busy timeout; it is a host-local backend, not a deployed
coordination service. Startup refuses unknown future schema versions and never resets or downgrades
data.
Deployed PostgreSQL
Section titled “Deployed PostgreSQL”Install sqlbuild[postgres] only in deployed processes and construct the backend explicitly. The
CLI never selects PostgreSQL implicitly. Neither backend is selected by ordinary CLI commands.
import osfrom datetime import UTC, datetime
from sqlbuild.execution_history import EventFilterfrom sqlbuild.observability import create_lifecycle_event, invocation_scope, run_scopefrom sqlbuild.postgres_history import PostgresExecutionHistory
occurred_at = datetime(2026, 9, 2, 10, 15, tzinfo=UTC)with invocation_scope("deployment-20260902-1015"): with run_scope("run-20260902-1015"): run_started = create_lifecycle_event( event_type="run_started", event_id="run-20260902-1015-started", occurred_at=occurred_at, payload={"run_kind": "build", "selected_count": 12}, )
with PostgresExecutionHistory(os.environ["SQLBUILD_HISTORY_DSN"]) as history: first = history.append_and_project((run_started,))[0] retry = history.append_event(run_started) assert retry.storage_order == first.storage_order
page = history.get_events( event_filter=EventFilter(run_id="run-20260902-1015"), after_cursor=None, ) checkpoint = page.next_cursor
# Persist checkpoint securely. A later process resumes after that exact global position.with PostgresExecutionHistory(os.environ["SQLBUILD_HISTORY_DSN"]) as history: later = history.get_events( event_filter=EventFilter(run_id="run-20260902-1015"), after_cursor=checkpoint, )The DSN must be resolved from a secret before construction. SQLBuild neither retains it for
diagnostics nor includes it in exceptions or repr. PostgreSQL schema v1 uses
sqlbuild_storage_migrations, authoritative sqlbuild_lifecycle_event_log, and disposable
sqlbuild_run_projection. Startup takes a transaction-scoped advisory migration lock, performs
forward-only migrations, and reconciles projection. All instances should run the same SQLBuild
version during rollout.
Append and projection serialize under a history advisory lock. Only serialization failures and
deadlocks (40001, 40P01) are retried internally, up to three retries by default. If connection
loss makes commit acknowledgement uncertain, discard the backend, reconnect, and retry the exact
same deterministic event ID and content. Equal content returns the existing stored fact; different
content raises IntegrityConflictError. This is storage idempotency, not exporter exactly-once
delivery.
Grant deployed application roles only the required schema/table privileges and reserve migration,
backup, retention, and destructive privileges for operators. Back up sqlbuild_lifecycle_event_log and
sqlbuild_storage_migrations, preserving event IDs, canonical JSON, identity sequence values, and
storage namespace. sqlbuild_run_projection can be rebuilt with reconcile(). PostgreSQL performs
no automatic retention, partitioning, or archival.
These are selected operational examples rather than an exhaustive API reference. The public
sqlbuild.execution_history facade also exports canonical content/ID helpers, page-limit
validation, projection helpers, schema and paging constants, storage protocols, models, filters,
statuses, and typed storage/cursor/schema errors.
See Observability for the lifecycle event envelope, correlation, and failure behavior.