Skip to content

Introduce two new abilities for getting and setting SEO data for given posts#23369

Draft
leonidasmi wants to merge 8 commits into
trunkfrom
add/read-write-post-abilities
Draft

Introduce two new abilities for getting and setting SEO data for given posts#23369
leonidasmi wants to merge 8 commits into
trunkfrom
add/read-write-post-abilities

Conversation

@leonidasmi

@leonidasmi leonidasmi commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Context

Summary

This PR can be summarized in the following changelog entry:

  • Introduces two new Yoast SEO abilities for getting and setting SEO data for given posts.

Relevant technical choices:

Title-based post lookup:

  • The read ability resolves a post by post_id, permalink, or title. The title value is a comma-separated list; each value is matched as a whole contiguous substring of the post's breadcrumb title, and a post is returned if it matches any value (OR/union). Example — hiking boots, trail compiles to:
WHERE object_type = 'post' AND object_sub_type = 'post'
  AND ( is_public IS NULL OR is_public = 1 )
  AND ( breadcrumb_title LIKE '%hiking boots%' OR breadcrumb_title LIKE '%trail%' )
ORDER BY object_last_modified DESC, id DESC
LIMIT 10 OFFSET <(page-1)*10>
  • So "Hiking boots for sale" matches; "hiking shoes for sale" does not.
  • Why we did it that way:
    • Whole-phrase, not per-word. Each comma value matches as a contiguous substring, so hiking boots means the phrase — not "hiking" and "boots" scattered across a title, which is noisy and surprising.
    • Comma = OR. Lets one call find several distinct posts; any match is returned.
    • Paginate, don't batch (the main perf call). Each returned post is rendered through the Meta surface), which is costly per post. So we cap pages at 10 and let the client page, rather than rendering a large set at once.
    • Write path stays strict. update-post-seo-data takes only post_id/permalink, never title, so an edit always hits one unambiguous post.

Test instructions

Test instructions for the acceptance test before the PR gets merged

This PR can be acceptance tested by following these steps:

Declaration of the abilities

The GET SEO data ability
  • Do a GET request to WP's http://example.com/wp-json/wp-abilities/v1/abilities endpoint and confirm that you see the yoast-seo/get-post-seo-data ability we added and that it looks like this:
    {
        "name": "yoast-seo/get-post-seo-data",
        "label": "Get Post SEO Data",
        "description": "Get the SEO data for a post. Identify the post by post_id, by permalink (URL), or by title keywords; a title keyword search returns the SEO data for every matching post. With no identifier, the latest public post is returned.",
        "category": "yoast-seo",
        "input_schema": {
            "type": "object",
            "properties": {
                "post_id": {
                    "type": "integer",
                    "description": "The ID of the post to retrieve.",
                    "minimum": 1
                },
                "permalink": {
                    "type": "string",
                    "description": "The permalink (URL) of the post to retrieve."
                },
                "title": {
                    "type": "string",
                    "description": "Keywords to search for in post titles. The search string is split on whitespace and each token must be present in the breadcrumb title. Returns the SEO data for every matching post."
                }
            }
        },
        "output_schema": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "post_id": {
                        "type": "integer"
                    },
                    "post_title": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "permalink": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "post_type": {
                        "type": "string"
                    },
                    "post_status": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "seo_title": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "seo_title_rendered": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The SEO title as output on the front end: the global default template applied when no custom value is set, with replacement variables expanded. Null when nothing is output."
                    },
                    "meta_description": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "meta_description_rendered": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The meta description as output on the front end: the global default template applied when no custom value is set, with replacement variables expanded. Null when nothing is output."
                    },
                    "focus_keyphrase": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "canonical": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "canonical_rendered": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The canonical URL as output on the front end: the global default template applied when no custom value is set, with replacement variables expanded. Null when nothing is output."
                    },
                    "is_cornerstone": {
                        "type": "boolean"
                    },
                    "noindex": {
                        "type": [
                            "boolean",
                            "null"
                        ]
                    },
                    "nofollow": {
                        "type": "boolean"
                    },
                    "noimageindex": {
                        "type": "boolean"
                    },
                    "noarchive": {
                        "type": "boolean"
                    },
                    "nosnippet": {
                        "type": "boolean"
                    },
                    "open_graph_title": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "open_graph_title_rendered": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The Open Graph title as output on the front end: the global default template applied when no custom value is set, with replacement variables expanded. Null when nothing is output."
                    },
                    "open_graph_description": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "open_graph_description_rendered": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The Open Graph description as output on the front end: the global default template applied when no custom value is set, with replacement variables expanded. Null when nothing is output."
                    },
                    "twitter_title": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "twitter_title_rendered": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The Twitter title as output on the front end: the global default template applied when no custom value is set, with replacement variables expanded. Null when nothing is output."
                    },
                    "twitter_description": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "twitter_description_rendered": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The Twitter description as output on the front end: the global default template applied when no custom value is set, with replacement variables expanded. Null when nothing is output."
                    },
                    "schema_page_type": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "schema_article_type": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "seo_score": {
                        "type": "string",
                        "enum": [
                            "na",
                            "bad",
                            "ok",
                            "good"
                        ]
                    },
                    "readability_score": {
                        "type": "string",
                        "enum": [
                            "na",
                            "bad",
                            "ok",
                            "good"
                        ]
                    },
                    "inclusive_language_score": {
                        "type": "string",
                        "enum": [
                            "na",
                            "bad",
                            "ok",
                            "good"
                        ]
                    }
                }
            }
        },
        "meta": {
            "annotations": {
                "readonly": true,
                "destructive": false,
                "idempotent": true
            },
            "show_in_rest": true,
            "mcp": {
                "public": true
            }
        },
        "_links": {
            "self": [
                {
                    "href": "https://basic.wordpress.test/wp-json/wp-abilities/v1/abilities/yoast-seo/get-post-seo-data",
                    "targetHints": {
                        "allow": [
                            "GET"
                        ]
                    }
                }
            ],
            "collection": [
                {
                    "href": "https://basic.wordpress.test/wp-json/wp-abilities/v1/abilities"
                }
            ],
            "wp:action-run": [
                {
                    "href": "https://basic.wordpress.test/wp-json/wp-abilities/v1/abilities/yoast-seo/get-post-seo-data/run"
                }
            ]
        }
    }
  • Confirm that the above declarations and their descriptions make sense and help agents understand what's possible.
The SET SEO data ability
  • Do a GET request to WP's http://example.com/wp-json/wp-abilities/v1/abilities endpoint and confirm that you see the yoast-seo/update-post-seo-data ability we added and that it looks like this:
    {
        "name": "yoast-seo/update-post-seo-data",
        "label": "Update Post SEO Data",
        "description": "Update the SEO data for a single post. Identify the post by post_id, by permalink (URL), or by unambiguous title keywords. Only the fields you provide are changed; a provided empty value clears that field.",
        "category": "yoast-seo",
        "input_schema": {
            "type": "object",
            "properties": {
                "post_id": {
                    "type": "integer",
                    "description": "The ID of the post to update.",
                    "minimum": 1
                },
                "permalink": {
                    "type": "string",
                    "description": "The permalink (URL) of the post to update."
                },
                "title": {
                    "type": "string",
                    "description": "Title keywords identifying the post to update. Must match exactly one post."
                },
                "seo_title": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "meta_description": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "focus_keyphrase": {
                    "type": [
                        "string",
                        "null"
                    ],
                    "maxLength": 191
                },
                "canonical": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "is_cornerstone": {
                    "type": "boolean"
                },
                "noindex": {
                    "type": [
                        "boolean",
                        "null"
                    ]
                },
                "nofollow": {
                    "type": "boolean"
                },
                "noimageindex": {
                    "type": "boolean"
                },
                "noarchive": {
                    "type": "boolean"
                },
                "nosnippet": {
                    "type": "boolean"
                },
                "open_graph_title": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "open_graph_description": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "twitter_title": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "twitter_description": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "schema_page_type": {
                    "type": [
                        "string",
                        "null"
                    ],
                    "maxLength": 64
                },
                "schema_article_type": {
                    "type": [
                        "string",
                        "null"
                    ],
                    "maxLength": 64
                }
            }
        },
        "output_schema": {
            "type": "object",
            "properties": {
                "post_id": {
                    "type": "integer"
                },
                "post_title": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "permalink": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "post_type": {
                    "type": "string"
                },
                "post_status": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "seo_title": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "seo_title_rendered": {
                    "type": [
                        "string",
                        "null"
                    ],
                    "description": "The SEO title as output on the front end: the global default template applied when no custom value is set, with replacement variables expanded. Null when nothing is output."
                },
                "meta_description": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "meta_description_rendered": {
                    "type": [
                        "string",
                        "null"
                    ],
                    "description": "The meta description as output on the front end: the global default template applied when no custom value is set, with replacement variables expanded. Null when nothing is output."
                },
                "focus_keyphrase": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "canonical": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "canonical_rendered": {
                    "type": [
                        "string",
                        "null"
                    ],
                    "description": "The canonical URL as output on the front end: the global default template applied when no custom value is set, with replacement variables expanded. Null when nothing is output."
                },
                "is_cornerstone": {
                    "type": "boolean"
                },
                "noindex": {
                    "type": [
                        "boolean",
                        "null"
                    ]
                },
                "nofollow": {
                    "type": "boolean"
                },
                "noimageindex": {
                    "type": "boolean"
                },
                "noarchive": {
                    "type": "boolean"
                },
                "nosnippet": {
                    "type": "boolean"
                },
                "open_graph_title": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "open_graph_title_rendered": {
                    "type": [
                        "string",
                        "null"
                    ],
                    "description": "The Open Graph title as output on the front end: the global default template applied when no custom value is set, with replacement variables expanded. Null when nothing is output."
                },
                "open_graph_description": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "open_graph_description_rendered": {
                    "type": [
                        "string",
                        "null"
                    ],
                    "description": "The Open Graph description as output on the front end: the global default template applied when no custom value is set, with replacement variables expanded. Null when nothing is output."
                },
                "twitter_title": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "twitter_title_rendered": {
                    "type": [
                        "string",
                        "null"
                    ],
                    "description": "The Twitter title as output on the front end: the global default template applied when no custom value is set, with replacement variables expanded. Null when nothing is output."
                },
                "twitter_description": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "twitter_description_rendered": {
                    "type": [
                        "string",
                        "null"
                    ],
                    "description": "The Twitter description as output on the front end: the global default template applied when no custom value is set, with replacement variables expanded. Null when nothing is output."
                },
                "schema_page_type": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "schema_article_type": {
                    "type": [
                        "string",
                        "null"
                    ]
                },
                "seo_score": {
                    "type": "string",
                    "enum": [
                        "na",
                        "bad",
                        "ok",
                        "good"
                    ]
                },
                "readability_score": {
                    "type": "string",
                    "enum": [
                        "na",
                        "bad",
                        "ok",
                        "good"
                    ]
                },
                "inclusive_language_score": {
                    "type": "string",
                    "enum": [
                        "na",
                        "bad",
                        "ok",
                        "good"
                    ]
                }
            }
        },
        "meta": {
            "annotations": {
                "readonly": false,
                "destructive": false,
                "idempotent": true
            },
            "show_in_rest": true,
            "mcp": {
                "public": true
            }
        },
        "_links": {
            "self": [
                {
                    "href": "https://basic.wordpress.test/wp-json/wp-abilities/v1/abilities/yoast-seo/update-post-seo-data",
                    "targetHints": {
                        "allow": [
                            "GET"
                        ]
                    }
                }
            ],
            "collection": [
                {
                    "href": "https://basic.wordpress.test/wp-json/wp-abilities/v1/abilities"
                }
            ],
            "wp:action-run": [
                {
                    "href": "https://basic.wordpress.test/wp-json/wp-abilities/v1/abilities/yoast-seo/update-post-seo-data/run"
                }
            ]
        }
    }
  • Confirm that the above declarations and their descriptions make sense and help agents understand what's possible.

Usage of the new abilities

  • Create a post with meaningful content and set its SEO data (title, meta description, social title, robots, SEO/readability score, etc.)
Using the REST API
  • Using our getting SEO data ability:
    • Run a GET request to /wp-json/wp-abilities/v1/abilities/yoast-seo/get-post-seo-data/run?input[id]=<POST_ID>
      • where <POST_ID> is the post ID
    • or run a GET request to /wp-json/wp-abilities/v1/abilities/yoast-seo/get-post-seo-data/run?input[permalink]=https://basic.wordpress.test/uncategorized/<POST_URL>
      • where <POST_URL> is the post's URL
    • or run a GET request to `/wp-json/wp-abilities/v1/abilities/yoast-seo/get-post-seo-data/run?input[title]=<POST_TITLE_KEYWORDS>
      • where <POST_TITLE_KEYWORDS> is keywords that exist in the post's title, separated by commas (we're gonna test this more extensively below).
      • So, if you want to find the post that has a title like 10 ways to backpack across northern Europe, you can use 10 ways, backpack, northern Europe or any combination of that, to find your post
  • Confirm that you get a result of an array with the post's SEO data, like:
[
    {
        "post_id": 712,
        "post_title": "NBA Draft 2026 winners and losers include Dybantsa, Peterson, Boozer and … fans?",
        "permalink": "https://basic.wordpress.test/uncategorized/nba-draft-2026-winners-and-losers-include-dybantsa-peterson-boozer-and-fans/",
        "post_type": "post",
        "post_status": "publish",
        "seo_title": null,
        "seo_title_rendered": "NBA Draft 2026 winners and losers include Dybantsa, Peterson, Boozer and … fans? - Basic",
        "meta_description": "The first round of the draft was a remarkably on-the-rails affair. %%sep%% %%primary_category%%",
        "meta_description_rendered": "The first round of the draft was a remarkably on-the-rails affair. -",
        "focus_keyphrase": "NBA Draft 2026",
        "canonical": null,
        "canonical_rendered": "https://basic.wordpress.test/uncategorized/nba-draft-2026-winners-and-losers-include-dybantsa-peterson-boozer-and-fans/",
        "is_cornerstone": false,
        "noindex": null,
        "nofollow": false,
        "noimageindex": false,
        "noarchive": false,
        "nosnippet": false,
        "open_graph_title": null,
        "open_graph_title_rendered": "NBA Draft 2026 winners and losers include Dybantsa, Peterson, Boozer and … fans?",
        "open_graph_description": null,
        "open_graph_description_rendered": "The first round of the draft was a remarkably on-the-rails affair. -",
        "twitter_title": null,
        "twitter_title_rendered": null,
        "twitter_description": null,
        "twitter_description_rendered": null,
        "schema_page_type": null,
        "schema_article_type": null,
        "seo_score": "ok",
        "readability_score": "ok",
        "inclusive_language_score": "na"
    }
]
  • Notice that the _rendered attributes are the ones that are output in the frontend, with default settings and replacement variables expanded
  • Repeat the test with multiple combinations of settings and confirm that the result is what you expect each time (empty SEO titles or custom, good SEO scores or no, cornerstone content or not, twitter titles same with OG ones or not, etc.)
  • Repeat the test with a post ID, title or permalink that should yield no posts and confirm that you get a yoast_seo_post_not_found error
  • Repeat the test with a user with no capabilities of editing that post and confirm that you get a rest_ability_cannot_execute error
  • Using our setting SEO data ability:
    • Run a POST request to /wp-json/wp-abilities/v1/abilities/yoast-seo/update-post-seo-data/run
      • you need to add the following body: {"input":{"post_id":<POST_ID>,"seo_title":"new SEO title"}}
      • or (similar to the above) use the post's permalink to find it (but not the title), by adding this body: {"input":{"permalink":<POST_PERMALINK>,"seo_title":"new SEO title"}}
  • Check the response that includes the new SEO data for that post and confirm you see the updated seo title
  • Check the frontend of the post and confirm that the seo title has changed
  • Repeat the test for the other SEO data you can update
  • Repeat the test using invalid SEO data, for example use a schema page type that doesn't exist. eg. {"permalink":<POST_PERMALINK>,"schema_page_type":"FooPage"}}. Confirm that you get an error.
  • Repeat the test with a post ID or permalink that should yield no posts and confirm that you get a yoast_seo_post_not_found error
  • Repeat the test with a user with no capabilities of editing that post and confirm that you get a rest_ability_cannot_execute error
Using an AI agent * Follow [the documentation on how to test WP Abilities](https://yoast.atlassian.net/wiki/spaces/PLUGIN/pages/4975198209/Set+up+an+MCP+server+to+test+WP+Abilities) *
Test the post lookup via titles

Using REST API

  • We want to test looking up posts via titles, so I'll shar esome examples below:
  • Run the following WP CLI commands to create the posts we want:
wp post create --post_status=publish --post_title="Hiking Boots Review"
wp post create --post_status=publish --post_title="Best Hiking Boots 2026"
wp post create --post_status=publish --post_title="Trail Running Guide"
  • Use POSTMAN to GET //wp-json/wp-abilities/v1/abilities/yoast-seo/get-post-seo-data/run?input[title]=<KEYWORDS>, each time with a different title as :
    • input[title]=hiking boots should yield the 2 first posts ("Hiking Boots Review" and "Best Hiking Boots 2026")
    • input[title]=hiking boots, trail should yield all posts ("Hiking Boots Review" and "Best Hiking Boots 2026" and "Trail Running Guide")
    • input[title]=boots hiking should yield no posts ([])
  • Now create 9 more posts that have Hiking Boots in their title to test the pagination
wp post create --post_status=publish --post_title="Hiking Boots 1"
wp post create --post_status=publish --post_title="Hiking Boots 2"
wp post create --post_status=publish --post_title="Hiking Boots 3"
...
...
wp post create --post_status=publish --post_title="Hiking Boots 9"
  • Use POSTMAN to GET //wp-json/wp-abilities/v1/abilities/yoast-seo/get-post-seo-data/run?input[title]=<KEYWORDS>&input[page]=<NUMBER_OF_PAGINATION>, each time with a different title as :
    • input[title]=hiking boots and input[page]=1 should yield the 10 most recently modified posts
    • input[title]=hiking boots and input[page]=2 should yield the next 10 most recently modified posts (if you followed the above instructions closely, this will be a single post with the "Hiking Boots Review" title

Using AI Agent

  • In an incognito window always, so that each prompt doesn't affect the other
  • After creating the above posts, let's ask the agent: "In the site, want the SEO data of posts that are about hiking boots"
  • Check in the details of the Agent's thinking what it does
    • In my test, it first "found 10 results on the first page" and then it "check if there's more content on page 2"
    • Eventually, it yielded SEO data for all 11 posts
  • In a new incognito window, ask it, "I want the SEO data of the oldest post that's about Hiking boots
    • In my tests, it said "I've got the first page of results showing 10 hiking boots posts ordered by most recent modification, and I'm checking whether there's a second page to make sure I haven't missed any entries"
    • It then asked the second pagination so it was able to find the oldest post (the Hiking Boots Review) as it should.

Relevant test scenarios

  • Changes should be tested with the browser console open
  • Changes should be tested on different posts/pages/taxonomies/custom post types/custom taxonomies
  • Changes should be tested on different editors (Default Block/Gutenberg/Classic/Elementor/other)
  • Changes should be tested on different browsers
  • Changes should be tested on multisite

Test instructions for QA when the code is in the RC

  • QA should use the same steps as above.

Impact check

This PR affects the following parts of the plugin, which may require extra testing:

Other environments

  • This PR also affects Shopify. I have added a changelog entry starting with [shopify-seo], added test instructions for Shopify and attached the Shopify label to this PR.
  • This PR also affects Yoast SEO for Google Docs. I have added a changelog entry starting with [yoast-doc-extension], added test instructions for Yoast SEO for Google Docs and attached the Google Docs Add-on label to this PR.

Documentation

  • I have written documentation for this change. For example, comments in the Relevant technical choices, comments in the code, documentation on Confluence / shared Google Drive / Yoast developer portal, or other.

Quality assurance

  • I have tested this code to the best of my abilities.
  • During testing, I had activated all plugins that Yoast SEO provides integrations for.
  • I have added unit tests to verify the code works as intended.
  • If any part of the code is behind a feature flag, my test instructions also cover cases where the feature flag is switched off.
  • I have written this PR in accordance with my team's definition of done.
  • I have checked that the base branch is correctly set.
  • I have run grunt build:images and committed the results, if my PR introduces or edits images or SVGs.

Innovation

  • No innovation project is applicable for this PR.
  • This PR falls under an innovation project. I have attached the innovation label.
  • I have added my hours to the WBSO document.

Fixes #

@leonidasmi leonidasmi added the changelog: enhancement Needs to be included in the 'Enhancements' category in the changelog label Jun 18, 2026
@coveralls

coveralls commented Jun 26, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 0

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage decreased (-0.2%) to 53.547%

Details

  • Coverage decreased (-0.2%) from the base build.
  • Patch coverage: 143 uncovered changes across 3 files (249 of 392 lines covered, 63.52%).
  • 413 coverage regressions across 19 files.

Uncovered Changes

File Changed Covered %
src/abilities/user-interface/abilities-integration.php 173 48 27.75%
src/abilities/application/post-seo-field-map.php 103 89 86.41%
src/abilities/application/post-identifier-resolver.php 41 37 90.24%
Total (7 files) 392 249 63.52%

Coverage Regressions

413 previously-covered lines in 19 files lost coverage.

Top 10 Files by Coverage Loss Lines Losing Coverage Coverage
src/myyoast-client/user-interface/auth-command.php 84 0.0%
src/myyoast-client/infrastructure/registration/client-registration.php 84 55.56%
src/myyoast-client/infrastructure/http/http-client.php 52 40.4%
src/myyoast-client/application/authorization-code-handler.php 31 70.09%
src/myyoast-client/application/myyoast-client.php 24 78.15%
src/ai-consent/application/consent-handler.php 23 0.0%
src/ai/http-request/domain/request.php 21 0.0%
src/ai-consent/user-interface/consent-route.php 20 0.0%
src/ai-http-request/infrastructure/api-client.php 15 0.0%
src/ai-http-request/domain/request.php 14 0.0%

Coverage Stats

Coverage Status
Relevant Lines: 68737
Covered Lines: 36609
Line Coverage: 53.26%
Relevant Branches: 16664
Covered Branches: 9121
Branch Coverage: 54.73%
Branches in Coverage %: Yes
Coverage Strength: 43781.14 hits per line

💛 - Coveralls

@leonidasmi leonidasmi force-pushed the add/read-write-post-abilities branch from 57d78fa to 824cc9a Compare June 26, 2026 13:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog: enhancement Needs to be included in the 'Enhancements' category in the changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants