/** * The Postgres driver for `loops-serve backfill-revisions` (hasna/apps#1724 P3). * * Pure shell around the contract-level backfill in `../bundle/backfill.ts`: * enumerate tenants, then per tenant run the candidate list through * `collectRevisionBackfillCandidates` (which pages `listLoops` — RLS-scoped, so * it must run inside the tenant context) and attempt each loop in P3 cohort * order, one batch per tenant-scoped transaction. * * Every per-tenant transaction is an `executor.withRequestContext(...)` — the * same `SET LOCAL ROLE open_loops_runtime` + `open_loops.tenant_id` binding the * API uses — so the job writes exactly as the push route does and RLS applies * exactly as it does in serve. The transaction is also the batch boundary: * anything a batch attempted either all commits or all rolls back, and an * unexpected error (a row archived mid-run, a concurrent push that won the * version race, an object store that already holds the key) rolls back the * batch, is recorded as a `batchError`, and the run moves PAST it — never * retrying the same failing batch in a loop. Rerunning the job retries it, and * everything that already committed is skipped by the `alreadyBackfilled` * guard. * * `--limit` budgets real backfill units (`created` + `wouldCreate`): skips are * free because they are stable across reruns, so a limited run stops exactly * where a resumption should continue. `--dry-run` performs zero writes of any * kind — no ledger rows, no objects. */ import type { BundleArtifactStorage } from "../bundle/artifact-storage.js"; import { type RevisionBackfillAttempt } from "../bundle/backfill.js"; import type { PgPoolExecutor } from "./pg-executor.js"; export declare const REVISION_BACKFILL_DEFAULT_AUTHOR = "backfill-revisions"; export declare const REVISION_BACKFILL_DEFAULT_BATCH_SIZE = 100; export declare const REVISION_BACKFILL_DEFAULT_REASON = "backfill-revisions: revision 1 for legacy bundled loops (hasna/apps#1724 P3)"; export interface RevisionBackfillCommandOptions { /** Classify, name and digest, but write nothing anywhere. */ dryRun?: boolean; /** * Cap on real backfill units this run performs (`created`, or `wouldCreate` * under --dry-run). Skips do not consume budget. Default: no cap. */ limit?: number; /** Loops per tenant-scoped transaction. Default 100. */ batchSize?: number; /** Restrict the run to one tenant. Default: every tenant in `tenants`. */ tenantId?: string; /** Ledger author for every revision (and the request-context principal id). */ author?: string; /** Manifest source.reason recorded on every revision. */ reason?: string; sourceStation?: string; sourceAgent?: string; } export interface RevisionBackfillTenantReport { tenantId: string; /** Loops the run looked at in this tenant (skips included). */ attempted: number; created: number; wouldCreate: number; skipped: number; skippedByReason: Record; /** First few skip attempts with their detail, so an operator can see who was left out and why. */ samples: RevisionBackfillAttempt[]; /** The most recent batch that rolled back, if any. The run moved past it; rerun to retry it. */ batchError?: string; } export interface RevisionBackfillCommandResult { dryRun: boolean; tenants: RevisionBackfillTenantReport[]; created: number; wouldCreate: number; skipped: number; /** Tenant-scoped batches attempted (committed, or would-committed under --dry-run). */ batches: number; batchErrors: string[]; /** True when --limit ran out of budget before every candidate was attempted. */ stopped: boolean; complete: boolean; } export declare function runRevisionBackfillCommand(executor: PgPoolExecutor, artifacts: BundleArtifactStorage, opts?: RevisionBackfillCommandOptions): Promise;