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