Connection: Add the protected_owner option and its storage plumbing - #51254
Conversation
Registers a protected_owner option that will anchor a connection owner whose identity is locked and non-transferable, so ownership-bound consumers can bind to the real owner rather than to whoever connected first. The anchor joins the compact option group next to master_user, which is what lets Sync propagate it without further work. It is added to the external storage allowlist for the APD seam, and to the unsafe-options list so the reset tooling cannot wipe it and silently unlock ownership on a site whose connection survived. Disconnecting drops it: WordPress.com owns the record, and the anchor is re-established when the owner reconnects. Nothing reads the option yet, so behavior is unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.
Interested in more tips and information?
|
|
Thank you for your PR! When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:
This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖 Follow this PR Review Process:
If you have questions about anything, reach out in #jetpack-developers for guidance! |
Code Coverage SummaryCoverage changed in 2 files.
|
There was a problem hiding this comment.
Pull request overview
Adds the initial “storage plumbing” for a new protected_owner connection option in the Connection package, establishing where the state will live and how it’s persisted/cleared, without introducing any behavioral change yet.
Changes:
- Register
protected_owneras a compactJetpack_Optionsoption and treat it as connection-critical (excluded from reset lists). - Allow
protected_ownerto be served via the external-storage provider seam and ensure it’s cleared on disconnect. - Add a dedicated PHPUnit test suite validating compact storage, deletion, reset behavior, and external-storage routing (including a negative control).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| projects/packages/connection/tests/php/Jetpack_Options_Test.php | Adds coverage for protected_owner registration/storage/deletion/reset semantics and external-storage routing behavior. |
| projects/packages/connection/src/class-manager.php | Ensures protected_owner is deleted alongside other connection-critical tokens/options during disconnect. |
| projects/packages/connection/legacy/class-jetpack-options.php | Registers protected_owner in the compact option set, adds it to the external storage allowlist, and marks it unsafe for resets. |
| projects/packages/connection/changelog/add-protected-owner-option | Adds a changelogger entry (comment-only) documenting the plumbing change without a user-facing entry body. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Closes CONNECT-406
This is PR1 of the Protected Owner series. It is storage plumbing only — nothing reads the option yet, so there is no behavior change.
A Protected Owner is a connection owner whose identity is locked and non-transferable, so ownership-bound consumers bind to the real owner rather than to whoever connected first. It generalizes the ownership lock that today only applies to Atomic sites. This PR just lays down where that state will live.
Proposed changes
protected_owneroption inJetpack_Options, in the compact group next tomaster_user. Compact placement is deliberate: the compact group lives in the singlejetpack_optionsrow, which is already on Sync's options allowlist, so a later PR gets outbound Sync of the anchor for free. The anchor holdswpcom_user_id,email,local_user_id,locked,confirmed_at, andconfirmed_by.$external_storage_allowlistso the APD seam can serve it, alongsideblog_token,id,master_user, anduser_tokens.get_all_jetpack_options(). That list feedsget_options_for_reset(), so without this the reset tooling would wipe the anchor while the connection itself survived — silently unlocking ownership on a site that still has a Protected Owner.Manager::delete_all_connection_tokens(), next to the existingmaster_userdeletion. WordPress.com owns the record; the local anchor is a cache. Dropping it on disconnect stops a site from carrying a lock naming a user who no longer holds a token, and the anchor is re-established from WordPress.com when the owner reconnects.Jetpack_Options_Test.php(no such file existed) covering the round-trip, compact-group registration, storage location, deletion, exclusion from the reset list, and the external-storage allowlist — including a negative control proving a non-allowlisted option is never routed to a provider.Related product discussion/links
Does this pull request change what data or activity we track or use?
No. The option is registered but never written or read by this PR. When later PRs populate it, the anchor will contain the connection owner's WordPress.com user ID and email — both already known to WordPress.com.
Testing instructions
This PR has no UI. Verification is that the option is wired up correctly and that nothing else moved.
jetpack test php packages/connectioncd projects/packages/connection && ./vendor/bin/phpunit-select-config phpunit.#.xml.dist --filter Jetpack_Options_Testwp eval 'var_dump( Jetpack_Options::get_option( "protected_owner" ) );'returnsfalse.wp eval 'Jetpack_Options::update_option( "protected_owner", array( "wpcom_user_id" => 1 ) ); var_dump( get_option( "jetpack_options" )["protected_owner"], get_option( "jetpack_protected_owner" ) );'shows the value inside thejetpack_optionsrow andfalsefor a standalone row.Made with Cursor