Pixel Ingress Perimeter — rule-04 decision package

Status: built, flag-off everywhere; the activation decision is OPEN and is the owner's (GATE 2). Nothing in the build this document ships changes an IAM binding, flips a flag, or serves a byte of pixel traffic. Claim label: built, pre-benchmark — this path has never carried a live batch. Lane: ORACLE pre-conversion follow-on (pixel ingress perimeter package, 2026-08-19); plan of record docs/ORACLE_PRECONVERSION_INTENT_v1.1.md.

1. The problem this closes

The ORACLE build landed the extender's /pixel/events door IAM-locked with no application-level auth — correct under rule 04 (the single-ingress collapse removed the extender from the intended-public register, and a second public receiver would re-open the door that collapse closed). But browsers cannot mint Cloud Run OIDC, so as landed there was no path from a consented storefront browser to the door at all: activating the pixel would have pointed every batch at a 403 wall, and the tempting quick fix — a public invoker binding on the extender — is exactly the rule-04 hazard.

2. Topology (as built)

storefront browser (Shopify Web Pixel sandbox, consent-gated capture-core)
    │  POST /pixel/collect — batch body carries the per-install ingest token
    ▼
service-marketing-connectors        ← THE public boundary (existing, load-bearing
    │  verify token (constant-time)    `allUsers` binding; NO new binding anywhere)
    │  tenant from registry row        flag PIXEL_COLLECT_ENABLED, default OFF ⇒ 404
    ▼  OIDC (outbound_headers)
intent-shopify-extender /pixel/events   ← stays IAM-locked, code unchanged
    ▼
Cell 33 /v1/signals                     ← the governed door: consent fail-closed,
                                          taxonomy, O-1 bright lines re-checked

The browser-facing edge lives on the service that is already public by design (webhook HMAC + OAuth — rule 04's load-bearing binding), preserving the single-ingress invariant: one public Shopify boundary, downstream consumers reached only over OIDC.

3. Auth design

4. What is public, exactly

Surface Unauthenticated answer Why
gateway /pixel/collect, flag OFF (today) 404 both verbs route behaves as absent; public surface byte-identical to pre-build
gateway /pixel/collect, flag ON 401 without a valid token (503 if misconfigured); CORS preflight 204 the decided public posture; every response body is counts/reason only
extender /pixel/events 403 at the IAM layer (unchanged) never public, before or after

/health and /readyz stay liveness/readiness-only (issue #676); pixel posture booleans live on the verify_caller-gated /api/v1/providers.

5. GATE-2 activation order (the open decision)

Each step is safe alone; the order makes every intermediate state fail-closed. Do not reorder — in particular the flag is LAST, so no public 503/401 surface exists before the lane behind it is real.

  1. Bind SHOPIFY_PIXEL_INGEST_SECRET (Secret Manager) on the gateway. Value out-of-band, never in chat (operator register item 6 discipline).
  2. Set SHOPIFY_PIXEL_COLLECT_URL = the gateway's own public origin.
  3. Confirm INTENT_EXTENDER_URL still set (webhook fan-out already uses it).
  4. Extender: set PIXEL_EVENTS_ENABLED=1 (route exists, still IAM-only).
  5. Gateway: set PIXEL_COLLECT_ENABLED=1 — the door opens.
  6. Re-run pixel activation for installed shops (install is re-runnable) so settings ship ingest_url + install_id + ingest_token + O-2 knobs (vdi_threshold, dwell_threshold_ms).
  7. Smoke, in this order: bad token ⇒ 401; no token ⇒ 401; valid token ⇒ 200 with forwarded counts; extender /health shows pixel_events_enabled: true; gateway /api/v1/providers shows both pixel booleans true. (Pre-flip, assert /pixel/collect ⇒ 404 — the flag-off state is the baseline every smoke starts from.)

Prerequisites that remain owner/operator-held and are not part of this package: the first holdout registration before any activation flag that exposes users (constitution — holdout precedes activation), Shopify app project pixel artifact deployment (OP row), and the label stream.

6. Explicitly out of scope for this build

No IAM change (no binding added or removed anywhere). No flag flips. No BQ DDL. No holdout registration, no dashboard surfacing, no causal-credit change (master prompt out-of-scope list). The superseded twin design (6c17727, token verify on the extender itself) was deliberately NOT ported as-is: the landed topology puts the browser edge on the gateway, and the extender keeps zero secret-reading code — the same posture as the webhook HMAC boundary ("the HMAC boundary and the shop pin live at the gateway").

7. Enforcement, both directions

tests/connectors/test_pixel_collect.py pins: flag-off 404 (source-literal OFF default asserted), secret-unset 503, uniform 401s (bad token / unknown shop / missing fields — bodies byte-identical), constant-time compare in source, valid-token forward (tenant from row, token stripped, OIDC headers), caps, CORS, and the hook's defer/ship behavior including derivation cross-assertion against the door's verifier. PIXEL_SETTINGS_ALLOWED_KEYS keeps its refusal test (person-shaped keys never ship) and gains the named stream. capture-core.test.mjs pins the fail-closed configuredSender and the batch auth fields.

← All docsView source on GitHub →