Register item 18 — Step-0 verification + build record (2026-08-20)

Read-only verification of "Connector rollout phases 2–3 (build, after the above): Merchant Center + public Shopping; then LinkedIn / Amazon Ads / The Trade Desk" before building, and the record of what was then built. Claims are measured in-repo at file:line unless labeled otherwise. Companion to docs/reports/ITEM15_STEP0_VERIFICATION_2026-08-20.md (whose projector this item's KG leg extends) and the item-16 report.

Headline

The register line was stale in the same way items 15/16's were: every phase-2 AND phase-3 adapter has been built and mounted since 2026-08-12 (direct_connectors.py — MerchantCenterAdapter, PublicShoppingAdapter, LinkedInAdsAdapter, AmazonAdsAdapter, TradeDeskAdapter; module memory records them live on revision service-marketing-connectors-00007-52f). What was actually open was narrower and partly a defect list:

  1. TENANT-001 gap — /api/v1/direct-connectors/{provider}/sync passed req.tenant_id straight to ingestion (old direct_connectors.py:441) while every sibling ingest door (ingest_batch main.py:650, shopify sync :721, bucket sync :679) resolves the tenant against the verified caller. A mapped caller could ingest into a tenant it does not hold, on all eight providers.
  2. Fetch-guard gap — PublicShoppingAdapter fetched caller-supplied options.urls with no scheme/host validation: an authed caller could point the gateway at internal hosts (probe primitive) or ingest arbitrary content as public_source.
  3. The KG lineage leg could not exist for ANY direct-pull provider — not just phases 2–3. The adapters emitted provider-prefixed record_types (meta_ads_insight, google_merchant_center_product_report via record_type=f"{provider}_{kind}") and raw-only payloads, while the item-26 projector maps (provider, record_type) keys against payload.normalized. Every direct-pull event would land in the canonical store and skip projection (unmapped:* / no_normalized_payload). The module's connected bar — "source→canonical→KG lineage proof passes" — was unreachable on this door.
  4. Test depth: 3 in-service tests covered the eight adapters; no route, tenant, or normalization pins.

What was already right: the governed order itself. The sync route flows fetched records through _ingest_records → service-canonical-ingestion (main.py:300-322,1814-1820) — no side door, rule 1 honored.

What was built (this change set)

Deliberately NOT built

Honest end condition

Build half of item 18 is code-complete and pinned; nothing here is live-verified, because no phase-2/3 provider has credentials: every adapter still reports configured: false and stays ready-to-connect (the module's own bar). Remaining operator work per provider: install credentials/account ids via Secret Manager (GOOGLE_MERCHANT_ID + content-API access, PUBLIC_SHOPPING_FEED_URLS, LINKEDIN_*, AMAZON_ADS_*, TTD_*), map the calling tenant (TENANT-001 registry), then run one bounded /api/v1/direct-connectors/{provider}/sync and prove the source→canonical→KG lineage on the projector sweep — that proof, not this change, is what upgrades a provider's status.

← All docsView source on GitHub →