'yoast', 'seo-by-rank-math/rank-math.php' => 'rank_math', 'all-in-one-seo-pack/all_in_one_seo_pack.php' => 'aioseo', ]; /** * @return array, * browsegradeRecheckState: string * }> */ public static function all(): array { return [ 'wp_resolution_robots' => [ 'resolverId' => 'wp_resolution_robots', 'ownership' => 'mixed', 'riskClass' => 'review_and_apply', 'capability' => 'manage_options', 'nonceAction' => 'thedotdev_agentic_bridge_resolve_robots', 'previewRequired' => true, 'explicitApprovalRequired' => true, 'beforeSnapshotRequired' => true, 'rollbackSupported' => true, 'localCheckRequired' => true, 'localCheckIds' => ['disc-robots-route'], 'browsegradeRecheckState' => 'not_started', ], 'wp_resolution_sitemap' => [ 'resolverId' => 'wp_resolution_sitemap', 'ownership' => 'mixed', 'riskClass' => 'review_and_apply', 'capability' => 'manage_options', 'nonceAction' => 'thedotdev_agentic_bridge_resolve_sitemap', 'previewRequired' => true, 'explicitApprovalRequired' => true, 'beforeSnapshotRequired' => true, 'rollbackSupported' => true, 'localCheckRequired' => true, 'localCheckIds' => ['disc-sitemap-route'], 'browsegradeRecheckState' => 'not_started', ], 'wp_resolution_llms_agent_map' => [ 'resolverId' => 'wp_resolution_llms_agent_map', 'ownership' => 'agentic_bridge', 'riskClass' => 'safe_automatic', 'capability' => 'manage_options', 'nonceAction' => 'thedotdev_agentic_bridge_resolve_artifact_routes', 'previewRequired' => true, 'explicitApprovalRequired' => true, 'beforeSnapshotRequired' => true, 'rollbackSupported' => true, 'localCheckRequired' => true, 'localCheckIds' => ['disc-llms-route', 'disc-agent-map-route'], 'browsegradeRecheckState' => 'not_started', ], 'wp_resolution_title_meta' => [ 'resolverId' => 'wp_resolution_title_meta', 'ownership' => 'seo_plugin', 'riskClass' => 'review_and_apply', 'capability' => 'manage_options', 'nonceAction' => 'thedotdev_agentic_bridge_resolve_title_meta', 'previewRequired' => true, 'explicitApprovalRequired' => true, 'beforeSnapshotRequired' => true, 'rollbackSupported' => true, 'localCheckRequired' => true, 'localCheckIds' => ['meta-title-check'], 'browsegradeRecheckState' => 'not_started', ], 'wp_resolution_canonical' => [ 'resolverId' => 'wp_resolution_canonical', 'ownership' => 'seo_plugin', 'riskClass' => 'review_and_apply', 'capability' => 'manage_options', 'nonceAction' => 'thedotdev_agentic_bridge_resolve_canonical', 'previewRequired' => true, 'explicitApprovalRequired' => true, 'beforeSnapshotRequired' => true, 'rollbackSupported' => true, 'localCheckRequired' => true, 'localCheckIds' => ['meta-canonical-check'], 'browsegradeRecheckState' => 'not_started', ], 'wp_resolution_index_intent' => [ 'resolverId' => 'wp_resolution_index_intent', 'ownership' => 'mixed', 'riskClass' => 'review_and_apply', 'capability' => 'manage_options', 'nonceAction' => 'thedotdev_agentic_bridge_resolve_index_intent', 'previewRequired' => true, 'explicitApprovalRequired' => true, 'beforeSnapshotRequired' => true, 'rollbackSupported' => true, 'localCheckRequired' => true, 'localCheckIds' => ['idx-site-indexable', 'idx-post-index-intent'], 'browsegradeRecheckState' => 'not_started', ], 'wp_resolution_og_twitter' => [ 'resolverId' => 'wp_resolution_og_twitter', 'ownership' => 'seo_plugin', 'riskClass' => 'review_and_apply', 'capability' => 'manage_options', 'nonceAction' => 'thedotdev_agentic_bridge_resolve_og_twitter', 'previewRequired' => true, 'explicitApprovalRequired' => true, 'beforeSnapshotRequired' => true, 'rollbackSupported' => true, 'localCheckRequired' => true, 'localCheckIds' => ['meta-og-twitter-check'], 'browsegradeRecheckState' => 'not_started', ], 'wp_resolution_schema_conflict_org_website' => [ 'resolverId' => 'wp_resolution_schema_conflict_org_website', 'ownership' => 'seo_plugin', 'riskClass' => 'guided_developer', 'capability' => 'manage_options', 'nonceAction' => 'thedotdev_agentic_bridge_resolve_schema_conflict', 'previewRequired' => true, 'explicitApprovalRequired' => true, 'beforeSnapshotRequired' => true, 'rollbackSupported' => true, 'localCheckRequired' => true, 'localCheckIds' => ['meta-schema-conflict-check'], 'browsegradeRecheckState' => 'not_started', ], 'wp_resolution_language_viewport_favicon' => [ 'resolverId' => 'wp_resolution_language_viewport_favicon', 'ownership' => 'theme_or_template', 'riskClass' => 'guided_developer', 'capability' => 'manage_options', 'nonceAction' => 'thedotdev_agentic_bridge_resolve_theme_meta', 'previewRequired' => true, 'explicitApprovalRequired' => true, 'beforeSnapshotRequired' => true, 'rollbackSupported' => false, 'localCheckRequired' => true, 'localCheckIds' => ['meta-language-viewport-favicon-check'], 'browsegradeRecheckState' => 'not_started', ], 'wp_resolution_policy_contact_paths' => [ 'resolverId' => 'wp_resolution_policy_contact_paths', 'ownership' => 'external', 'riskClass' => 'external_hosting', 'capability' => 'manage_options', 'nonceAction' => 'thedotdev_agentic_bridge_resolve_policy_contact_paths', 'previewRequired' => true, 'explicitApprovalRequired' => true, 'beforeSnapshotRequired' => true, 'rollbackSupported' => false, 'localCheckRequired' => true, 'localCheckIds' => ['disc-policy-contact-paths'], 'browsegradeRecheckState' => 'not_started', ], 'wp_resolution_schema_org_website' => [ 'resolverId' => 'wp_resolution_schema_org_website', 'ownership' => 'mixed', 'riskClass' => 'safe_automatic', 'capability' => 'manage_options', 'nonceAction' => 'thedotdev_agentic_bridge_preview_fix', 'previewRequired' => true, 'explicitApprovalRequired' => true, 'beforeSnapshotRequired' => true, 'rollbackSupported' => true, 'localCheckRequired' => true, 'localCheckIds' => ['und-home-schema'], 'browsegradeRecheckState' => 'not_started', ], 'wp_resolution_meta_missing' => [ 'resolverId' => 'wp_resolution_meta_missing', 'ownership' => 'seo_plugin', 'riskClass' => 'review_and_apply', 'capability' => 'manage_options', 'nonceAction' => 'thedotdev_agentic_bridge_preview_fix', 'previewRequired' => true, 'explicitApprovalRequired' => true, 'beforeSnapshotRequired' => true, 'rollbackSupported' => true, 'localCheckRequired' => true, 'localCheckIds' => ['trn-robots-rules'], 'browsegradeRecheckState' => 'not_started', ], 'wp_resolution_alt_text_advisory' => [ 'resolverId' => 'wp_resolution_alt_text_advisory', 'ownership' => 'mixed', 'riskClass' => 'guided_developer', 'capability' => 'manage_options', 'nonceAction' => 'thedotdev_agentic_bridge_preview_fix', 'previewRequired' => true, 'explicitApprovalRequired' => true, 'beforeSnapshotRequired' => true, 'rollbackSupported' => false, 'localCheckRequired' => true, 'localCheckIds' => ['act-intent-declarations'], 'browsegradeRecheckState' => 'not_started', ], ]; } /** * @return array{ * resolverId: string, * ownership: string, * riskClass: string, * capability: string, * nonceAction: string, * previewRequired: bool, * explicitApprovalRequired: bool, * beforeSnapshotRequired: bool, * rollbackSupported: bool, * localCheckRequired: bool, * localCheckIds: list, * browsegradeRecheckState: string * }|null */ public static function resolver(string $resolver_id): ?array { $registry = self::all(); return $registry[$resolver_id] ?? null; } public static function is_supported_resolver(string $resolver_id): bool { return self::resolver($resolver_id) !== null; } /** * @return array{ * resolverId: string, * ownership: string, * riskClass: string, * capability: string, * nonceAction: string, * previewRequired: bool, * explicitApprovalRequired: bool, * beforeSnapshotRequired: bool, * rollbackSupported: bool, * localCheckRequired: bool, * localCheckIds: list, * browsegradeRecheckState: string * }|null */ public static function classify_finding(string $finding_key): ?array { $map = self::finding_to_resolver_map(); if (!isset($map[$finding_key])) { return null; } return self::resolver($map[$finding_key]); } /** @return array */ public static function engine_finding_map(): array { return [ 'disc-llms-route' => 'publish_artifacts', 'disc-agent-map-route' => 'publish_artifacts', 'disc-llms-valid' => 'publish_artifacts', 'und-agent-map-format' => 'publish_artifacts', 'und-home-schema' => 'add_home_schema', ]; } /** * Machine-readable, source-of-truth coverage for every finding the local * registry recognizes. "apply_after_review" is deliberately limited to * implemented Agentic Bridge-owned output; every other outcome is human- * guided, externally owned, or deferred. * * @return array */ public static function coverage_projection(): array { $projection = []; $engine_map = self::engine_finding_map(); foreach (self::finding_to_resolver_map() as $finding_id => $registry_resolver_id) { $resolver = self::resolver($registry_resolver_id); if ($resolver === null) { continue; } $engine_resolver_id = $engine_map[$finding_id] ?? ''; $ownership = (string) $resolver['ownership']; $outcome = 'guided'; $apply_available = false; $reason = __('This finding needs a human-guided change because Agentic Bridge does not own the affected setting.', 'agentic-bridge'); if ($engine_resolver_id === 'publish_artifacts' && $ownership === 'agentic_bridge') { $outcome = 'apply_after_review'; $apply_available = true; $reason = __('Agentic Bridge can publish its own reviewed local artifact routes after named approval.', 'agentic-bridge'); } elseif ($engine_resolver_id === 'add_home_schema' && $registry_resolver_id === 'wp_resolution_schema_org_website') { $seo_owner = self::detect_seo_owner_vocabulary(); if (($seo_owner['owner'] ?? 'none') !== 'none') { $outcome = 'external_owner'; $reason = __('An SEO plugin currently owns homepage schema output. Agentic Bridge will not write it; review it in that plugin.', 'agentic-bridge'); } else { $outcome = 'apply_after_review'; $apply_available = true; $reason = __('Agentic Bridge can add only its own missing homepage schema after named approval.', 'agentic-bridge'); } } elseif ($ownership === 'seo_plugin') { $outcome = 'external_owner'; $reason = __('An SEO plugin may own this setting. Agentic Bridge will not write it; review it in that plugin.', 'agentic-bridge'); } elseif ($ownership === 'theme_or_template') { $outcome = 'guided'; $reason = __('Your active theme or template owns this setting. Follow the guided manual check; no change is available here.', 'agentic-bridge'); } elseif ($ownership === 'external') { $outcome = 'external_owner'; $reason = __('This is outside the WordPress site’s local ownership. No Agentic Bridge change is available.', 'agentic-bridge'); } elseif ($registry_resolver_id === 'wp_resolution_alt_text_advisory') { $outcome = 'deferred'; $reason = __('Alt text needs human-authored source context. Agentic Bridge will not invent or write it.', 'agentic-bridge'); } $projection[$finding_id] = [ 'findingId' => $finding_id, 'registryResolverId' => $registry_resolver_id, 'engineResolverId' => $engine_resolver_id, 'outcome' => $outcome, 'applyAvailable' => $apply_available, 'reason' => $reason, ]; } return $projection; } /** @return array{findingId: string, registryResolverId: string, engineResolverId: string, outcome: string, applyAvailable: bool, reason: string}|null */ public static function coverage_for_finding(string $finding_id): ?array { $projection = self::coverage_projection(); return $projection[$finding_id] ?? null; } /** * @return array{owner: string, activePlugins: list} */ public static function detect_seo_owner_vocabulary(): array { $matched = []; foreach (self::SEO_PLUGIN_MAP as $plugin_file => $owner) { if (is_plugin_active($plugin_file)) { $matched[] = $owner; } } if ($matched === []) { return ['owner' => 'none', 'activePlugins' => []]; } $matched = array_values(array_unique($matched)); if (count($matched) > 1) { return ['owner' => 'multiple', 'activePlugins' => $matched]; } $owner = $matched[0]; if (!in_array($owner, ['yoast', 'rank_math', 'aioseo'], true)) { return ['owner' => 'unknown', 'activePlugins' => $matched]; } return ['owner' => $owner, 'activePlugins' => $matched]; } public static function should_block_seo_managed_resolver(string $resolver_id): bool { $resolver = self::resolver($resolver_id); if ($resolver === null || $resolver['ownership'] !== 'seo_plugin') { return false; } $owner = self::detect_seo_owner_vocabulary()['owner'] ?? 'unknown'; return $owner !== 'none'; } /** * @return array */ private static function finding_to_resolver_map(): array { return [ 'browsegrade.robots' => 'wp_resolution_robots', 'browsegrade.sitemap' => 'wp_resolution_sitemap', 'browsegrade.llms_route' => 'wp_resolution_llms_agent_map', 'browsegrade.agent_map_route' => 'wp_resolution_llms_agent_map', 'browsegrade.title_meta' => 'wp_resolution_title_meta', 'browsegrade.canonical' => 'wp_resolution_canonical', 'browsegrade.index_intent' => 'wp_resolution_index_intent', 'browsegrade.og_twitter' => 'wp_resolution_og_twitter', 'browsegrade.schema_org_website_conflict' => 'wp_resolution_schema_conflict_org_website', 'browsegrade.language_viewport_favicon' => 'wp_resolution_language_viewport_favicon', 'browsegrade.policy_contact_paths' => 'wp_resolution_policy_contact_paths', 'disc-llms-route' => 'wp_resolution_llms_agent_map', 'disc-agent-map-route' => 'wp_resolution_llms_agent_map', 'disc-llms-valid' => 'wp_resolution_llms_agent_map', 'und-agent-map-format' => 'wp_resolution_llms_agent_map', 'und-home-schema' => 'wp_resolution_schema_org_website', 'trn-robots-rules' => 'wp_resolution_meta_missing', 'act-intent-declarations' => 'wp_resolution_alt_text_advisory', ]; } }