Releases: IsuDevelopment/wp-content-bridge
Release list
v0.10.0
Answers which redirect is missing, and stops a renamed page's old URL serving stale content.
Runtime verification: this release originally shipped without an inventory run, deliberately, and said so. The run has since been completed: 26/26 PHP verifiers and all three shell verifiers green on 2026-09-04 (commit 9c69b52). error-statistics-verification.php ran for the first time at any version and reached its measured branch against a live Redirection 5.9.0 404 log, rather than only its refusal branch.
The only code difference between this artifact and the verified commit is one whitespace character, so the artifact below is the verified build and there is no 0.10.1.
Changelog
- New:
get-404-statisticsanswers which redirect is missing, which creating a redirect cannot. It reports the paths that returned 404 and how often, read from the Redirection plugin's own log, behind the new off-by-default404 statistics abilityswitch and the newwpcb_read_error_statisticscapability. - Aggregate only, by construction. A result carries the requested path and a hit count and nothing else. Visitor IP, user agent, referrer, and request data are never read into the plugin, never stored, never returned, and never projected to an MCP client — and no parameter exists that could include them, so no retention or redaction obligation attaches to this plugin.
- "Nothing collects this", "the log is switched off", "you may not read it", and "no 404s happened" are four different answers and stay four different answers. Yoast SEO Premium collects no 404 data at all, so a Yoast-only site reports the data as unavailable rather than reporting zero — which would read as a healthy site. A disabled log names the Redirection setting responsible. Every result also reports the retention window it covers, and says so explicitly when retention cut a requested range short, because pruned rows would otherwise look like 404s that stopped happening.
- Reading statistics is deliberately not
wpcb_manage_redirects. Redirection separates the two in its own permission model, so "diagnose the site without authority to change its routing" is a grant you can actually express. Because the log is read directly, Redirection's own permission for it still applies — by default that means Administrator, widened through Redirection's own filter if a site wants an integration principal to read it. Nothing here enables, prunes, or resets any log. get-diagnosticsnow reports redirect providers, asredirects.enabledplus aredirects.providerslist with adetectedflag each — and it reports them whether or not the redirect abilities are switched on. Missing redirect abilities have two very different causes, the switch being off and no provider being installed, and they need opposite fixes. Diagnostics schema version moves to 1.2; the addition is backward-compatible.update-permalinknow invalidates the old URL's cache and reports what it did. A page-cache entry for the old URL is keyed by URL, so the post-scoped invalidation every write already performs cannot reach it, and it keeps serving the old page — the rename appears not to have taken effect. The write now notifies a bounded set, exactly the old and new URL, through public single-URL purge hooks and a newwp_content_bridge_purge_urlsaction for sites to bind. The result reports which channels were notified and flagsdelegatedwhen the actual purge depends on site-level glue; it never claims a purge it cannot observe.- Fixed:
wpcb_upload_media, added in 0.9.0, could not be granted through the settings screen and survived uninstall. It was missing from the capability list the screen renders and from the uninstall cleanup. Both now include it. - The database schema version moves to 14, so an active install grants the new capability only when the plugin is reactivated — until then the statistics ability refuses, which is the correct failure direction.
- Adds
tests/Integration/error-statistics-verification.php, a read-only runtime fixture for the statistics port. It writes no log row, changes no Redirection setting, and prunes nothing.
Install: download wp-content-bridge.zip below and install it as a plugin, or let the built-in updater offer it.
Full Changelog: v0.9.0...v0.10.0
v0.9.0
Media writes, permalinks, and the completed redirect lifecycle.
Action required if you chain writes: use the version_token a write returns, not the one you first read. The token now covers meta, the featured image, attachment edits and the slug, so a token taken before one write is correctly rejected by the next. Clients that already re-read are unaffected.
Published retroactively on 2026-09-03, minutes after v0.8.4, having been committed the same day.
Runtime verification: 25/25 verifiers green on WordPress 7.1 (2026-09-03). That run found three defects before release, all fixed in this tag - see the changelog.
Changelog
- Action required if you chain writes: use the
version_tokena write returns, not the one you first read. The token was blind to most of what a write changes. It hashed onlypost_modified_gmt, the title, the content and the status, so an SEO write, a Custom or Service Schema write, a featured-image change, an attachment edit, and a slug change all left it byte-identical after succeeding. Two agents could read the same token, both write, and the second would silently overwrite the first with no conflict raised — the one thing the token exists to prevent. It now covers the post's meta and its other mutable columns. Nothing changes for a caller that already re-read before each write; a caller that reused a stale token was relying on a defect and now gets the409it should always have had. post_modified_gmtcould not have covered this on its own: it has one-second resolution, so a second edit inside the same second leaves it unchanged. That is why a slug change was accepted against a stale token, and it failed intermittently rather than always.- New: redirect abilities —
search-redirects,create-redirect,update-redirect,delete-redirect, behind the off-by-defaultRedirect abilitiesswitch and thewpcb_manage_redirectscapability. They work through one provider-neutral port over Yoast SEO Premium's Redirect Manager and the Redirection plugin, requiring each provider's own native permission in addition to the bridge's. - Reads span every available provider and writes name their target explicitly; nothing is ever dual-written or silently substituted into a different engine. A site running both plugins has two live redirect engines serving the same paths, so every result names which provider holds a path, and collision, chain, and loop checks are cross-provider. Verified on a live site with both engines active.
- Reserved paths are refused, including this plugin's own
/llms.txt, and a redirect cannot shadow live content. That last check had a defect during development: a redirect on the site root/was accepted, becauseurl_to_postid()answers0there. Fixed and covered by a regression test. - New: permalink changes —
update-permalinkchanges one post's slug and returns the previous URL beside the new one, so a redirect can be created from it. A slug already in use is refused, not quietly turned intoslug-2the way WordPress would; a slug that normalizes to nothing is refused rather than stored empty, which would make WordPress regenerate one from the title. It cannot change the site-wide permalink structure. Gated by a new per-typeChange permalinkpolicy. - New: media writes —
update-featured-imageassigns or removes a featured image,create-mediaimports one image from a remote URL, andupdate-mediaedits an existing attachment's title, alternative text, caption, and description. Each is behind its own off-by-default switch: assigning an image the site already holds, importing a file from the internet, and editing text about a file are separate grants. create-mediarequires the newwpcb_upload_mediacapability plus nativeupload_files, and is deliberately not covered bywpcb_edit_content: a principal that may edit text is not thereby one that may put files on the server. The URL is screened by WordPress's own host allowlist, which refuses loopback, private, link-local and cloud-metadata addresses and re-checks every redirect target. The stored file type is decided by the downloaded bytes, not the URL or its extension, and only JPEG, PNG, GIF, WebP and AVIF are accepted — never SVG. Anidempotency_keyis required so a retried call returns the same attachment instead of importing a second copy. It creates the attachment only; it never attaches it to a post.update-featured-imagerefuses any attachment that is not an image or that the caller cannot read. WordPress itself accepts any attachment ID as a thumbnail — a PDF, or a private upload — and themes then render it in a public image slot.- Fixed:
search-contentresults had no stable order. Sorting by date, modification time, or title used no tie-break, so rows sharing that value came back in whatever order the database chose. Two posts with the same modification time is what every bulk import produces. On a single read that was untidy; on a paginated read it was a correctness bug, because a row could appear on two pages or on none. Results are now ordered deterministically.search-content,get-editorial-context, and llms.txt source selection all read through the same path and are all fixed. - Fixed:
get-url-seoreported "rendered schema unavailable" without saying why. The reader answered five different failures — a refused request, a non-200 response, an oversize page, a cross-origin URL, and a page that genuinely emits no JSON-LD — with the same empty result. A blocked loopback request is a host fault to fix; a page with no JSON-LD is a correct answer, and self-requests are exactly what a firewall or edge proxy blocks. The warning now names the cause. get-custom-schemanow returns the post it describes: title, slug, permalink, status, publication and modification dates, and featured-image identity. These are the fields a JSON-LD document is built from, and their absence meant authoring a schema for one page needed a separate content read. Measured at 14 ms and under a kilobyte for the whole response.get-media-by-idnow returns aversion_token. It is the only read that issues one for an attachment, so without itupdate-mediawould have had no token to submit.validation.context_resolvedon the Custom Schema abilities is documented as always false and not a failure signal — it reports that validation is source-level only, and it can accompanyvalid: true.- Three new off-by-default options (
Redirect abilities,Media writes,Import images from a URL), one new capability (wpcb_upload_media), and two new per-type policies (Set featured image,Change permalink). The database schema version moves to 13, and an active install grants the new capability only when the plugin is reactivated — until then abilities requiring it refuse, which is the correct failure direction. - Adds
tests/Integration/ability-timing-probe.php, a read-only diagnostic that times each read ability in-process so a slow transport can be told apart from slow PHP. It asserts nothing and changes nothing, so it is safe to run on a production install.
Install: download wp-content-bridge.zip below and install it as a plugin, or let the built-in updater offer it.
Full Changelog: v0.8.4...v0.9.0
v0.8.4
WordPress 7.1 Abilities API adoption. Minimum WordPress is now 7.1 and minimum PHP 8.2.
Two changes are visible to existing clients and both are listed below: domain rejections now answer 4xx instead of the HTTP 500 every one of them used to return (public error codes unchanged), and update-llms-txt is now annotated destructive.
Published retroactively on 2026-09-03. The release was committed on 2026-09-01 and tagged two days later, so this tag's date is later than the work it contains.
Runtime verification: 24/24 verifiers green on WordPress 7.1 (2026-09-01).
Changelog
- WordPress 7.1 is now the minimum supported version (ADR 0027). The plugin declares it and contains no 6.x compatibility branches. Nothing here is optional on an older release — the exposure flag, the lifecycle hooks, and the filtering used below all arrived in 7.1.
- Fixed: every rejection answered HTTP 500. No ability error carried a status, so a missing post, a refused capability, and an oversized payload were all indistinguishable from a server fault over REST — agent clients retried them and monitoring read them as outages. Domain rejections now answer the status they always meant: 400 for invalid input, 403 for a refused capability, 404 for content that is missing or not visible to the caller, 409 for a concurrency or state conflict, 413 for an over-limit payload, 501 for an unavailable provider, and 500 only for an actual internal fault. Public error codes are unchanged, so any client matching on
codekeeps working; only the status differs. - Missing and not-visible deliberately share 404. Which of the two it was is not disclosed, so status codes cannot be used to enumerate content a caller may not read.
wp-content-bridge/update-llms-txtis now annotateddestructive(ADR 0028), because its input is a complete configuration that replaces the stored one — a caller omitting a field loses it. Its HTTP method is unchanged: it remains non-idempotent, so it is still POST, not DELETE. No other annotation changed; the other thirty were already correct under the definition this release finally writes down.- Abilities now declare 7.1's unified
publicexposure flag alongside the explicitshow_in_restthey already carried. Registration metadata is built in one place, which removed thirteen near-identical per-class helpers — two of which took different single booleans under the same name. - Added an off-by-default invocation telemetry diagnostic mode (ADR 0029). Enabled, it records the last 200 invocation attempts — ability name, principal, channel, outcome, timestamp, and nothing else — including the permission denials that previously left no trace anywhere. It never touches the audit table, never stores ability input, and writes once per request. It is a diagnostic, not an audit record: the hook fires before validation and authorization, so an entry proves an attempt was made, never that anything happened.
get-diagnosticsreports the site's minimum WordPress version and which Abilities API features it actually detected at runtime, rather than assuming them from a version number.
Install: download wp-content-bridge.zip below and install it as a plugin, or let the built-in updater offer it.
Full Changelog: v0.8.3...v0.8.4