Run report schema¶
SyncReport.to_dict() projects a whole run as plain, JSON-serialisable data —
dict, list, str, int, bool, and None only, so
json.dumps(report.to_dict()) works directly. It is the machine-readable view
of a run, for CI gates, run-history persistence, and structured logging. The
human-readable views are report.render() and report.render_diff(). The
equivalent render_report(report) and render_diff(report) functions remain
available for callers that prefer function-style composition.
TableRun.to_dict() projects a single table’s record, the same object
that appears in the run-level tables list.
Stability¶
The fields and enumerated values below are a public contract, versioned by
schema_version. Adding a field is backwards-compatible; renaming or removing
one, or renaming an enumerated value, is a breaking change and increments
schema_version. The projection is deterministic: tables appear in run order,
and changes and statements in action-plan order, so two projections of the same
report compare equal — successive dry-run outputs can be diffed.
Version 2 renamed the planning-phase status from VALIDATION_FAILED to
PLANNING_FAILED and the corresponding failure phase from VALIDATION to
PLANNING.
rejected_changes was added without a version bump: adding a field is
backwards-compatible, and a reader that does not know the key sees exactly the
payload it saw before.
The 2026-08 Python renames (PlanningAccepted/PlanningRejected,
TableCreation, ExecutionResult) changed no serialized field or value;
schema_version remains 2.
The per-type failure fields (2026-08-05) were likewise added without a bump:
every failure record still carries phase, type, and message with
unchanged meaning, and the additional keys sit beside them.
The DEFERRED status value (2026-08-26) was added without a bump: a table
whose declaration cannot create it (any scope narrower than full) now
reports DEFERRED instead of PLANNING_FAILED when the table does not
exist. Every previously possible payload is unchanged; readers that switch on
status see the new value only in runs that previously failed.
Consistency¶
SyncReport rejects combinations that a completed engine run cannot produce.
A dry run cannot contain execution results. A real run with a non-empty plan
must either contain its execution result or a failure explaining why execution
did not run. Empty plans and plans rejected before compilation require no
execution result.
These checks apply when constructing a report directly as well as when the
engine assembles one. ExecutionResult separately validates the statement
history it records.
Table change states¶
For Python callers, SyncReport.table_change_states returns one
TableChangeState per table run, in the same order as
SyncReport.table_runs. The aggregate owns this view because the distinction
between a planned dry-run change and an unapplied real-run change depends on
the run’s dry_run mode, not on the table report alone.
Member |
Value |
Meaning |
|---|---|---|
|
|
Reading or planning failed before a plan was accepted |
|
|
The table does not exist and the declaration cannot create it — skipped with a warning until something else creates it |
|
|
The accepted plan contained no catalog changes |
|
|
A dry run compiled a non-empty plan without executing it |
|
|
A real-run change was blocked, or its first statement failed |
|
|
Some statements succeeded before a later statement failed |
|
|
Every statement in a non-empty real-run plan succeeded |
Change state is deliberately separate from TableRunStatus: status explains
which phase failed, while change state describes the effect on the catalog. A
table can therefore be EXECUTION_FAILED with either NOT_APPLIED or
PARTIALLY_APPLIED, and an unchanged table can still carry a foreign-key
failure. Import the enum with from delta_engine import TableChangeState.
The human-readable views use these states on real runs. report.render_diff()
marks non-empty plans that were not applied or only partially applied, while
the report.render() footer counts catalog outcomes. A compiled plan blocked
before execution shows statement progress as 0/n. Dry-run diff blocks and
their changed/unchanged/deferred/failed footer keep describing planned work
instead.
This Python-level derived view does not add fields to SyncReport.to_dict() or
TableRun.to_dict(); the structured schema below remains version 2.
Run-level fields¶
SyncReport.to_dict() returns:
Field |
Type |
Meaning |
|---|---|---|
|
|
Version of this payload schema; currently |
|
|
ISO 8601 timestamp when the run began |
|
|
ISO 8601 timestamp when the run ended |
|
|
Whether execution was skipped |
|
|
True if any table has a planned change |
|
|
True if any table failed a phase |
|
|
Per-table records, in run order (see below) |
Table-level fields¶
Each entry in tables, and the whole of TableRun.to_dict():
Field |
Type |
Meaning |
|---|---|---|
|
|
Dotted, unquoted qualified name, e.g. |
|
|
A |
|
|
True if this table has a planned change |
|
|
True if this table failed a phase |
|
|
Summaries of the planned changes, in plan order (see below) |
|
|
Differences the rejected plan was built from; empty when the plan was accepted |
|
|
Full compiled DDL the plan lowers to, in order |
|
|
Failure records, in phase order (see below) |
|
|
Execution counts, or |
Change records¶
Each entry in changes summarises part of a planned change, derived from the
same interpretation the text renderers use. They are human-oriented summaries,
not one record per plan action (a table creation expands into several), and
not a complete description of the change — the authoritative, complete
description is planned_sql_statements:
Field |
Type |
Meaning |
|---|---|---|
|
|
Change category: |
|
|
|
|
|
What the change targets: the name of a column, property, tag, or table feature; |
|
|
How it changed, e.g. |
Rejected change records¶
When a table’s diff is rejected, no plan exists, so changes is empty. The
differences the engine did find are projected into rejected_changes in the
same record shape, so a reader can see what was rejected alongside the
failures list that says why. It includes both the actions the engine would
have taken and the differences no action can close (a column spelled
differently from the catalog, a declared rename whose old and new names both
exist, a property set but undeclared, a partitioning change). It is always
empty for a table that planned successfully.
Failure records¶
Each entry in failures:
Failures follow lifecycle order: structural foreign-key resolution, read,
planning, then execution. A BLOCKED_BY_FAILED_DEPENDENCY foreign-key failure
appears where execution would have occurred because it records why the table
was skipped.
Field |
Type |
Meaning |
|---|---|---|
|
|
The phase that produced it: |
|
|
The concrete failure class name, e.g. |
|
|
The rendered failure message |
Records are not all-string: the per-type fields below include an integer
(statement_index) and lists (details, columns).
Additional keys by type¶
ReadFailure:
Field |
Type |
Meaning |
|---|---|---|
|
|
The backend exception class name |
|
|
The complete backend message; |
ValidationFailure:
Field |
Type |
Meaning |
|---|---|---|
|
|
The rule that rejected the diff, e.g. |
|
|
The column, property, or aspect judged; empty for whole-table rules |
|
|
One line per difference behind a summary judgment |
ExecutionFailure:
Field |
Type |
Meaning |
|---|---|---|
|
|
The backend exception class name |
|
|
The complete backend message; |
|
|
0-based index into the table’s |
|
|
The statement that failed |
ForeignKeyFailure:
Field |
Type |
Meaning |
|---|---|---|
|
|
Machine code for why, e.g. |
|
|
The declaring table’s foreign-key columns |
|
|
Dotted name of the referenced table |
Execution record¶
When a table executed, execution is:
Field |
Type |
Meaning |
|---|---|---|
|
|
Statements that ran successfully |
|
|
Statements planned ( |
It is None for a dry run and for any table skipped by an earlier-phase
failure or blocked by a failed foreign-key dependency. The engine executes statement by statement and stops at the first
failure, so applied < total means the trailing statements were never
attempted.
The planned SQL property¶
Alongside the projection, SyncReport.planned_sql_statements is a
dict[str, tuple[str, ...]] mapping each table’s dotted name to its compiled
statements, omitting tables with no planned change. It is the same statement
text that appears per table under planned_sql_statements in to_dict().
Planned is not executed: a table blocked after planning (for example by a
foreign-key dependency failure) still reports the SQL its plan compiles to.
Whether the statements ran is answered by execution and has_failures.
See how to gate schema changes in CI for the projection in use.