Memory Wiki

Untitled Session

2026-05-24 18:13 UTC · 64 messages · cli

You2026-05-24 18:14 UTC
SGS Phase Gate checkpoint for billing conversion tracking. Complete adversarial review of T52 (new Google Ads module), T17 (webhook handler revision), and T15 (embedded checkout + GCLID). Check architecture, security, FAULT compliance, backwards compatibility, test quality, and YMYL fitness. GREENLIGHT or REDLIGHT verdict.
Donna2026-05-24 18:14 UTC
I'll start by loading all reference documents and source code in parallel.
Tool2026-05-24 18:14 UTC
{"content": " 1| 1|# Module 3 Revision: Google Ads Conversion Tracking\n 2| 2|\n 3| 3|**Status:** Decision Plan\n 4| 4|**Date:** 2026-05-24\n 5| 5|**Author:** SA (Super Architect)\n 6| 6|**Scope:** Module 3 (T15-T17) revision — billing conversion track...
Tool2026-05-24 18:14 UTC
{"content": " 1|# Past Faults Log — Profe Core Modules\n 2|\n 3|> **Purpose:** Every bug, design miss, and edge case discovered during development is recorded here.\n 4|> Future modules (and future TBs) MUST read this log before writing code.\n 5|\n 6|---\n 7|\n 8|## ...
Tool2026-05-24 18:14 UTC
{"content": " 439|### Module Module 3 — Billing (Site Areas: 1, 8, 12)\n 440|\n 441|**Goal:** Stripe payment gateway integration with recurring billing.\n 442|\n 443|**Modules (6):** T15-T20\n 444|\n 445|| # | Module | Responsibility | New Tables | Depends On (Locked) |\n 446||---|--...
Donna2026-05-24 18:14 UTC
Now let me read all source code files in parallel.
Tool2026-05-24 18:14 UTC
{"total_count": 7, "files": ["/root/profe-modules/google_ads_conversion/src/GoogleAdsConversion/GoogleAdsConversionService.php", "/root/profe-modules/google_ads_conversion/tests/Unit/GoogleAdsConversionServiceTest.php", "/root/profe-modules/google_ads_conversion/tests/Unit/GoogleAdsConversionModuleT...
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * WebhookHandler — Stripe webhook event processing implementation\n 4| *\n 5| * HC-01: Stripe SDK used for signature verification via StripeServiceInterface.\n 6| * HC-02: Webhook signature via SDK — NOT hand-rolled HMAC.\n 7| * HC-03: I...
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * WebhookHandlerModule — ModuleInstanceInterface implementation for webhook_handler\n 4| *\n 5| * HC-04: Webhook route CSRF/Auth exempt.\n 6| * FAULT-004: Webhook route explicitly exempted.\n 7| * FAULT-01: Factory closure captures $c, n...
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * StripeServiceInterface — Stripe API Wrapper Service Contract\n 4| *\n 5| * Thin, type-safe wrapper around the official stripe/stripe-php SDK.\n 6| * This is the ONLY module that interacts with Stripe — no other module\n 7| * may call \...
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * StripeService — Stripe API Wrapper Implementation\n 4| *\n 5| * HC-01: Stripe SDK used — NOT hand-rolled curl.\n 6| * HC-02: Webhook signature via SDK — uses \\Stripe\\Webhook::constructEvent().\n 7| * HC-03: API keys via ConfigManager...
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * CheckoutService — Checkout flow orchestration implementation\n 4| *\n 5| * HC-02: Tier slug validated before session creation via InputSanitizerInterface.\n 6| * HC-03: Price map maps slugs to Stripe Price IDs from ConfigManager.\n 7| ...
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * CheckoutServiceModule — ModuleInstanceInterface implementation for checkout_service\n 4| *\n 5| * HC-01: Payment endpoints CSRF-protected via checkAuthWithCsrf().\n 6| * FAULT-004: CSRF enforced on all mutation POST endpoints.\n 7| * F...
Donna2026-05-24 18:14 UTC
Now reading all T52 files in parallel.
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * GoogleAdsConversionInterface — Google Ads Conversion Tracking Service Contract\n 4| *\n 5| * @package Profe\\GoogleAdsConversion\n 6| */\n 7|\n 8|declare(strict_types=1);\n 9|\n 10|namespace Profe\\GoogleAdsConversion;\n ...
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * GoogleAdsConversionService — Google Ads Conversion Tracking Implementation\n 4| *\n 5| * HC-01: Google Ads PHP SDK used (googleads/google-ads-php) — NOT hand-rolled API calls.\n 6| * HC-02: OAuth2 credentials via ConfigManagerInterface — N...
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * GoogleAdsConversionModule — ModuleInstanceInterface implementation for google_ads_conversion\n 4| *\n 5| * FAULT-01: Factory closure captures $c, never stores container.\n 6| * FAULT-011: Uses \\\\ (2 backslashes) in single-quoted strings ...
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * GoogleAdsConversionException — Typed exception for Google Ads Conversion operations\n 4| *\n 5| * FAULT-017: Exception class lives in ONE canonical location: src/{Module}/Exception/.\n 6| *\n 7| * @package Profe\\GoogleAdsConversion\n ...
Tool2026-05-24 18:14 UTC
{"total_count": 3, "files": ["/root/profe-modules/google_ads_conversion/tests/Unit/GoogleAdsConversionServiceTest.php", "/root/profe-modules/google_ads_conversion/tests/Unit/GoogleAdsConversionModuleTest.php", "/root/profe-modules/google_ads_conversion/tests/Unit/GoogleAdsConversionExceptionTest.php...
Donna2026-05-24 18:14 UTC
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * GoogleAdsConversionServiceTest — Unit tests for GoogleAdsConversionService\n 4| *\n 5| * FAULT-005/009: Minimum 12 tests per module.\n 6| * HC-09: Tests ≥ 12.\n 7| *\n 8| * @package Profe\\GoogleAdsConversion\\Tests\\Unit\n 9| ...
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * GoogleAdsConversionModuleTest — Unit tests for GoogleAdsConversionModule\n 4| *\n 5| * FAULT-005/009: Minimum 12 tests per module.\n 6| *\n 7| * @package Profe\\GoogleAdsConversion\\Tests\\Unit\n 8| */\n 9|\n 10|declare(stri...
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * GoogleAdsConversionExceptionTest — Unit tests for GoogleAdsConversionException\n 4| *\n 5| * @package Profe\\GoogleAdsConversion\\Tests\\Unit\n 6| */\n 7|\n 8|declare(strict_types=1);\n 9|\n 10|namespace Profe\\GoogleAdsConv...
Tool2026-05-24 18:14 UTC
{"total_count": 5, "files": ["/root/profe-modules/webhook_handler/tests/Integration/WebhookHandlerIntegrationTest.php", "/root/profe-modules/webhook_handler/tests/Unit/WebhookHandlerModuleTest.php", "/root/profe-modules/webhook_handler/tests/Fixture/WebhookHandlerFixtures.php", "/root/profe-modules/...
Tool2026-05-24 18:14 UTC
{"total_count": 4, "files": ["/root/profe-modules/checkout_service/tests/Unit/CheckoutServiceTest.php", "/root/profe-modules/checkout_service/tests/Fixture/CheckoutServiceFixtures.php", "/root/profe-modules/checkout_service/tests/Integration/CheckoutServiceIntegrationTest.php", "/root/profe-modules/...
Tool2026-05-24 18:14 UTC
{"total_count": 3, "files": ["/root/profe-modules/stripe_service/tests/Unit/StripeServiceTest.php", "/root/profe-modules/stripe_service/tests/Integration/StripeServiceIntegrationTest.php", "/root/profe-modules/stripe_service/tests/Unit/StripeServiceModuleTest.php"]}
Donna2026-05-24 18:14 UTC
Let me check the webhook handler tests and repository for the conversion retry implementation.
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|declare(strict_types=1);\n 3|\n 4|namespace Profe\\WebhookHandler\\Test\\Unit;\n 5|\n 6|use PHPUnit\\Framework\\TestCase;\n 7|use Profe\\WebhookHandler\\WebhookHandler;\n 8|use Profe\\WebhookHandler\\WebhookHandlerInterface;\n 9|use Profe...
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|declare(strict_types=1);\n 3|\n 4|namespace Profe\\WebhookHandler\\Test\\Unit;\n 5|\n 6|use PHPUnit\\Framework\\TestCase;\n 7|use Profe\\WebhookHandler\\Repository\\WebhookEventRepository;\n 8|use Profe\\Chassis\\ProfeDB;\n 9|\n 10|cla...
Tool2026-05-24 18:14 UTC
{"total_count": 1, "files": ["/root/profe-modules/webhook_handler/src/WebhookHandler/Repository/WebhookEventRepository.php"]}
Tool2026-05-24 18:14 UTC
{"total_count": 1, "files": ["/root/profe-modules/webhook_handler/src/WebhookHandler/Migration/CreateWebhookEventsMigration.php"]}
Donna2026-05-24 18:14 UTC
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * WebhookEventRepository — DB operations on profe_webhook_events\n 4| *\n 5| * FAULT-04: Bare table names, no $wpdb->prefix.\n 6| * FAULT-05: All queries use prepared statements.\n 7| *\n 8| * @package Profe\\WebhookHandler\\Reposito...
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * CreateWebhookEventsMigration — Migration for profe_webhook_events table\n 4| *\n 5| * Registered via DbMigrationInterface in boot().\n 6| * HC-03: Idempotency via UNIQUE KEY defined in DDL.\n 7| *\n 8| * @package Profe\\WebhookHand...
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|declare(strict_types=1);\n 3|\n 4|namespace Profe\\WebhookHandler\\Test\\Fixture;\n 5|\n 6|/**\n 7| * WebhookHandlerFixtures — Test data fixtures for webhook_handler tests\n 8| *\n 9| * FAULT-07: Test fixtures in correct test namespace.\n...
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|declare(strict_types=1);\n 3|\n 4|namespace Profe\\CheckoutService\\Test\\Unit;\n 5|\n 6|use PHPUnit\\Framework\\TestCase;\n 7|use Profe\\CheckoutService\\CheckoutService;\n 8|use Profe\\CheckoutService\\CheckoutServiceInterface;\n 9|use ...
Donna2026-05-24 18:14 UTC
Tool2026-05-24 18:14 UTC
{"content": " 500| $config->method('getInt')->with('stripe.trial_period_days', 0)->willReturn(0);\n 501|\n 502| $sanitizer = $this->createMock(InputSanitizerInterface::class);\n 503| $sanitizer->method('validate')->with('lite', 'slug');\n 504|\n 505| $stripeSe...
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|declare(strict_types=1);\n 3|\n 4|namespace Profe\\CheckoutService\\Test\\Fixture;\n 5|\n 6|/**\n 7| * CheckoutServiceFixtures — Test data fixtures for checkout_service tests\n 8| *\n 9| * FAULT-07: Test fixtures in correct test namespace...
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|declare(strict_types=1);\n 3|\n 4|namespace Profe\\StripeService\\Test\\Unit;\n 5|\n 6|use PHPUnit\\Framework\\TestCase;\n 7|use Profe\\StripeService\\StripeService;\n 8|use Profe\\StripeService\\StripeServiceInterface;\n 9|use Profe\\Str...
Donna2026-05-24 18:14 UTC
Now let me run the verification commands.
Tool2026-05-24 18:14 UTC
{"output": "No syntax errors detected in /root/profe-modules/google_ads_conversion/src/GoogleAdsConversion/GoogleAdsConversionService.php\nNo syntax errors detected in /root/profe-modules/google_ads_conversion/src/GoogleAdsConversion/GoogleAdsConversionInterface.php\nNo syntax errors detected in /ro...
Tool2026-05-24 18:14 UTC
{"output": "No syntax errors detected in /root/profe-modules/webhook_handler/src/WebhookHandler/WebhookHandler.php\nNo syntax errors detected in /root/profe-modules/webhook_handler/src/WebhookHandler/WebhookHandlerModule.php\nNo syntax errors detected in /root/profe-modules/webhook_handler/src/Webho...
Tool2026-05-24 18:14 UTC
{"output": "No syntax errors detected in /root/profe-modules/stripe_service/src/StripeService/StripeService.php\nNo syntax errors detected in /root/profe-modules/stripe_service/src/StripeService/StripeServiceInterface.php\nNo syntax errors detected in /root/profe-modules/checkout_service/src/Checkou...
Tool2026-05-24 18:14 UTC
{"output": "T52 clean\nT17 clean\nT15 stripe clean\nT15 checkout clean", "exit_code": 0, "error": null}
Donna2026-05-24 18:14 UTC
Let me check for the critical migration issue I spotted, and verify test syntax.
Tool2026-05-24 18:14 UTC
{"output": "No syntax errors detected in /root/profe-modules/google_ads_conversion/tests/Unit/GoogleAdsConversionServiceTest.php\nNo syntax errors detected in /root/profe-modules/google_ads_conversion/tests/Unit/GoogleAdsConversionModuleTest.php\nNo syntax errors detected in /root/profe-modules/goog...
Tool2026-05-24 18:14 UTC
{"output": "No syntax errors detected in /root/profe-modules/webhook_handler/tests/Unit/WebhookHandlerTest.php\nNo syntax errors detected in /root/profe-modules/webhook_handler/tests/Unit/WebhookEventRepositoryTest.php\nNo syntax errors detected in /root/profe-modules/webhook_handler/tests/Fixture/W...
Tool2026-05-24 18:14 UTC
{"output": "No syntax errors detected in /root/profe-modules/checkout_service/tests/Unit/CheckoutServiceTest.php\nNo syntax errors detected in /root/profe-modules/checkout_service/tests/Fixture/CheckoutServiceFixtures.php", "exit_code": 0, "error": null}
Tool2026-05-24 18:14 UTC
{"output": "No syntax errors detected in /root/profe-modules/stripe_service/tests/Unit/StripeServiceTest.php", "exit_code": 0, "error": null}
Donna2026-05-24 18:14 UTC
Tool2026-05-24 18:14 UTC
{"total_count": 0}
Tool2026-05-24 18:14 UTC
{"total_count": 18, "matches": [{"path": "/root/profe-modules/webhook_handler/src/WebhookHandler/Repository/WebhookEventRepository.php", "line": 110, "content": " `conversion_retry_count` = 0,"}, {"path": "/root/profe-modules/webhook_handler/src/WebhookHandler/Repository/WebhookEventRepo...
Tool2026-05-24 18:14 UTC
{"total_count": 0}
Tool2026-05-24 18:14 UTC
{"total_count": 0}
Donna2026-05-24 18:14 UTC
Let me check a few more things — the interface contracts and README.
Tool2026-05-24 18:14 UTC
{"total_count": 1, "files": ["/root/profe-modules/checkout_service/src/CheckoutService/CheckoutServiceInterface.php"]}
Tool2026-05-24 18:14 UTC
{"content": " 90|| 1 (immediate) | 0s (first attempt in handler) |\n 91|| 2 (first retry) | 5 minutes |\n 92|| 3 | 15 minutes |\n 93|| 4 | 1 hour |\n 94|| 5 | 6 hours |\n 95|| 6 | 24 hours |\n 96|| 7+ | Abandon — manual intervention required |\n 97|\n 98|### New Columns in...
Tool2026-05-24 18:14 UTC
{"total_count": 0}
Donna2026-05-24 18:14 UTC
Tool2026-05-24 18:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * CheckoutServiceInterface — Checkout flow orchestration service contract\n 4| *\n 5| * @package Profe\\CheckoutService\n 6| */\n 7|\n 8|declare(strict_types=1);\n 9|\n 10|namespace Profe\\CheckoutService;\n 11|\n 12|int...
Donna2026-05-24 18:14 UTC
Now I have everything I need. Let me compile the complete SGS Phase Gate verdict.
Tool2026-05-24 18:14 UTC
{"output": "/root/profe-modules/google_ads_conversion/tests/Unit/GoogleAdsConversionServiceTest.php:19\n/root/profe-modules/google_ads_conversion/tests/Unit/GoogleAdsConversionModuleTest.php:11\n/root/profe-modules/google_ads_conversion/tests/Unit/GoogleAdsConversionExceptionTest.php:9\n/root/profe-...
Donna2026-05-24 18:14 UTC
Here is my complete SGS Phase Gate verdict: --- ## SGS Phase Gate Verdict ### Architecture: FAIL The architecture follows the SA's plan correctly (Option C: Both — server-side API + embedded checkout). T52 creates the Google Ads interface, T17 wires conversion upload into the webhook, T15 adds uiMode + GCLID capture. The module dependency chain is sound. **But there are structural gaps:** 1. **Missing migration for conversion retry columns (CRITICAL).** The `CreateWebhookEventsMigration.php` DDL creates the `profe_webhook_events` table with 9 columns. But the Repository and Handler code references `conversion_retry_count`, `conversion_retry_at`, and `conversion_payload` — none of which exist in the migration. There is no second migration adding them either (searched entire profe-modules tree for `ALTER TABLE` or `conversion_retry` — zero ALTERs found). The README correctly documents these columns, confirming they were intended. Deploying this will produce MySQL "Unknown column" errors on any conversion retry operation. 2. **No scheduler for retry processing.** `processConversionRetries()` is public and well-implemented (exponential backoff: 5min, 15min, 1hr, 6hr, 24hr), but nothing calls it. No cron job, no WP Cron hook, no scheduler registration. Without invocation, failed conversions silently sit in the database forever. 3. **T52's `uploadClickConversion()` is a stub.** The actual Google Ads PHP SDK call is commented out behind a simulated `sprintf()` return. The plan specifies "thin wrapper around the Google Ads PHP SDK" — this wrapper is not wired. Acceptable for Phase 1 scaffolding but noted. --- ### Security: PASS - **GCLID input validation** — Regex `/^[a-zA-Z0-9_-]+$/` applied to both cookie and URL-sourced GCLID. XSS in GCLID payload is blocked. - **CSRF enforcement** — CheckoutServiceModule's `checkAuthWithCsrf()` calls `requireValidNonce()` on both POST endpoints (create-session, success). - **Webhook route exempt** — `registerWebhookExemption()` calls `addExemptRoute('stripe-webhook')` per FAULT-004 requirement. - **Signature verification** — Uses Stripe SDK `\Stripe\Webhook::constructEvent()` via StripeServiceInterface. No hand-rolled HMAC. - **No quadruple backslashes** — FAULT-011 verification passes: 0 occurrences of quadruple backslashes across all 4 modules. - **API keys via ConfigManager** — Not hardcoded. Credentials validated via `isReady()`. - **No injection vectors** — Prepared statements in Repository. Email normalized (lowercase + trim + filter_var) before SHA-256. --- ### FAULT compliance: FAIL - **FAULT-006 (Missing migration columns): RECURS.** The original CreateWebhookEventsMigration (FAULT-006 corrected in Phase 1) created the table. But the new conversion retry feature adds 3 columns that have no migration. This is a recurrence of FAULT-006. - **FAULT-004 (CSRF): PASS** — Both checkout endpoints enforce CSRF. - **FAULT-005 (No tests): PASS** — Exceeds 12 tests per module. - **FAULT-008 (Injected dependencies): PASS** — GoogleAdsConversionInterface injected into WebhookHandler constructor. - **FAULT-011 (Quadruple backslash): PASS** — Zero occurrences. - **FAULT-017 (Exception location): PASS** — Single canonical location. --- ### Backwards compatibility: PASS - `uiMode` parameter defaults to `'hosted'` in both StripeServiceInterface and CheckoutServiceInterface. - `metadata` parameter defaults to `[]` in StripeServiceInterface. - Existing callers (checkout_service → stripe_service) work unchanged when omitting the new params. - Old Stripe Checkout redirect flow is preserved — embedded mode is additive. - The `getGclidFromCookie()` returns null when no Site Kit cookie or URL param is present, so existing sessions with no GCLID metadata are unaffected. - StripeService `createRefund()`, `retrieveSubscription()`, `syncSubscription()` all unchanged. --- ### Test quality: PASS Test counts across modules: | Module | Tests | Assertions | Quality | |--------|-------|------------|---------| | T52 GoogleAdsConversionServiceTest | 19 | ~50 | Real assertions on actual return values. Tests isReady() all 5 config keys, hashEmail normalization, uploadConversion resource name format, enhanced conversions disabled path, credential failure path, currency uppercasing. | | T52 GoogleAdsConversionModuleTest | 11 | ~20 | DI registration, interface compliance, boot/shutdown lifecycle. | | T52 GoogleAdsConversionExceptionTest | 9 | ~12 | Factory methods, message prefixing, code propagation. | | T17 WebhookHandlerTest | 16 | ~40 | Signature verification, duplicate detection, event routing (all 5 types), error marking. NEW: GCLID skip path, upload called with correct params, retry enqueue on failure, processConversionRetries success + failure paths. | | T15 CheckoutServiceTest | 18 | ~35 | Hosted mode, embedded mode returns client_secret, GCLID cookie pass-through, GCLID URL fallback, malformed GCLID rejection, event emission with uiMode+gclid. | | T15 StripeServiceTest | 10 | ~15 | Interface signature verification, uiMode default value, metadata default value, webhookSecret, user not found paths. | No false-positive tests. No try/catch swallowing in tests. All use `expectException()` or `assert*()` with real value checks. The `preg_match` test for malformed GCLID specifically verifies XSS `<script>` payloads are rejected. --- ### YMYL fitness: PASS (with caveats) **The good:** - Non-blocking conversion upload — webhook processing continues even if Google Ads API fails - Exponential backoff retry (5min → 15min → 1hr → 6hr → 24hr) - Dedup-safe: uses `$session->created` timestamp as the conversionDateTime for consistent dedup key - Both `GoogleAdsConversionException` and `\Throwable` caught in upload path, both enqueue retry - HTTP 500 returned for unhandled errors (not silent 200) - Stripe idempotency via UNIQUE KEY on `event_id` - Conversion data stored as JSON in `conversion_payload` for audit **The bad:** - **Retry mechanism won't work without the migration columns** — this is the showstopper - **Retry mechanism won't trigger without a scheduler** — `processConversionRetries()` exists but is never called - T52 SDK stub means no actual Google Ads API calls happen — conversion upload returns a simulated resource name --- ### Critical findings: 1. **BLOCKING: Migration missing 3 columns for conversion retry.** `conversion_retry_count`, `conversion_retry_at`, and `conversion_payload` are referenced by Repository code and documented in README but do not exist in any migration DDL. Deploying as-is will cause SQL errors. 2. **BLOCKING: No scheduler for `processConversionRetries()`.** The retry processor is never called — no cron hook, no WP Cron job, no scheduler registration. Failed conversions will never be retried. 3. **HIGH: T52 `uploadClickConversion()` is a stub.** The real Google Ads PHP SDK integration is commented out. The module cannot actually upload conversions to Google Ads. --- ### Verdict: REDLIGHT Reasoning: Two blocking issues. 1. **Migration columns missing** — The `profe_webhook_events` table DDL lacks the 3 columns that the entire conversion retry system depends on. Every `storeConversionRetry()`, `findPendingConversionRetries()`, `updateConversionRetry()`, and `clearConversionRetry()` call will fail with MySQL "Unknown column" errors. This makes the retry mechanism — a core reliability requirement from the SA's plan (Section 4.5 Idempotency & Reliability) — entirely non-functional. 2. **No retry scheduler** — Even if the migration were fixed, `processConversionRetries()` has no invocation mechanism. The plan explicitly requires "retry via a queue" with exponential backoff. The enqueue logic is correct, but the dequeue/processing is not wired. **These are fixable.** Required actions before GREENLIGHT: 1. **Create a migration** (either alter the existing `CreateWebhookEventsMigration.php` DDL, or create a new `AddConversionRetryColumnsMigration.php`) that adds: - `conversion_retry_count` TINYINT(3) UNSIGNED DEFAULT 0 - `conversion_retry_at` DATETIME NULL - `conversion_payload` JSON NULL 2. **Register the retry processor** — Either a WP Cron hook (`wp_schedule_event`) or a simple `add_action('init', ...)` that calls `processConversionRetries()`. The cron approach is preferred to avoid blocking web requests. 3. **Wire the real SDK call** in `uploadClickConversion()` or add a clear TODO/stub ticket for Phase 2. 4. **Add concurrency guard** — `SELECT ... FOR UPDATE` on the retry query, or at minimum `LOCK IN SHARE MODE`, to prevent double-processing if multiple workers run simultaneously.