Three fixes shipped across two PRs:
- #72 (Fix 1) — activation-time fatal guard when
acrossai-abilities-managersibling plugin is inactive. - #73 (Fix 2 + 3) —
mailerpress-pro/list-webhooksresponse-shape normalization +mailerpress/update-campaignread-modify-write shim.
Fix 1 — Activation-time fatal guard
Sites running acrossai-pro 0.9.5+ WITHOUT the sibling acrossai-abilities-manager also active would fatal on plugins_loaded @ 20:
PHP Fatal error: Uncaught Error: Class "AcrossAI_Abilities_Manager\Includes\Modules\Library\Ability_Definition"
not found in .../includes/Abilities/MailerPress/Contacts/List_Contacts.php:21Root cause. AcrossAI_Pro_Abilities_Bootstrap::register_abilities() iterated its Integration_Bootstrap registry and instantiated ability classes regardless of whether the sibling plugin was active. Instantiation triggered the Jetpack autoloader → loaded the ability file → fatal on its extends Ability_Definition clause.
Fix. Static has_abilities_manager() probe that checks class_exists('\AcrossAI_Abilities_Manager\Includes\Modules\Library\Ability_Definition'). Both register_abilities() and register_category_callbacks() early-return on its negation. Plugin now activates cleanly and no-ops its ability suite when the sibling is absent — per DEC-DEPENDENCY-NOTICE-NOT-DIE (D12).
Critical invariant: leading backslash on the probe FQN. Without it class_exists resolves namespace-relative and always returns false — the plugin would silently register zero abilities even when the sibling IS active. Regression test explicitly locks this in.
Fix 2 — mailerpress-pro/list-webhooks response-shape drift
MailerPress-side inconsistency: empty state returns [] (bare array), populated state returns {webhook_id: {...}, ...} (associative map keyed by id). Callers iterating the payload write buggy code that works with 0 or 2+ webhooks but crashes with exactly 1 (or vice versa depending on which case they test first).
Fix. Ability normalizes both shapes to {webhooks: [{id, secret, enabled, description}, ...]} — empty → {webhooks: []}; associative map → extract via array_values().
Sixth documented instance of BUG-ENDPOINT-DRIFT-PATTERNS (B15) with a new sub-variant flagged: shape switches with cardinality.
Fix 3 — mailerpress/update-campaign read-modify-write shim
Two MailerPress-upstream bugs in Campaigns::edit() (mailerpress/src/Api/Campaigns.php ~lines 2178-2180):
- BUG-UPSTREAM-CAMPAIGN-DOUBLE-ENCODE:
'content_html' => !empty($meta['json']) ? wp_json_encode($meta['json']) : wp_json_encode($campaign->content_html)— the fallback re-encodes an already-encoded JSON string. Every PUT that omitsmeta.jsonnests the encoding one level deeper; after N such PUTs the stored content_html is JSON-encoded N+1 times and the block editor can't parse it. - BUG-UPSTREAM-CAMPAIGN-CONFIG-WHOLESALE-REPLACE:
'config' => !empty($meta['emailConfig']) ? wp_json_encode($meta['emailConfig']) : $campaign->config— a partialmeta.emailConfigpayload wholesale-replaces the stored config, wipingcampaignName/editorType/sendChoice/sendAt/lists/campaignList(any key not in the partial).
Shim design. Update_Campaign::execute() now:
GET /campaign/{id}→ returns decodedjson+configfields- Decode via response-shape keys (
jsonfor content,configfor emailConfig — NOT the DB-side keys) - Deep-merge caller partials via
array_replace_recursive - Send FULL merged payload back — MailerPress's non-empty branches trigger correctly (single-encode + full-config replace = intact merge since caller values overlay current)
- If GET fails: return
update_precondition_lookup_failedand refuse the PUT
Both upstream bugs will be reported to MailerPress separately. Shim is idempotent + correct even after upstream fixes land, so no future removal required for correctness.
Test coverage
454 tests / 1657 assertions (0.9.7) → 457 tests / 1673 assertions (0.9.8) — three new source-content regression tests:
test_bootstrap_guards_register_abilities_on_abilities_manager_presencetest_update_campaign_shims_upstream_bugs_via_read_modify_writetest_list_webhooks_normalizes_upstream_cardinality_drift
Zero effect on happy-path installs
Sites with both plugins active + callers sending full campaign meta payloads see no behavioural change. The three fixes only trip when the failure mode they address would have triggered — otherwise they're pure-passthrough with one extra class_exists / GET /campaign/{id} / array_values call respectively.
Merged PRs
- #72 — hotfix(0.9.8): guard AcrossAI_Pro_Abilities_Bootstrap on sibling plugin
- #73 — fix(abilities): 2 MailerPress-upstream drift shims (sibling of #72 for 0.9.8)
Previous release
For v0.9.7 (Feature 017: MailerPress workflow abilities), see v0.9.7 release notes.