Import core's prefixed PSR EventDispatcher in the vendored EmbeddingBuilder - #1005
Conversation
…dingBuilder WordPress core scopes the PHP AI Client's PSR dependencies under `WordPress\AiClientDependencies\`, so the unprefixed names do not exist under a WordPress bootstrap. The vendored `EmbeddingBuilder` imports `Psr\EventDispatcher\EventDispatcherInterface` unprefixed, which resolves to nothing at runtime. It has stayed latent because the symbol appears only as a nullable typed property and constructor parameter, and PHP does not resolve such a type while the value is null. The first caller to pass a real dispatcher would have hit a TypeError instead, since core's prefixed interface does not satisfy the unprefixed name — a failure at the moment the feature was first used rather than when it was introduced. This is a vendoring adaptation, not an upstream defect: upstream installs its PSR packages through Composer, where the bare name is correct, so there is nothing to send upstream. Nothing guarded the vendored tree against this, so a re-vendor could reintroduce it silently. Adds a test asserting no vendored file imports an unprefixed `Nyholm\` or `Psr\` symbol. It matches every `Psr\` namespace rather than `Psr\Http\` alone, because core prefixes `Psr\EventDispatcher\` and `Psr\SimpleCache\` the same way. Reverting the import turns it red. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BRSJEyQyYp3XTeum8fiq3L
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
✅ WordPress Plugin Check Report
📊 ReportAll checks passed! No errors or warnings found. 🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #1005 +/- ##
==========================================
Coverage 75.55% 75.55%
Complexity 3381 3381
==========================================
Files 138 138
Lines 13081 13081
==========================================
Hits 9883 9883
Misses 3198 3198
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
dkotter
left a comment
There was a problem hiding this comment.
Changes look correct and test well.
For future reference, we do have a specific PR template we want people to use and it doesn't appear this one uses that (seems to just be AI generated copy). That template is helpful to follow as it includes things like the changelog entry, actual test steps (not just automated tests) and AI usage.
The vendored
EmbeddingBuilderimports an unprefixed PSR interface that does not exist under a WordPress bootstrap.The bug
WordPress core scopes the PHP AI Client's PSR dependencies under
WordPress\AiClientDependencies\. Core ships the interface atwp-includes/php-ai-client/third-party/Psr/EventDispatcher/EventDispatcherInterface.php, declaringnamespace WordPress\AiClientDependencies\Psr\EventDispatcher. The vendored file imports the bare name instead:Why it has not blown up yet. The symbol appears only as a nullable typed property and constructor parameter:
PHP does not resolve a nullable type while the value is null, and nothing currently passes a dispatcher. The first caller to pass a real one gets a
TypeError— core's prefixed interface does not satisfy the unprefixed name. The failure lands when the feature is first used, not when it was introduced, which is the worst time to find it.Not an upstream change
This is a vendoring adaptation, not a defect in php-ai-client. Upstream installs its PSR packages through Composer, where the bare name is correct — prefixing there would break their builds to fix a problem only WordPress has. There is nothing to send upstream.
The missing guard
Nothing asserted this property, so a future re-vendor could reintroduce it silently. This adds
SDK_OverlayTest::test_vendored_files_use_the_prefixed_psr_dependencies(), which scans the vendored tree and fails on any unprefixedNyholm\orPsr\import.It deliberately matches every
Psr\namespace rather thanPsr\Http\alone: core prefixesPsr\EventDispatcher\andPsr\SimpleCache\the same way, and a PSR-7-only pattern is narrower than the hazard it exists to prevent — which is precisely how this import went unnoticed.Testing
npm run test:php— 1571 tests, 4487 assertions, 39 skipped, 0 failures.composer run-script lint— clean.vendor/bin/phpstan analyse --memory-limit=3G(level 8) — no errors.The guard was verified load-bearing rather than assumed: reverting the import to the bare name fails the new test, naming the offending file. Restored, it passes across all vendored files.
Relationship to #1004
These same changes are also present in #1004 (AI Workspace), where the guard is needed for the streaming files that PR vendors. They are byte-identical, so whichever merges first makes the other a no-op. This PR exists so the fix is not blocked behind that much larger, still-draft branch.
AI assistance: Yes. Tool(s): Claude Code (Opus 5). Used for: diagnosis, the fix, the guard test, and this description.
🤖 Generated with Claude Code
https://claude.ai/code/session_01BRSJEyQyYp3XTeum8fiq3L