Skip to content

v0.9.0

Choose a tag to compare

@github-actions github-actions released this 03 Sep 22:16
· 5 commits to main since this release
244a58f

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_token a write returns, not the one you first read. The token was blind to most of what a write changes. It hashed only post_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 the 409 it should always have had.
  • post_modified_gmt could 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 abilitiessearch-redirects, create-redirect, update-redirect, delete-redirect, behind the off-by-default Redirect abilities switch and the wpcb_manage_redirects capability. 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, because url_to_postid() answers 0 there. Fixed and covered by a regression test.
  • New: permalink changesupdate-permalink changes 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 into slug-2 the 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-type Change permalink policy.
  • New: media writesupdate-featured-image assigns or removes a featured image, create-media imports one image from a remote URL, and update-media edits 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-media requires the new wpcb_upload_media capability plus native upload_files, and is deliberately not covered by wpcb_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. An idempotency_key is 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-image refuses 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-content results 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-seo reported "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-schema now 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-id now returns a version_token. It is the only read that issues one for an attachment, so without it update-media would have had no token to submit.
  • validation.context_resolved on 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 accompany valid: 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