diff --git a/wp-content/plugins/esp-instanda-integration/includes/class-config.php b/wp-content/plugins/esp-instanda-integration/includes/class-config.php index 086bd3de..0cc7f3b3 100644 --- a/wp-content/plugins/esp-instanda-integration/includes/class-config.php +++ b/wp-content/plugins/esp-instanda-integration/includes/class-config.php @@ -9,8 +9,11 @@ * an explicit, gated switch to Live - see the Go-Live checklist. * * Secrets entered in the admin UI are encrypted at rest with libsodium - * (sodium_crypto_secretbox, XSalsa20-Poly1305). The key comes from the - * SPG_INSTANDA_KEY constant, so a database dump alone never exposes a usable secret. + * (sodium_crypto_secretbox, XSalsa20-Poly1305). The "a database dump alone never + * exposes a usable secret" guarantee holds ONLY when the SPG_INSTANDA_KEY constant is + * set: without it the key is self-provisioned into the DB (see db_key()), so the key + * and ciphertext live in the same database and a dump recovers the password. Set + * SPG_INSTANDA_KEY (and the credential constants) in production for the hardened posture. * Live credentials are expected as constants and are never written to the database. * * @package ESP\Instanda diff --git a/wp-content/plugins/esp-instanda-integration/includes/class-go-live.php b/wp-content/plugins/esp-instanda-integration/includes/class-go-live.php index b0c0c517..cd1bd580 100644 --- a/wp-content/plugins/esp-instanda-integration/includes/class-go-live.php +++ b/wp-content/plugins/esp-instanda-integration/includes/class-go-live.php @@ -106,9 +106,29 @@ final class Go_Live { ['key' => 'A2', 'label' => 'Alert email recipient(s)', 'detail' => $emails !== '' ? $emails : 'None set', 'pass' => $emails !== ''], ['key' => 'A3', 'label' => 'Front-end form submit + redirect verified in a browser', 'detail' => $this->ack_detail('espinstanda_frontend_ack'), 'pass' => (bool) get_option('espinstanda_frontend_ack')], ['key' => 'A4', 'label' => 'No unresolved failures', 'detail' => $unresolved === 0 ? 'Clean' : "{$unresolved} unresolved", 'pass' => $unresolved === 0], + ['key' => 'A5', 'label' => 'Exactly one active INSTANDA feed', 'detail' => $this->feed_summary(), 'pass' => $this->active_feed_count() === 1], ]; } + /** Count active INSTANDA feeds across all forms (should be exactly one at go-live). */ + private function active_feed_count(): int { + if (!class_exists(__NAMESPACE__ . '\\Instanda_AddOn')) { + return 0; + } + $feeds = Instanda_AddOn::get_instance()->get_feeds(); + return count(array_filter((array) $feeds, static fn ($f) => !empty($f['is_active']))); + } + + private function feed_summary(): string { + $n = $this->active_feed_count(); + if ($n === 1) { + return '1 active feed'; + } + return $n === 0 + ? 'No active feed - the quote form will not push to INSTANDA' + : "{$n} active feeds - deactivate all but the canonical quote form (a stray test-form feed also creates quotes)"; + } + /** @return array{complete:int,total:int,ready:bool} */ public function readiness(): array { $blocking = $this->blocking_items(); @@ -129,6 +149,10 @@ final class Go_Live { return false; } update_option('espinstanda_operating_mode', 'live'); + // Switching the mode must also select the Live environment, mirroring + // return_to_test(); otherwise environment() stays clamped to Test and real + // customer quotes would keep hitting the Test endpoint after go-live. + update_option('espinstanda_env', 'live'); update_option('espinstanda_operating_mode_audit', [ 'actor' => get_current_user_id(), 'ts' => current_time('mysql'),