# Core snapshot mirroring This is a server-only library using PostgreSQL control and artifact persistence, wired into the executable runner. It supports snapshot-diff delivery and opt-in native replacement. The ordinary upsert lifecycle still rejects mirror mode; mirror runs use this separate core lifecycle. ## Adapter and admission `SnapshotMirrorAdapter` combines a normal destination stream with a pure `MirrorProjection` (`rowType` and `project`). The projection returns bounded provider-ready `{ identity, upsert, remove }` values, deterministic for the immutable configuration revision. The core validates both output payloads against the stream schemas, enforces unique source keys, and rejects conflicting shared identities. Returning `[]` deliberately excludes a valid source row from the desired audience. Its source key is still validated, stored and checked for duplicates. Invalid rows or projections must fail; never convert errors into empty results. A previously tracked member becomes removable only when no source row projects it, after the full snapshot is sealed and additions are accepted. An all-excluded snapshot is a valid empty desired audience and still requires explicit finish and promotion. Prepared provider delivery operations remain non-empty. Schema transforms must already be applied by the projection; validation cannot silently alter the stored provider payload. The canonical identity must include everything needed to identify a removable remote member; its removal payload must depend only on that identity and immutable configuration, not mutable source attributes. Adapters must declare `batchDelivery: "accepted"` or `"asynchronous"`, snapshot-diff or native-replace capability and explicit removal support. Asynchronous batches must resolve independently of `finish()` and return recoverable remote job IDs with staged outcomes. Finish-staged upload strategies are rejected. Unexpected staged outcomes from an `"accepted"` adapter are journaled but cannot authorize removals. Use `openMirrorPersistence` to bind the journal to normalized effect envelopes. Do not pass an ordinary row-projecting persistence session: it could normalize/hash an identifier twice. A private session marker rejects that accidental combination. The caller must hold and renew the Kubernetes per-sync lease. PostgreSQL provides atomic persistence, not a second worker lease or stale-worker fencing. The caller must verify `targetBaseline`: `new-empty` means a new empty target; `tracked` means an exclusively managed target whose complete baseline has been tracked/imported. An arbitrary pre-existing audience is not valid for snapshot-diff. Native replacement instead requires `targetBaseline: "replace"`: adapter-verified replacement authority without relying on an imported membership baseline. This library cannot discover/import remote membership or verify account permissions itself. ## New run 1. Build a mirror/full `RunInput` and open `openMirrorPersistence` after Kubernetes admission. 2. Supply `runSnapshotMirror` with the session, adapter, runtime context, verified baseline, mapping and a lazy complete cursorless `source`. 3. The core initializes the writer, collects/validates the full desired snapshot, then seals it before sending any additions/removals. 4. Bounded additions must all be durably accepted before removal planning starts. Requests obey both provider row limits and the configured manifest byte limit. Asynchronous adapters may stage multiple batches in the current phase. The core then returns `delivery: "pending"` without starting removals or finalization. Pending additions and removals are not accepted membership changes. 5. Explicit `finish()` handles empty/unchanged snapshots too. Only accepted finish can atomically commit state and promote the generation; pending finish cannot. Writers receive the ordinary context and journal facade, never snapshot storage, database access or a diff API. Journal manifests carry core `Effect` envelopes; provider requests contain their stored `upsert`/`remove` payloads with the same IDs. `mirrorDeliveryBatch` performs this conversion without normalization. Mirror operation keys are identity hashes; model keys enforce source uniqueness without source-to-identity association rows. The returned `sourceSequence` counts delivery operations, not source rows or a full-query extraction offset. Known outcomes are persisted even when a row is rejected or cancellation arrives during a request. Permanent rejection stops immediately. Accepted effects survive abort and participate in later diffs. Uncertain calls and started finalization do not trigger cleanup that could erase recovery evidence. ## Native replacement The snapshot head persists `strategy: "native-replace"`; absent strategy means the original snapshot-diff. Recovery rejects a strategy mismatch. Every desired member is uploaded, including unchanged members; touched identities prevent re-preparation within the logical run. Individual removal batches are prohibited. `prepareFinish()` verifies the snapshot is sealed, every desired member has a prepared upload and every upload was accepted. The provider's `finish()` then performs cleanup; pending cleanup uses the existing durable finish receipt and `reconcileFinish()` lifecycle. An empty snapshot still goes through explicit finish. Failed extraction never does. After confirmed cleanup, persist `replacementStatus: "accepted"` before pruning local members absent from the snapshot. Restore replays that pruning before later baseline compaction, so deleted memberships cannot reappear after a crash. The existing baseline and object-store machinery is retained; no per-row SQL tables or new schema are added. The writer context is unchanged and exposes no snapshot methods. ## Recovery `resumeSnapshotMirror` requires a recovery session and a sealed candidate. It has no source argument and never invokes projection or `stream.createWriter`. - `reconcileBatch` receives the exact persisted provider request, action, prior receipt and current context/store. Return verified outcomes or perform a provider-proven safe idempotent replay; missing IDs do not prove absence. State changes use explicit core reconciliation methods. - `attachWriter(context)` reattaches the verified existing session using the recovered context and buffered store. It must not blindly create a new remote session and is only invoked when further delivery is needed. - `reconcileFinish` handles prepared/pending finalization. Already accepted/local finish resolution and final checkpoint retries need no provider call or writer attachment. Reconciled store changes share the receipt/checkpoint transaction. - Diff reads restart from acknowledged membership, skipping accepted effects without a mutable diff cursor or warehouse re-extraction. - For asynchronous delivery, reconcile every unresolved journal batch once per attempt. If any remain staged, return pending without attaching a writer or planning more changes. Only after all additions resolve can removals start; only after all removals resolve can finalization start. Provider polling cadence belongs to the adapter/scheduler, not a long-running loop in the core. Missing remote IDs or unresolvable outcomes block recovery rather than permit replay. - Incomplete extraction cannot resume from changed SQL. Reconcile/abort the old session, acquire a new logical run (discarding its abandoned candidate) and collect a new full source. Interrupted initialization uses persistence's explicit init reset/reopen path. Unknown remote state remains blocked. The caller owns maintenance, task/log state, timeouts, lease renewal, cancellation and provider reconciliation policy. These remain executable-runner work, not background loops silently started by this library. ## Tests From `services/retl-runner`, run `pnpm test` against disposable PostgreSQL with restricted runtime roles. `RETL_MIRROR_SCALE_TEST=1 pnpm test` also seeds one million synthetic desired SQLite rows and checks bounded diff pages. This validates pagination, not million-row serialization/extraction or provider throughput. No production database or advertising API is touched.