retrofit0.10.0-wip.8

snapshot

retrofit snapshot emits canonical DDL for the materialized schema: the SQL equivalent of replaying your oplog forward to a cut. The output is intended to be pg_dump --schema-only parity-shaped (see pg_dump-parity.md for the option-by-option audit).

Read concepts.md first for the oplog / cut / replay vocabulary. This doc covers what snapshot sees, what it produces, what it cannot produce, and why.

What snapshot sees

One genealogy cut: the state of the schema after replaying ops up to --at <opid> (default: last). --at also accepts the canonical anchors root / last / seal, each with an optional ^ / ~N decrement (always older). --at root refuses cleanly (opid-not-found, exit 2): the empty pre-genesis cut has no live elements to render.

An unreconciled fork anywhere in (genesis, --at] refuses with fork-in-range (exit 1), the same envelope migrate uses, hinting retrofit reconcile <shared-prev>. On a fork there is no single cut to render: the default --at last resolves to the line-order tail rather than a leaf, so emitting would ship whichever branch the merge happened to write last, which is silently wrong for non-commutative history such as two concurrent renames of the same table. The check covers only the range: an explicit --at <opid> at or before the fork’s shared prev renders normally. It runs before the data band, so a labeled run refuses the same way and emits no DDL and no data artifact. Under --as=json the error envelope still goes to stdout; no partial artifact does.

The replay walks the oplog and materializes:

  • the set of live elements (tables, columns, constraints, indexes, views) with their current names, types, and adjacencies;
  • the dependency order needed to emit valid DDL.

snapshot is read-only: it never modifies the oplog. Output goes to stdout. Schema selection follows the six-step resolver chain.

retrofit snapshot                                  # last cut, resolver picks schema
retrofit snapshot --schema sales                   # explicit schema
retrofit snapshot --at 988edcb612ab                # cut at a specific opid
retrofit snapshot --include-drop=scoped            # per-element DROP ... IF EXISTS prologue
retrofit snapshot --include-drop=schema            # one DROP SCHEMA IF EXISTS CASCADE
retrofit snapshot --table=users --table=orders     # pg_dump -t parity
retrofit snapshot --exclude-table=audit_log        # pg_dump -T parity
retrofit snapshot --exclude-schema=staging         # pg_dump -N parity
retrofit snapshot --using=postgres:18              # one-shot emit-target override
retrofit snapshot --no-header                      # suppress banner
retrofit snapshot --no-comments                    # suppress COMMENT ON
retrofit snapshot --label env:dev                  # embed the env:dev dataset between tables and constraints
retrofit snapshot --label base --label env:dev     # layer two datasets, in that order
retrofit snapshot --data-only --over env:dev       # the data band alone, projected onto the top layer
retrofit snapshot --skip-manifest skips.jsonl       # record render-skipped elements (JSONL)

--include-drop has three modes:

  • none (default): pure CREATE. Pair with external dropdb / createdb if you need a clean reflash.
  • scoped: per-element DROP … IF EXISTS for tables / indexes / FKs in the emission set, then CREATE. Sibling objects in the same schema survive. Refuses inside a BEGIN/COMMIT if any table outside the set holds an inbound FK into the set; switch to schema in that case.
  • schema: one DROP SCHEMA IF EXISTS <name> CASCADE per schema, then CREATE. CASCADE blast radius covers every dependent outside the emission set; use only when you own the whole schema.

--include-drop=schema is incompatible with --table / --exclude-table (flag-conflict): schema-level CASCADE would remove the very tables --exclude-table claims to keep.

What snapshot produces

A canonical DDL document on stdout. Structure:

  1. Banner, one comment line (unless --no-header):

    -- retrofit snapshot v1 opid=<12-hex> engine=postgres engine_version=<major> include-drop=<mode>

    The banner does not carry the schema name. Its opid is a content hash of the emitted body, not the cut’s tail opid. Two imports converging on the same schema emit the same banner, which is what makes import → snapshot → re-import idempotent.

  2. Optional DROP prologue, per --include-drop mode.

  3. CREATE EXTENSION statements first (some auto-create schemas).

  4. CREATE TABLE statements in dependency order.

  5. ALTER TABLE statements for constraints, comments, replica identity and defaults: the Set* family pg_dump round-trips.

  6. CREATE FUNCTION / VIEW / MATERIALIZED VIEW statements in dependency order.

  7. CREATE INDEX statements after the tables they belong to.

  8. CREATE TRIGGER / POLICY statements and RLS toggles.

  9. COMMENT ON statements (unless --no-comments).

  10. Data band, one block of embedded row data, opening with a -- retrofit:snapshot-data v1 header. It does not follow item 9: text mode splices it in where pg_dump puts its data section, after the CREATE TABLE statements (and any function or view a table needs) and before the constraints, indexes, other views and comments, so the rows land while the tables are bare. It carries every selected record regardless of designation: ref rows from the base corpus, which apply with no flags, plus purpose=seed rows once a --label names their dataset. The header names the band, not a designation: a band containing only ref rows carries the same header, so a label is not the condition for the band’s existence, only for seed-row inclusion. The band appears whenever at least one record was selected and is omitted entirely otherwise, and opens with its own schema prologue on a non-public target (see § Row data below).

JSON mode carries the document as {"type":"snapshot","ddl":"<full doc>"}, with {"type":"ok"} terminal. The ddl field is the DDL document only: when a data band is selected, text mode splices the band into the printed document while JSON mode carries it on its own record (below), so the two modes agree on the DDL portion, not on the whole byte stream. Other records ride the same stdout stream around the snapshot record; this is an inventory, not a closed set:

  • {"type":"snapshot-data","band":"<data band text>"}: emitted before the snapshot record whenever at least one record was selected, and omitted entirely when none was. It is not label-gated: a ref-only corpus emits it with no flags, so a consumer keying on a label would drop reference rows.
  • {"type":"data-excluded","purpose":"<purpose>","table":"<name>", "count":N}: one per (purpose, table) pair that was counted out of the data band; see § Row data. These are emitted after the snapshot record (the artifact is written first so a text-mode redirect keeps stdout clean), so snapshot is not the last record before ok.
  • The same record carries two additional members, "cause" and "schema", only on an ownership exclusion: a corpus file under a shared data_root root whose header names a schema this run does not own. "cause" is other-schema when the named schema is registered and unregistered-schema when no registered schema owns that namespace; "schema" is the owning namespace. Owned-record rows carry neither member, so existing purpose/table/count consumers are untouched. A "count" of 0 is meaningful: it reports a foreign file that declares a table empty and therefore contributed no rows.

The records print in the order snapshot-data?, snapshot, data-excluded*, ok; dispatch on type and skip types you do not recognize.

Render skips do not ride this stdout stream. They are stderr advisories in both modes, and under --as=json they take the gateway’s stderr advisory shape ({"level":"warning","code": "view-render-skipped"|"element-render-skipped","message":...}), keyed on level/code, not type/kind. A consumer reading only stdout will not see them; see § Render failures for the machine-readable companion.

snapshot is deterministic: same oplog + same cut + same flags = byte-identical output. This is load-bearing for CI, parity checks, and the harness’s roundtrip-* scenarios.

SELECT * views are emitted expanded

A view defined with a star projection comes out with the star replaced by an explicit column list, in the source table’s ordinal order:

-- authored
CREATE VIEW app.vv AS SELECT * FROM app.t;

-- snapshotted
CREATE VIEW app.vv AS SELECT id, a, b FROM app.t;

This matches PostgreSQL, which expands and freezes a view’s output column list when the view is defined; pg_dump shows the expanded list for the same reason. A qualified t.* expands the same way, and a star over a join expands source by source in FROM order.

The column list is frozen when the view is defined, and a later ADD COLUMN on the source table never joins it. Adding app.t.c after the view exists leaves app.vv projecting id, a, b in every later snapshot; redefine the view to include it.

Not every star can be expanded. When it can’t, the view is skipped rather than emitted with a bare *; see § A SELECT * view snapshot cannot expand.

Type references are schema-qualified

Inside a rendered body (a view query, a function body, a CHECK expression, a column default), a reference to a type you manage in this oplog is emitted fully qualified and quoted, as ::"schema"."typename". Built-in and extension types (text, int4, jsonb) stay bare.

This is what lets a snapshot apply to a fresh database without the caller setting a search_path first: every reference that could otherwise resolve against the wrong schema names its schema outright. The qualified spelling is part of the output contract and is byte-stable across runs.

What snapshot is

snapshot is genealogy-driven: there is no per-operator allow-list. If the op can be replayed, snapshot renders its post-state. Reachability by the replay is the admission test, not membership in a hand-maintained set.

Reachable is not the same as rendered, though: § Render failures names the two ways an element can still fail to come out.

This is why nearly every operator renders on snapshot. The exceptions:

  • Seal: N/A:log-level. Seal pins log integrity; it doesn’t change the rendered schema. snapshot emits no DDL for it.
  • Drop* and Rename*: these don’t have a standalone renderer; they contribute by changing the visible element set the replay produced (one fewer element, or a different name). The snapshot emitter sees their effect via the cut.

What snapshot cannot produce, and why

The cut is the input. What the cut doesn’t reach, snapshot can’t emit. Three situations to know about:

Operators outside the catalog

Statements outside the oplog, whether import skipped them or the catalog has no operator for them, are not in the cut, and snapshot emits nothing for them.

Functions, triggers, policies, extensions and materialized views are in the catalog and snapshot emits them. Standalone sequences, privileges and cross-schema objects are not: import lists them on its skip manifest, and the deployment pipeline applies them.

PG18-gated emissions

At an 18 emit target (--using=postgres:18, or a schema whose engine_version is 18), snapshot renders SetConstraintEnforced value=false as NOT ENFORCED on the foreign key, emits nothing for SetConstraintInherit, and writes a VIRTUAL generated column as STORED. Below 18, a cut holding any of them fails with unsupported-on-engine-version. PostgreSQL versions gives every verb’s row at each major.

A SELECT * view snapshot cannot expand

A view whose top-level projection is SELECT * is normally expanded to an explicit column list (see § SELECT * views are emitted expanded, above). When that expansion cannot be performed soundly, snapshot emits no CREATE VIEW for it. The run warns view-render-skipped (see § Render failures) and exits 0; the view is absent from the document. Expansion fails when:

  • the star’s FROM is a derived relation (a subquery or a CTE) that carries no fixed base-column list to expand from;
  • the FROM names a relation that is not a live table in the cut. Expansion anchors only on base tables, so this also covers a star over another view: the referenced view has a frozen column list of its own, but that list lives in the referenced view’s definition rather than in a table the cut can read columns from;
  • the star sits over a coalescing join (NATURAL JOIN, or JOIN … USING), where the output columns are merged rather than concatenated;
  • a source column was renamed somewhere in the cut’s history, so a re-expanded star would bind to the new name where the original definition froze the old one.

The remedy is to give the view an explicit output column list. A CREATE VIEW v (a, b, c) AS … names the final output columns in the DDL itself, which suppresses the skip entirely: those names are frozen in the statement and cannot drift. Rewriting the body to name its columns instead of * works equally well.

Emit-time filters

--table / --exclude-table / --exclude-schema mirror pg_dump’s -t / -T / -N flags. Names match the registered name exactly; there is no glob matching (see pg_dump-parity.md). An unknown name refuses with table-not-in-cut or schema-not-in-cut.

Interactions:

  • When --schema and --exclude-schema name the same schema, the body is empty and the banner remains.
  • --include-drop=scoped + --table emits per-element DROPs only for the included tables.
  • FK reachability is checked against the filtered set: an FK from an included table to an excluded table refuses with fk-cross-schema-unreachable. Include the target or drop the FK.

Row data: ref always, --label for the rest

snapshot emits schema-only DDL, with two row-data exceptions. Both draw on the data corpus: every addressed *.data.<format> data file under the schema’s declared data_root (currently .data.sql, plus readable legacy *.dml.sql), discovered automatically; no file argument is needed.

  • ref rows apply from the base corpus. Records classified ref (production reference data) belong to the current state, so a base-corpus ref record is embedded with no flag and is never label-filtered.
  • seed rows are opt-in, and the opt-in is the dataset’s name. --label <label> (repeatable, in layer order) embeds the purpose=seed records carrying that exact label in the data band, between the tables and the constraints and indexes, under a -- retrofit:snapshot-data v1 header, so a snapshot can double as a reproducible schema-plus-reference-data fixture. Membership is byte-exact: the selector is not trimmed, and a record with no labels matches no selector. Labels are stamped at import time with --label.

Records carrying any other purpose are excluded, because a snapshot is current state, not a transition log. Every exclusion is counted (data-excluded, exit stays 0); a silent run means the corpus held nothing to drop. This covers unclassified records; seed rows no --label named (not-selected); rows a higher layer’s replace floor superseded; and transition and cleanse records. The history-bound purposes are counted but never applied: retrofit migrate applies them at their anchor op, so their absence from a snapshot is by design: the count tells you they existed, not that something went wrong.

A dataset can be left unnamed on purpose: a test-only label such as fixtures on a snapshot of a long-lived database. Its run-level apply-risk-data-exclusions warning offers two recourses: --label to name the dataset, or --allow-unnamed-label <label> to leave it out. That flag attests that the dataset carrying the exact label was left unnamed on purpose. It is repeatable and byte-exact, and there is no form that covers every dataset, so a dataset added later still warns. The warning drops the attested labels and fires while any other unnamed dataset remains. Under --as=json the data-excluded record carries them as unnamed_attested. Every other cause still reports, and -v still lists the attested tables. An attestation that answers nothing (the corpus has no dataset with that label, or the run also named it with --label) prints a note that it had no effect.

In text mode each stderr advisory names the tables and per-table counts, and carries a hint naming the recourse. Advisories are split by why the records were dropped, so one purpose can produce more than one: a dataset no --label named and a dataset a higher layer superseded are separate lines, because the remedy differs (the second set was selected; nothing was missing). Under --as=json the counts ride the stdout stream as data-excluded records. An owned-record exclusion is keyed by purpose and table, one record per pair with its causes summed. An ownership exclusion (a record under a shared data_root root whose file header names another schema) is keyed by purpose, table, "cause" and "schema" instead, so two foreign schemas claiming one table are two records rather than one sum. Owned records emit first; the ownership records follow, sorted on (table, cause, schema).

The data band opens with a schema prologue when the resolved target schema is anything other than public:

CREATE SCHEMA IF NOT EXISTS "<schema>";

It precedes the row bodies inside the -- retrofit:snapshot-data v1 block. On a public (or default) target the prologue is empty and the band is byte-identical to a plain row block.

Every run of rows in the band opens under one designation line, -- Data: <target> (<designation>), so a reviewer can tell reference rows from seed rows by reading the nearest line above a statement:

-- retrofit:snapshot-data v1
-- Data: "shop"."statuses" (reference)
INSERT INTO "shop"."statuses" ("status_id", "name") VALUES (1, 'new');
-- Data: "shop"."fixtures" (seed, label qa:golden)
INSERT INTO "shop"."fixtures" ("id", "kind") VALUES (1, 'golden');

The target is the table the rows load, in the two-quoted-halves spelling the DDL header uses. The designation is reference, seed, label <label>, transition, cleanse or unclassified. One line opens each maximal run of consecutive groups for one table, purpose and layer; a table interleaved across layers gets one line per run. The line is an ordinary comment, never a -- retrofit: marker: --no-comments and --no-header leave it, and under --as=json it rides inside the snapshot-data record’s band text.

The band carries no SET search_path. The renderer resolves every bare relation reference in a data body when it emits it, naming the relation’s schema at the emission cut, so an emitted body does not depend on the applying session’s search path. The stored data file is unchanged by that: authors still write bare identifiers, and the file on disk keeps them.

The CREATE SCHEMA line survives, because qualification resolves an identifier but does not create the namespace that identifier names. It is dropped from the text document when the DDL immediately above the band already emitted it for that same schema. That is the usual case, and repeating it would make every snapshot --label … | psql print NOTICE: schema "…" already exists, skipping. It is retained whenever nothing above the band creates the schema: in the snapshot-data JSON record, which is contracted to be extractable on its own (applied without the DDL above it, a band missing its CREATE SCHEMA would fail with 42P01), and under --data-only, which emits no DDL byte at all on either its text or its JSON arm.

Row blocks are versioned by anchor position: within one (table, label-set) chain, the latest disposition=replace block at-or-before the render cut supersedes earlier state, so snapshot --at renders the row set as it stood at that cut.

Before a selected record is emitted, snapshot repairs its references as migrate does, in the emitted body only: a table or column rename resolves to its name at the cut, a write-only reference to a dropped column can be removed, and records for a dropped table are excluded. A reference that cannot be repaired without changing its meaning refuses as stale-dml-reference; --allow-stale-dml overrides it. The data file on disk is unchanged. See Stale data references and repair.

Datasets layer: each --label occurrence names one layer, in the order given, and a later layer’s disposition=replace floor supersedes the layers below it for that table. A file carrying several named labels folds in each of those layers and is emitted once, at the highest of them. External sources are no longer layered by flag: the corpus under the schema’s declared data_root is always read, so committing a file is what makes it available.

A full snapshot renders the band as plain row bodies: the DDL above it has just created every table empty, so no floor needs a prologue and none is emitted. The rows carry their own identity and serial values, so after a table’s last row the band re-seats each of its identity/serial sequences with an annotated SELECT … setval( pg_get_serial_sequence(…), …) guarded on max(<column>) IS NOT NULL, the alignment pg_dump’s data section performs, and the first insert that omits the key takes the next free value instead of colliding with row 1. Each re-seat is a query, so applying the script prints one single-row result per sequence under the column name setval, as pg_dump’s own setval line does; psql -q does not suppress query results, psql -o /dev/null discards them.

snapshot --data-only lands the band on an existing database instead. There a table whose resolved floor is a disposition=replace block carries a prologue, so the rows land as the table’s complete state, and the prologue renders exactly as it does on migrate: a keyed table (its primary key, a UNIQUE constraint or a unique index the captured rows write in full; a DEFERRABLE constraint never qualifies) gets a staged block that upserts through a TEMP table and never empties the target; a table with no such key gets a whole-table clear and a staged reload, or refuses as stale-dml-reference (reason key-uncovered) when another table’s foreign key references it; a unique index that shares its columns with a DEFERRABLE UNIQUE constraint refuses as replace-no-upsert-form; and a body=none floor renders a bare DELETE FROM <table>; clear. Clears render children before parents; an unreadable cut degrades the order to a deterministic tie-break behind a -- WARNING: comment. Every row body is followed by the same sequence re-seat, inside the staged block when there is one; see docs/migrate.md for the key order, the marker causes and each refusal’s recourse. Apply with psql -v ON_ERROR_STOP=1 -1 -f <script> under a role holding the TEMP privilege. A secondary UNIQUE the arbiter does not cover, or a surrogate key re-minted at capture, can make an upsert collide or replace rather than update. Re-applying the same band to a converged target changes no row: the band is idempotent on its success path, and the recovery from a failed apply is to re-run the same bytes once the blocking condition is cleared. The staged block’s DROP TABLE IF EXISTS opener absorbs a temp table a failed run left behind.

That prologue is where snapshot --data-only departs from pg_dump --section=data: pg_dump’s data section assumes an empty target, while this band asserts the captured state over whatever the target holds. The band is not atomic across tables unless applied inside one transaction; that is stated as a stderr advisory, never as a refusal. The snapshot-data record’s prologue member reads delete only when a prologue was actually emitted, so a full snapshot’s band reports none.

The data band is assembled in a pre-flight pass before any DDL byte is written, so a malformed corpus file aborts cleanly with no partial output.

Order and duplicates. Rows are emitted in a deterministic total order: anchor-op position first, then layer rank (the --label order), then file path, then position within the file. Records identical in {anchor op, canonical body, label set} are coalesced to one, so the same row present in both the base corpus and a second layer is emitted once. Two records that re-state the same row divergently at one anchor are a hard dml-data-conflict abort, never a silent pick.

The order is corpus order, not an FK topological sort. In a full snapshot the foreign keys are added after the band, so a child row ahead of its parent still loads, and a child row with no parent anywhere in the band fails at the post-data ADD CONSTRAINT. A snapshot --data-only band lands on a database whose foreign keys already exist, so there a child row ahead of its parent fails at its INSERT against an immediate foreign key. Either way the band fails at psql -f apply time rather than at snapshot time. For seed data with FK dependencies, the anchor positions set the order.

Render failures: skip vs abort

A state the replay reaches can still fail to render, and the two layers behave differently.

Element-level: skip and continue, exit 0. When one element’s body cannot be reconstructed from the cut, snapshot omits it, finishes the document and exits 0. Each skip prints one warning on stderr (warning: <code>: <message>, or {"level":"warning","code":"<code>",...} under --as=json); read them before applying the output. The split is by element type, not by cause:

  • view-render-skipped: a view or materialized view whose body cannot be reconstructed. Views take this code for every render refusal, including one caused by a reference that is not live as of the cut, so a view never surfaces under the element code below. Because the code does not name the cause, the remedy depends on which one the message describes. When the refusal is an output-column reconstruction failure (a projection whose column labels can’t be derived), emit the view’s frozen column list explicitly. When it is a missing or non-live reference (a tombstoned or not-yet-live object the body names), no column list helps: restore the referenced object into the cut, or rewrite the view to stop referencing it. Recreating the view by hand after applying works for either.
  • element-render-skipped: a function or trigger whose body references an element that is not live as of the cut. At an interior cut this is often the correct historical answer, not a defect; at --at last it points at a real gap.

Forest-level: abort, non-zero exit. A failure to materialize the cut at all is fatal and emits no DDL: render-blocked-on-unknowns, render-blocked-on-partial-forest, render-blocked-on-replay-error.

--skip-manifest <path>

--skip-manifest <path> streams the element-level skips above to <path> as JSONL: the machine-readable companion to the stderr warnings, sharing import’s record shape. Records carry at least kind, name, schema, raw_stmt_kind, line, and raw_text; line and raw_text are import-time fields with no emit-time meaning, so on a snapshot skip they are always 0 and "" rather than absent.

The manifest’s kind values are view-render and element-render, deliberately not the same spellings as the stderr warning codes view-render-skipped and element-render-skipped above. The two surfaces describe the same skips under different vocabularies, so a filter written against the warning codes matches nothing in the JSONL. Key the manifest on the bare kind values.

An element_id naming the tombstoned element rides the element-render path and is omitted whenever it is unset: always on view-render records, and on any element-render record whose skip supplied no id. Read it by presence, never by position. src_file is omitted entirely, since an emit-time skip has no source file. Treat the field set as open and tolerate keys you don’t recognize. The flag does not change exit status: a run whose only issue is skipped elements still exits 0.

The file is written whenever the run actually reaches the manifest step, including with zero skips (you get an empty file), so its existence is not evidence that anything was skipped, and its absence is not evidence that nothing was. The write sits near the end of the run, after the DDL has been emitted, so anything that returns before it leaves no file: an empty or header-only oplog short-circuits well before the render, and a refusal along the way (a bad flag combination, an unresolvable cut, a forest-level render-blocked-on-* abort) ends the run without a manifest.

Because the write is an atomic rename, a path naming a project-managed file (the active oplog or the resolved configuration file) would destroy it, so that collision refuses with skip-manifest-clobbers-managed-file on any snapshot that reaches the manifest step, not only one that had something to record. The guard follows config resolution rather than the default filename, so it covers a custom path selected by --config or RETROFIT_CONFIG, not just a project-root retrofit.toml. The check runs at write time rather than at flag-parse time, so the DDL document has already been emitted to stdout when it fires: the refusal protects the managed file, not the run’s output. Point it somewhere else.

–using: one-shot emit target

--using=<engine>:<major> overrides the engine version for one invocation. Resolution chain when --using is absent: [[schema]] engine_version (per-schema override) > [project] engine_version (project default). The flag’s <engine> must match the project engine; <major> must be in this build’s supported set.

--using is read-only: it does not write the pin to retrofit.toml. For a durable change use retrofit schema engine <version> or edit retrofit.toml.

Failure modes

  • opid-not-found: the --at opid prefix doesn’t resolve.
  • schema-not-in-cut: --schema or --exclude-schema named a schema the cut doesn’t contain.
  • table-not-in-cut: --table or --exclude-table named a table the cut doesn’t contain.
  • fk-cross-schema-unreachable: an FK from an included table to an excluded table; pick a different filter set.
  • flag-conflict: incompatible flag combination (e.g. --include-drop=schema with --table).
  • unsupported-on-engine-version: the cut contains ops the resolved engine version cannot emit (e.g. PG18 ops with a PG17 pin).
  • unknown-engine-name: --using named an engine that doesn’t match the project engine.
  • unknown-engine-version: --using named a version not in this build’s supported set.
  • invalid-include-drop: --include-drop value is not none / scoped / schema.
  • data-path-missing: the schema’s declared data_root directory is absent.
  • data-file-unreadable: a corpus data-file path is missing or unreadable. The envelope names the path.
  • data-file-malformed: a corpus data file source violates the data-file grammar (e.g. an out-of-enum purpose= value). Aborts in pre-flight, before any DDL is written.
  • dml-data-conflict: two divergent versions of one row at one anchor. Hard abort; the envelope names both files.
  • data-unavailable-at: an --at cut where a chain’s surviving versions all anchor later than the cut.
  • label-empty: --label was passed empty or whitespace-only.
  • skip-manifest-clobbers-managed-file: the --skip-manifest path is the active oplog or the resolved configuration file; see § --skip-manifest <path>.
  • render-blocked-on-unknowns / render-blocked-on-partial-forest / render-blocked-on-replay-error: the cut could not be materialized; see § Render failures. Distinct from the element-level skips there, which exit 0.

Scope contract

snapshot’s in-scope set is every replayable operator: if an op is in the replay engine, snapshot renders its post-state (see § What snapshot is), subject to the element-level render skips in § Render failures. The full per-operator inventory is in smo-catalog.md.

The pg_dump option-by-option parity audit lives in pg_dump-parity.md.

Before 1.0, any release may change behavior; the release notes list what changed.