- Fixed `updated_at` not changing when `addPrices` updates an existing price rule. The update
  wrote the loaded row back including its old `updated_at`, which keeps MySQL's
  `ON UPDATE CURRENT_TIMESTAMP` from firing, and a row whose values did not change is not
  updated at all. With automatic cleanup enabled, every rule the nightly sync kept sending was
  deleted once it was older than "Days Since Last Update", emptying the table every other night
  with a one-day setting. `Model\ResourceModel\Price::updateMultiple()` now sets `updated_at` to
  the current time.
- Added "Start Time" to the automatic cleanup settings, so the cleanup can run after the
  nightly price import instead of at midnight. Saving it writes the job's cron expression to
  `crontab/default/jobs/conneqt_specialprices_clean_table_cronjob/schedule/cron_expr`; until
  then the schedule in `etc/crontab.xml` (midnight) applies.
- IMM-207 :: Fixed the first page after login being cached in the full page cache under a
  key shared by every customer of the same customer group, so another customer of that group
  could be served the first customer's special prices (built-in FPC and Varnish). The module's
  HTTP context values (`customer_id`, `special_prices_customer_id`, `customer`) were only set
  before the controller action, so the `X-Magento-Vary` cookie sent with the login response
  did not contain them; Magento's page cache uses that cookie for both lookup and save on the
  next request. They are now also updated on `customer_login` and removed on
  `customer_logout`, which covers every login route through the customer session (login form,
  registration auto-login, account confirmation, Login as Customer, Magewire). The shared logic
  lives in `Model\CustomerHttpContext`.
- IMM-207 :: `Plugin\CustomerSessionContext` now hooks `ActionInterface::execute()` instead of
  `AbstractAction::dispatch()`, so controllers that do not extend `AbstractAction` also get the
  customer values, and is declared for the storefront only (`etc/frontend/di.xml`, as Magento's
  own customer context plugin is) instead of globally. The plugin's `beforeDispatch()` is
  replaced by `beforeExecute()` and its constructor changed; code calling the plugin directly
  must be updated.
- IMM-207 :: The `customer_login` observer ignores events that carry a customer data object
  instead of a `Customer` model (the integration token service and some third-party
  registration modules dispatch it that way); the session login that follows dispatches it
  with the model.
- IMM-207 :: The customer data model in the HTTP context (`customer`) no longer enters the
  vary data. For guests the empty data model gave every response an `X-Magento-Vary` cookie,
  so a guest's first request never matched a cached page. For logged-in customers the built-in
  full page cache stores an entry's context as JSON, where the object is lost, so every cache
  hit re-set the vary cookie to a different value and the next page was a miss cached under a
  second key. The data model is still available per request through
  `HttpContext::getValue('customer')`; after a logout in the same request it returns `false`.
- IMM-207 :: `Plugin\QuotePrices::beforeCollectTotals()` skips quote items without a product
  instead of failing on them.
- IMM-208 :: Added a safeguard to the automatic cleanup: with "Cleanup Safeguard" enabled, a
  cleanup that would leave no more than "Minimum Remaining Percentage" (0–90, default 10) of all
  price rules is skipped and logged as a warning. This protects against a full delete when an
  import stops updating rows, for example after the connection to the source system dropped.
  Disabled by default. A percentage above 90 is capped at 90 and a non-numeric value is
  treated as 90, both with a logged warning. `Cron\CleanTableCronjob`'s constructor gains two
  trailing arguments (`ScopeConfigHelper`, a logger).
- Fixed price boxes serving one customer's personal price to other customers when the
  `block_html` cache is enabled. Configurable, bundle and grouped final prices were shared
  within a customer group, and the tier price box (per-customer `qty > 1` rules) was shared
  with every logged-in customer. The per-customer cache key and the
  `conneqt_special_prices` / `customer_<id>` cache tags were added by
  `FinalPriceBoxOverride`, which is only a preference for the catalog final price box; the
  other price boxes extend core classes directly and never received them. The variation now
  lives in the `Plugin\PriceBoxCache` plugin on `Magento\Framework\Pricing\Render\PriceBox`,
  which every price box inherits. `FinalPriceBoxOverride` no longer overrides
  `getCacheKey()` and `getCacheTags()`; subclasses that call `parent::` on either still get
  the customer variation. The tag names are defined once on `Helper\MagentoCache`, which
  also cleans them.
- "Guest Customer Special Prices" now has an explicit default of No in `etc/config.xml`.
  `ScopeConfigHelper::getGuestCustomerAllowed()` carried an unreachable `?? true`
  fallback; an unsaved setting has always resolved to No, so no shop changes behaviour.