Skip to content
Patronage Ads Sync Docs
Esc
navigateopen⌘Jpreview
On this page

Architecture and boundaries

Why Ads Sync separates public contracts, reference deployment, and private dogfood operations.

Ads Sync keeps one public product promise across a package and a nested reference deployment.

operator TypeScript config
        |
        v
@patronage/ads-sync  ->  operator runner  ->  Cloudflare Containers
        |                       |                    |
        |                       v                    v
        +-----------------> Postgres <---------- Airbyte destination
                                |
                                +---- artifact metadata and committed state

operator runner  --------------------> R2 replay artifacts

The package owns typed configuration, curated provider definitions, catalog and destination helpers, state and artifact primitives, control/reporting SQL, canary validation, and historical comparison. It does not read environment variables, store credential values, persist R2 objects, authorize migrations, or create remote resources.

The reference deployment proves that an operator can import only the public package boundary and build a Worker with Wrangler. It is intentionally small: one Sync Connection, one Cron trigger, /runs behind the Ads Sync Deployment Token, and the four connector Container classes. It runs a scheduled Qualified Run through @patronage/ads-sync/run with the watermark discipline; multi-connection scheduling and fan-out remain Loop work.

provider tiers

Google Ads, Google Search Console, and Meta Ads are curated Supported Providers. Their definitions include pinned Airbyte source images, backfill defaults, rate limit policy, default schemas, and reporting views. This is the maintained product surface.

defineProvider() exposes the shape for an advanced custom definition. A custom provider still needs an operator-owned catalog, Dockerfile, Container class, policies, and reporting work. The generic helper is not a promise that Ads Sync supports every Airbyte source.

storage and lifecycle

Cloudflare Workers, Containers, Workflows, and R2 define the deployment identity. Standard reachable Postgres is the database contract. Hyperdrive is a reference Worker read path, while destination Containers write to Postgres directly.

Desired state belongs in TypeScript config. Postgres owns mutable runtime state such as Sync Connections, Backfill Plans, Sync Run Windows, committed cursors, generations, artifact metadata, and leases. R2 holds replay artifacts at keys derived from provider, stream, and run id. The Qualified Run streams connector output through the Worker into R2 while it redacts diagnostics and computes manifests; Containers use bounded ephemeral files and never receive R2 credentials.

Destination rows carry their generation id. Reporting views join that id to the committed generation ledger before deduplicating, so a partially written or failed destination attempt remains internal and the Reporting Contract continues serving the prior completed generation.

Each enabled Sync Connection gets an isolated Airbyte Direct Load schema. This avoids collisions in Airbyte generation and state behavior when accounts or catalogs multiply.

canary, backfill, and comparison

A canary is one exact connection over no more than 24 hours. It limits the first live proof and gives the operator a canonical request hash.

A Backfill Plan divides history into durable bounded windows. Provider defaults limit step size and work per run; an operator runner owns scheduling, lease handling, retries, cooldowns, and status. The reference deployment’s Cron tick is the single-connection form: one window from the committed watermark to the Final-Data Horizon, capped at windowStepDays * maxWindowsPerRun, and no_new_final_data when the horizon has not moved.

The Historical Comparison Gate checks stable campaign/day provider data against the reporting view. It is the reporting-correctness launch evidence, separate from live sidecar accumulation.

See the runnable prepare-bounded-run.ts and run-historical-comparison.ts source tutorials.

custody and ejection

Custody has two modes (ADR 0047) over one seam. The reference deployment is single-tenant: secrets are the operator’s own Worker secrets, read through workerSecretsCustodyAdapter, fenced to ADS_SYNC_MANAGED_SECRET_NAMES. Loop is multi-tenant: per-tenant, version-pinned secrets in the Cloudflare secrets store. Because both invoke the same Qualified Run and the same schema contract, a Loop tenant can resume single-tenant on the open-source core. That is the Ejection Path: an informal, documented procedure, not a guarantee.

public and private dogfood

src/packages/ads-sync is born-clean public release source. The deterministic release process mirrors it to packages/ads-sync and applies privacy checks.

apps/ads-sync is Patronage’s private operator deployment. It proves the public imports against real Workflows, Containers, Postgres, R2, canary controls, backfill orchestration, and operational safety. It is evidence, not release source, and its private routes, bindings, credentials, infrastructure names, and maintenance commands are not public package capabilities.

Was this page helpful?