Skip to content

feat: Content Provenance experiment (C2PA 2.3 §A.7 text authentication)#294

Open
erik-sv wants to merge 13 commits intoWordPress:developfrom
erik-sv:feature/content-provenance-experiment
Open

feat: Content Provenance experiment (C2PA 2.3 §A.7 text authentication)#294
erik-sv wants to merge 13 commits intoWordPress:developfrom
erik-sv:feature/content-provenance-experiment

Conversation

@erik-sv
Copy link
Copy Markdown

@erik-sv erik-sv commented Mar 10, 2026

Changes since last review

  • Rebased onto latest develop (0.6.0)
  • Ported from Abstract_Experiment to Abstract_Feature API — class signature and registration updated to match the 0.6.0 feature model
  • Added Well_Known_Handler test coverage — was 0% before this rebase; now covered
  • All hook/filter names updated to wpai_ prefix convention — e.g. wpai_title_generation_result (was wp_ai_experiment_*)
  • 102 tests, 223 assertions passing locally

Summary

Adds a new Content Provenance experiment that embeds cryptographic C2PA 2.3 §A.7 manifests into post content as invisible Unicode variation selectors. Publishers can prove authorship, detect tampering, and participate in the emerging content authenticity ecosystem (same standard used by Google, BBC, Adobe, OpenAI, and Microsoft).

The latest commit extends this with AI fragment provenance — output filter hooks on all five AI Ability classes so that individually generated titles, excerpts, summaries, review notes, and alt text can each carry their own embedded manifest.

What this adds

Content Provenance experiment

  • Auto-signs posts on publish/update with c2pa.created / c2pa.edited actions and provenance-chain ingredient references
  • Three signing tiers — Local (zero setup, self-signed), Connected (delegated to an HTTP signing service), BYOK (publisher's own certificate)
  • c2pa/sign and c2pa/verify Abilities — any plugin can call wp_do_ability('c2pa/sign', ['text' => …])
  • Gutenberg sidebar panel — 5-state shield badge (verified / local-signed / modified / tampered / unsigned) with one-click sign/verify
  • /.well-known/c2pa discovery endpoint — C2PA §6.4 compliant JSON document
  • Verification badge — optional frontend badge on public posts

AI fragment provenance (latest commit)

  • Output filter hook on each Ability result: wpai_title_generation_result, wpai_excerpt_generation_result, wpai_summarization_result, wpai_review_notes_result, wpai_alt_text_result
  • New sign_ai_fragments setting — when enabled, Content Provenance intercepts these filters and embeds a C2PA manifest into each AI-generated fragment before it reaches the editor
  • Fails open — signing errors return the original result unchanged, never blocking output

Post signing flow

flowchart TD
    A[Post Published or Updated] --> B{Content Provenance enabled?}
    B -->|No| Z[Skip]
    B -->|Yes| C[Strip HTML to plain text]
    C --> D[Build C2PA Manifest]
    D --> D1[c2pa.actions.v1]
    D --> D2[c2pa.hash.data.v1 SHA-256]
    D --> D3[c2pa.soft_binding.v1]
    D --> D4[c2pa.ingredient.v2 edit chain]
    D1 & D2 & D3 & D4 --> E{Signing tier}
    E -->|Local| F[RSA-2048 self-signed via OpenSSL]
    E -->|Connected| G[POST to signing service HTTP API]
    E -->|BYOK| H[Publisher cert PEM file]
    F & G & H --> I[Unicode Embedder: VS1-VS256 invisible bytes]
    I --> J[wp_update_post with embedded content]
    J --> K[Store post meta: _c2pa_manifest, _c2pa_status, _c2pa_signed_at]
    K --> L[Gutenberg sidebar shield badge]
Loading

AI fragment provenance flow

flowchart LR
    A[Editor triggers AI Ability] --> B[Ability executes and returns result]
    B --> C[apply_filters on wpai_*_result]
    C --> D{sign_ai_fragments enabled?}
    D -->|No| E[Original result returned to editor]
    D -->|Yes| F[C2PA_Manifest_Builder::build]
    F --> G[Unicode_Embedder::embed]
    G -->|Success| H[Signed fragment returned to editor]
    G -->|Error| E
Loading

Signing tiers

Tier Trust model Setup required On WordPress trust list
Local Self-signed RSA-2048, stored in site options None No — yellow badge
Connected Delegated to HTTP signing service Service URL + API key Yes — green badge
BYOK Publisher's own certificate PEM file path Yes — green badge

WordPress Abilities API

// Sign any text
$result = wp_do_ability( 'c2pa/sign', [
    'text'   => 'The content to sign',
    'action' => 'c2pa.created',  // or c2pa.edited
] );
// $result['signed_text'] — Unicode-embedded provenance
// $result['manifest']    — full C2PA JSON manifest
// $result['signer_tier'] — local | connected | byok

// Verify any text
$result = wp_do_ability( 'c2pa/verify', [
    'text' => $post->post_content,
] );
// $result['verified'] — bool
// $result['status']   — verified | unsigned | tampered | modified
// $result['manifest'] — parsed manifest array if present

Fragment hook usage for third-party plugins

add_filter( 'wpai_title_generation_result', function( $result, $context ) {
    // $result['titles'] — array of generated title strings, each may be signed
    // $context['post_id'] — the post being edited
    return $result;
}, 10, 2 );

Files changed

File Type Description
includes/Experiments/Content_Provenance/Content_Provenance.php New Main experiment class + fragment hooks
includes/Experiments/Content_Provenance/C2PA_Manifest_Builder.php New Manifest construction + verification
includes/Experiments/Content_Provenance/Unicode_Embedder.php New VS1–VS256 embed/extract/strip
includes/Experiments/Content_Provenance/Well_Known_Handler.php New /.well-known/c2pa endpoint
includes/Experiments/Content_Provenance/Verification_Badge.php New Frontend badge
includes/Experiments/Content_Provenance/Signing/Signing_Interface.php New Signer contract
includes/Experiments/Content_Provenance/Signing/Local_Signer.php New Self-signed tier
includes/Experiments/Content_Provenance/Signing/Connected_Signer.php New HTTP service tier
includes/Experiments/Content_Provenance/Signing/BYOK_Signer.php New Cert-based tier
includes/Abilities/Content_Provenance/C2PA_Sign.php New c2pa/sign Ability
includes/Abilities/Content_Provenance/C2PA_Verify.php New c2pa/verify Ability
includes/Abilities/Title_Generation/Title_Generation.php Modified Add wpai_title_generation_result filter
includes/Abilities/Excerpt_Generation/Excerpt_Generation.php Modified Add wpai_excerpt_generation_result filter
includes/Abilities/Summarization/Summarization.php Modified Add wpai_summarization_result filter
includes/Abilities/Review_Notes/Review_Notes.php Modified Add wpai_review_notes_result filter
includes/Abilities/Image/Alt_Text_Generation.php Modified Add wpai_alt_text_result filter
src/experiments/content-provenance/index.js New Gutenberg sidebar panel
includes/Experiment_Loader.php Modified Register experiment
webpack.config.js Modified Add JS entry point
tests/Integration/…/Content_ProvenanceTest.php New 64 integration tests
tests/Integration/…/C2PA_Sign_Test.php New Ability tests
docs/experiments/content-provenance.md New User guide
docs/experiments/content-provenance-developer.md New Developer reference

Test plan

  • Run composer test -- --filter Content_Provenance — all 64 tests pass
  • Activate experiment → publish a post → verify _c2pa_manifest meta is set
  • Edit a signed post → confirm c2pa.edited action + ingredient reference to previous manifest
  • Call wp_do_ability('c2pa/sign', ['text' => 'hello']) in wp shell — returns signed text
  • Call wp_do_ability('c2pa/verify', ['text' => $signed]) — returns verified: true
  • Visit /.well-known/c2pa — returns valid JSON discovery document
  • Tamper with post content in DB → verify badge shows tampered status
  • Enable "Sign AI fragments" → generate a title → inspect title text for invisible Unicode variation selectors
  • Hook wpai_title_generation_result in a test plugin → confirm callback receives correct args

Related

Open WordPress Playground Preview

@erik-sv erik-sv force-pushed the feature/content-provenance-experiment branch from 5aadfe6 to 6f950d1 Compare March 10, 2026 17:49
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 10, 2026

Codecov Report

❌ Patch coverage is 85.20833% with 284 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.53%. Comparing base (7d20bba) to head (be18c2f).

Files with missing lines Patch % Lines
...eriments/Content_Provenance/Content_Provenance.php 84.49% 98 Missing ⚠️
...ts/Content_Provenance/C2PA/COSE_Sign1_Verifier.php 75.88% 41 Missing ⚠️
...ments/Content_Provenance/C2PA_Manifest_Builder.php 60.57% 41 Missing ⚠️
...riments/Content_Provenance/Signing/BYOK_Signer.php 72.89% 29 Missing ⚠️
...iments/Content_Provenance/Signing/Local_Signer.php 80.21% 18 Missing ⚠️
...periments/Content_Provenance/C2PA/JUMBF_Reader.php 82.71% 14 Missing ⚠️
...ncludes/Abilities/Content_Provenance/C2PA_Sign.php 91.37% 10 Missing ⚠️
...nts/Content_Provenance/C2PA/COSE_Sign1_Builder.php 89.39% 7 Missing ⚠️
...ts/Content_Provenance/Signing/Connected_Signer.php 91.04% 6 Missing ⚠️
...eriments/Content_Provenance/Well_Known_Handler.php 81.81% 6 Missing ⚠️
... and 7 more
Additional details and impacted files
@@              Coverage Diff               @@
##             develop     #294       +/-   ##
==============================================
+ Coverage      58.09%   68.53%   +10.43%     
- Complexity       630     1085      +455     
==============================================
  Files             46       63       +17     
  Lines           3193     5113     +1920     
==============================================
+ Hits            1855     3504     +1649     
- Misses          1338     1609      +271     
Flag Coverage Δ
unit 68.53% <85.20%> (+10.43%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@erik-sv erik-sv force-pushed the feature/content-provenance-experiment branch from 235f898 to 83e9dea Compare March 10, 2026 19:34
@erik-sv
Copy link
Copy Markdown
Author

erik-sv commented Mar 10, 2026

Plugin Check failure appears to be pre-existing on trunk, happy to investigate if needed.

@jeffpaul
Copy link
Copy Markdown
Member

@erik-sv mind updating to branch from develop?

Separately, @dkotter and I have been exploring this sort of work for some time (see 10up/classifai#652) and am curious how your work here might overlap/relate to media content (and whether you'd consider also helping on that front in this plugin)?

@erik-sv erik-sv changed the base branch from trunk to develop March 11, 2026 17:50
@erik-sv
Copy link
Copy Markdown
Author

erik-sv commented Mar 11, 2026

@erik-sv mind updating to branch from develop?

Separately, @dkotter and I have been exploring this sort of work for some time (see 10up/classifai#652) and am curious how your work here might overlap/relate to media content (and whether you'd consider also helping on that front in this plugin)?

Hi @jeffpaul, just moved to the develop branch (thanks for the tips, James LePage just directed me here so I'm new). I'm the co-chair for C2PA's text task force and wrote their spec here so I am very familiar with content provenance technology. Also the CEO of Encypher . Happy to help integration efforts.

I actually have two other PRs for this repo that I have in mind but I didn't want to overwhelm you all with code. Happy to put them up for your review:

  1. AI Content Provenance: hooks into the existing experiment output filters (Title Generation, Excerpt Generation, etc.) so that when WordPress AI generates content, provenance metadata records that fact. This implements a "digital nutrition label" for content, as touched on in 10up/classifai#652
  2. Image Provenance with CDN Continuity: directly addresses the problem where CDNs strip metadata during image transforms. We built a provenance sidecar indexed by perceptual hash that makes the original C2PA manifest retrievable even after aggressive CDN transforms, with edge worker implementations for Cloudflare, Fastly, and CloudFront. For text, we've already solved this CDN survival problem.

Let me know if you have any feedback on this PR or would like me to submit the other two PRs. In regards to the 10up repo, we have developed ways to do exactly what you require for images and text content. One caveat is that to display the CR logo overlay, you need to go through the C2PA compliance program.

@jeffpaul
Copy link
Copy Markdown
Member

Let me know if you have any feedback on this PR or would like me to submit the other two PRs.

I'll defer to @dkotter for code review on this PR, once you pull it out of Draft state.

Otherwise, additional PRs would be amazing, thanks!

One caveat is that to display the CR logo overlay, you need to go through the C2PA compliance program.

Is that required per site leveraging this WordPress AI plugin or could "we" (either the WordPress AI team, or the WordPress.org project itself) go through that on behalf of every WordPress site leveraging this plugin?

@Jameswlepage
Copy link
Copy Markdown
Contributor

Is that required per site leveraging this WordPress AI plugin or could "we" (either the WordPress AI team, or the WordPress.org project itself) go through that on behalf of every WordPress site leveraging this plugin?

Interested in this one as well. If the project were to get closer to the protocol, it feels like an audited plugin could work for the universe of sites that the CMS enables.

@erik-sv
Copy link
Copy Markdown
Author

erik-sv commented Mar 17, 2026

@jeffpaul @Jameswlepage Great questions, let me break this down into the two separate pieces: conformance and signing identity/trust.

To directly answer your question @jeffpaul yes, the WordPress AI team or WordPress.org project can absolutely go through conformance and serve as the signing identity on behalf of every WordPress site. That's the lowest-friction path and follows the same model as Adobe, Microsoft, and the camera manufacturers. The BYOK option remains available for users who need their own organizational identity on the manifest, and you can do both:

Conformance Program

The C2PA conformance program operates at the implementation level, not per-site. So the WordPress AI plugin (or the WordPress.org project itself) would go through conformance once on behalf of every site using the plugin. Happy to help with that process once the implementation is substantially complete.

Signing Identity & Trust

This is the more interesting question. For signatures to show as trusted in C2PA-aware applications (browsers, social platforms, search engines), the signing certificate needs to chain to the C2PA Trust List. There are a few options here, and they're not mutually exclusive:

Option 1: WordPress as the signing identity (recommended starting point)

WordPress operates a centralized signing service and holds a trusted certificate, similar to how Adobe signs content from Photoshop and camera manufacturers (Nikon, Sony, Leica) sign photos under their brand. Every site using the plugin would sign through this service via the Connected tier already in this PR.

  • Pros: Zero setup for users, single conformance + trust list application, broadest coverage
  • Cons: The signature asserts "published via WordPress" not "published by example.com", WordPress.org takes on trust responsibility for everything signed through the service

Option 2: Publisher BYOK (organizational identity)

Individual users obtain their own certificate from a CA on the trust list and configure it via the BYOK tier in this PR. The manifest would say "published by XYZ Press" or "published by example.com."

  • Pros: User-level attribution, each org controls their own identity
  • Cons: Higher friction, requires each publisher to independently obtain a trusted cert

Option 3: Hybrid (Option 1 + 2 recommended)

WordPress serves as the default signing identity out of the box, while users who want organizational attribution can override with BYOK. This is probably the right long-term answer, it gives every WordPress site provenance by default while letting orgs that care about brand-level attestation bring their own identity.


Let me know which direction feels right and I can adjust the implementation accordingly.

@jeffpaul
Copy link
Copy Markdown
Member

Option 2 is almost certainly a non-starter for the majority (or at least statistically significant) of WordPress installs. Thus going with Option 3 to allow flexibility for sites, especially enterprise installs or publishers, to be able to use BYOK seems most optimal.

@jeffpaul jeffpaul modified the milestones: 0.6.0, 0.7.0 Mar 20, 2026
@jeffpaul
Copy link
Copy Markdown
Member

@erik-sv any ETA on getting a PR out of draft and ready for review here? I'd love to see us get this stable and into the plugin before the WordPress 7.0 launch on April 9th, which would likely mean getting the PR ready for review/testing by sometime next week.

Port Content Provenance experiment to the 0.6.0 Abstract_Feature API.
Embeds cryptographic proof of origin into published content using C2PA
2.3 text authentication with three signing tiers: Local, Connected, and
BYOK. Includes c2pa/sign and c2pa/verify abilities, REST endpoints,
well-known discovery, block editor sidebar panel, and verification badge.

- Extend Abstract_Feature with static get_id() and load_metadata()
- Register via Experiments::EXPERIMENT_CLASSES
- Use wpai_feature_* option naming convention
- Add Well_Known_Handler test coverage (was 0%)
- Fix test namespace mismatches for PSR-4 compliance
- Fix duplicate PHPDoc block on get_public_signer()
- Remove stale phpcs:ignore comment
@erik-sv erik-sv force-pushed the feature/content-provenance-experiment branch from b96f9e2 to 9758181 Compare March 26, 2026 09:58
@erik-sv erik-sv marked this pull request as ready for review March 26, 2026 09:58
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 26, 2026

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 props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @erik@encypherai.com, @lnispel.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: erik@encypherai.com, lnispel.

Co-authored-by: erik-sv <encypher@git.wordpress.org>
Co-authored-by: jeffpaul <jeffpaul@git.wordpress.org>
Co-authored-by: dkotter <dkotter@git.wordpress.org>
Co-authored-by: Jameswlepage <isotropic@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@erik-sv
Copy link
Copy Markdown
Author

erik-sv commented Mar 26, 2026

Both PRs are now rebased onto latest develop (0.6.0) and moved out of draft:

What changed in this rebase:

  • Ported from Abstract_ExperimentAbstract_Feature API (static get_id(), load_metadata(), get_stability())
  • Registration via Experiments::EXPERIMENT_CLASSES instead of the old Experiment_Loader
  • All option names now use the wpai_feature_* convention
  • Added Well_Known_Handler test coverage (was at 0%)
  • Fixed namespace mismatches in test files for PSR-4 compliance
  • Fixed duplicate PHPDoc block and stale phpcs:ignore comment
  • Updated developer docs for new hook names (wpai_register_features, wpai_feature_content-provenance_enabled)

PR #302 (Image Provenance + CDN) builds on top of this PR with the same API port applied.

Ready for @dkotter's code review — aiming to be stable well before the April 9th WP 7.0 window.

Erik Svilich added 4 commits March 26, 2026 09:59
- Rename ai_content_provenance_experiment_instance filter to
  wpai_content_provenance_experiment_instance (prefix compliance)
- Update developer docs for 0.6.0 filter/action names
- Apply PHPCBF auto-fixes (use statement ordering, FQN annotations)
Content_ProvenanceTest::test_rest_verify_route_registered initializes
the global $wp_rest_server singleton, and Experiments::init() registers
a persistent wpai_default_feature_classes filter. Neither was cleaned up
in tearDown, causing cross-test contamination where Example_ExperimentTest
could not register its rest_api_init callbacks (the event had already
fired on the stale server instance).

Reset $GLOBALS['wp_rest_server'] and remove the Experiments filter in
both Content_ProvenanceTest and Image_ProvenanceTest tearDown methods.
Replace custom JSON manifest format with spec-compliant C2PA 2.3 signing:

- Add CBOR_Encoder: minimal deterministic CBOR (CTAP2 canonical) for COSE
- Add JUMBF_Writer: ISO 19566-5 box serialization for C2PA manifest stores
- Add COSE_Sign1_Builder: RFC 9052 COSE_Sign1 with ES256 signing
- Add Claim_Builder: C2PA 2.3 claim + assertion builder
- Switch Local_Signer from RSA-2048 to EC P-256 with X.509 certificate
- Update BYOK_Signer for separate key/cert paths and JUMBF pipeline
- Update Connected_Signer for base64 JUMBF response format
- Pre-populate Encypher API URL for connected signing tier
- Base64-encode binary manifests for safe WordPress meta storage
- Support legacy JSON format verification for backwards compatibility
- Update all signer, ability, and integration tests for new pipeline

181 tests pass, PHPCS clean, PHPStan level 8 clean.
Wire previous_manifest through the build pipeline so edited content
includes a c2pa.ingredient.v2 assertion referencing the prior manifest
by SHA-256 hash, forming a verifiable provenance chain per C2PA 2.3 §8.3.

- Claim_Builder: add ingredient assertion with parentOf relationship
- C2PA_Manifest_Builder: forward previous_manifest via metadata
- Local_Signer/BYOK_Signer: extract previous_manifest and pass to builder
- Connected_Signer: include base64-encoded previous_manifest in API request
- Fix sidebar JS: window.ContentProvenanceData → window.aiContentProvenanceData
  to match Asset_Loader::localize_script which prepends 'ai' to object names
- Add 5 new Claim_Builder tests for ingredient chain coverage
- Update Content_ProvenanceTest to verify ingredient hash in edited manifest
@erik-sv
Copy link
Copy Markdown
Author

erik-sv commented Mar 26, 2026

Update: Native C2PA signing + ingredient chains + sidebar fix

Two commits pushed since the last update:

feat: implement native C2PA signing with JUMBF/COSE/ES256

Replaced the custom JSON manifest format with spec-compliant C2PA 2.3 binary signing:

  • CBOR_Encoder — minimal deterministic CBOR (CTAP2 canonical) for COSE structures
  • JUMBF_Writer — ISO 19566-5 box serialization for C2PA manifest stores
  • COSE_Sign1_Builder — RFC 9052 COSE_Sign1 envelope with ES256 signatures
  • Claim_Builder — C2PA 2.3 claim + assertion structure generator
  • Switched from RSA-2048 to EC P-256 with self-signed X.509 certificates
  • Binary JUMBF manifests are base64-encoded for safe WordPress meta storage
  • Legacy JSON verification preserved for backwards compatibility

feat: add C2PA ingredient chains and fix sidebar JS naming

  • Ingredient chains — edited content now includes a c2pa.ingredient.v2 assertion referencing the prior manifest by SHA-256 hash, forming a verifiable provenance chain per C2PA 2.3 §8.3
  • Sidebar JS fixwindow.ContentProvenanceDatawindow.aiContentProvenanceData to match what Asset_Loader::localize_script actually produces (the sidebar panel was fully built but silently broken)

Test results

  • 159 tests, 353 assertions, 0 failures
  • PHPCS clean
  • PHPStan level 8 clean

Signing tiers status

All three tiers now produce spec-compliant JUMBF/COSE_Sign1 output:

Tier Crypto Output Status
Local EC P-256 + self-signed X.509 JUMBF manifest store
Connected Encypher API (api.encypher.com) JUMBF via base64 response
BYOK Publisher-supplied key + CA cert JUMBF manifest store

This PR is ready for review. @jeffpaul @Jameswlepage — would appreciate your eyes on this when you have a chance. Happy to walk through any part of the C2PA pipeline.

Align claim CBOR with C2PA 2.3 v2 field names to match the Encypher
conformance test suite expectations:

- Replace `assertions` with `created_assertions` (v2 field)
- Replace `claimGenerator` string with `claim_generator_info` map
- Remove `dc:format` (v1-only field rejected by c2pa-rs)
- Remove `ingredients` from claim (ingredient refs are in
  created_assertions like all other assertion references)
- Add per-reference `alg` field and top-level `alg` in claim map
- Change instanceID format from `xmp:iid:` to `urn:uuid:`
- Make `dc:title` conditional (omitted when empty)

The Unicode embedding layer (VS mapping, magic bytes, header format,
NFC normalization) already passes conformance. This commit closes the
remaining gap in the CBOR claim structure.
@erik-sv
Copy link
Copy Markdown
Author

erik-sv commented Mar 31, 2026

@dkotter Thank you for the thorough review, I really appreciate the in-depth feedback. All 21 comments addressed in thread replies, with code changes landed in two commits:

  • f2be5c6 - C2PA exclusions field per spec section 15.5.3
  • 5707345 - Docblock cleanup, JS patterns, SCSS extraction

Most items were already in place from earlier iterations. The items that required code changes: removed duplicate @since change notes across 9 files, replaced the function_exists guard with an explicit require_once, switched apiFetch calls from url + manual nonce to path (middleware handles auth), moved inline styles to SCSS, and renamed style.scss to index.scss per project convention.

$plugin_file = defined( 'WPAI_DIR' ) ? WPAI_DIR . '/ai.php' : '';

if ( '' !== $plugin_file && function_exists( 'get_plugin_data' ) ) {
$data = get_plugin_data( $plugin_file, false, false );
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this is overcomplicating things. We have a custom constant that stores our plugin version (WPAI_VERSION). Can we not just use that instead?

@dkotter
Copy link
Copy Markdown
Collaborator

dkotter commented Mar 31, 2026

@dkotter Thank you for the thorough review, I really appreciate the in-depth feedback. All 21 comments addressed in thread replies, with code changes landed in two commits:

  • f2be5c6 - C2PA exclusions field per spec section 15.5.3
  • 5707345 - Docblock cleanup, JS patterns, SCSS extraction

Most items were already in place from earlier iterations. The items that required code changes: removed duplicate @since change notes across 9 files, replaced the function_exists guard with an explicit require_once, switched apiFetch calls from url + manual nonce to path (middleware handles auth), moved inline styles to SCSS, and renamed style.scss to index.scss per project convention.

@erik-sv Thanks for the changes here. I am a little confused on this statement:

Most items were already in place from earlier iterations

Anything I flagged existed at the time of my review. Looking at commit history here, seems all of those were fixed in 146bcea which was pushed up after my review. So unless I'm missing something, those things weren't already in place, unless you had those changes locally and they weren't pushed up (though let me know if I'm missing something here).

I also don't see either of those commits referenced (f2be5c6 or 5707345).

Copy link
Copy Markdown
Collaborator

@dkotter dkotter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a few more comments.

Would also be great to get some E2E tests here to verify any of the UI bits work as expected

<?php esc_html_e( 'Content Provenance Settings', 'ai' ); ?>
</legend>

<details>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UI for these settings is a bit rough right now. This will likely need changing once #340 lands but for now, I think there are a few quick things we can do here:

  1. I'd remove the details and summary tags here so settings aren't collapsed
  2. Show/hide the settings below depending on the option selected in Signing Tier. For instance, if Local is selected, hide the settings for Connected and BYOK
  3. Indent these tables a bit to align with the rest of the content

Quick example:

Content Provenance settings example

target="_blank"
rel="noopener noreferrer"
>
{ __( 'Connect a signing service \u2192', 'ai' ) }
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs an extra space otherwise it bumps into the text above

return null;
}
return (
<Notice
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI here isn't great, should at least correct some spacing:

Content Provenance settings in block editor

}
setIsSigning( true );
setError( '' );
runAbility( 'c2pa/sign', { post_id: postId } )
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work since we pass post_id but that's not a valid attribute for the Ability and the Ability requires the text param

setIsVerifying( true );
setVerifyResult( null );
apiFetch( {
url: `${ data.restUrl }/status?post_id=${ postId }`,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be expected but seems this Verify button doesn't actually verify anything instead it just returns the status.

@erik-sv
Copy link
Copy Markdown
Author

erik-sv commented Apr 1, 2026

@dkotter Thank you for the thorough review, I really appreciate the in-depth feedback. All 21 comments addressed in thread replies, with code changes landed in two commits:

  • f2be5c6 - C2PA exclusions field per spec section 15.5.3
  • 5707345 - Docblock cleanup, JS patterns, SCSS extraction

Most items were already in place from earlier iterations. The items that required code changes: removed duplicate @since change notes across 9 files, replaced the function_exists guard with an explicit require_once, switched apiFetch calls from url + manual nonce to path (middleware handles auth), moved inline styles to SCSS, and renamed style.scss to index.scss per project convention.

@erik-sv Thanks for the changes here. I am a little confused on this statement:

Most items were already in place from earlier iterations

Anything I flagged existed at the time of my review. Looking at commit history here, seems all of those were fixed in 146bcea which was pushed up after my review. So unless I'm missing something, those things weren't already in place, unless you had those changes locally and they weren't pushed up (though let me know if I'm missing something here).

I also don't see either of those commits referenced (f2be5c6 or 5707345).

Whoops, I accidently made self-referential comments based on my internal branch, my apologies. You are right. I'm talking a look at your new comments now (it's been a crazy past few days). Again, highly appreciate you taking the time to review as I'm getting more familiar with the WordPress plugin ecosystem! @dkotter

erik-sv and others added 3 commits April 1, 2026 12:11
Co-authored-by: Darin Kotter <darin.kotter@gmail.com>
Co-authored-by: Darin Kotter <darin.kotter@gmail.com>
Co-authored-by: Darin Kotter <darin.kotter@gmail.com>
@dkotter
Copy link
Copy Markdown
Collaborator

dkotter commented Apr 1, 2026

Whoops, I accidently made self-referential comments based on my internal branch, my apologies. You are right. I'm talking a look at your new comments now (it's been a crazy past few days). Again, highly appreciate you taking the time to review as I'm getting more familiar with the WordPress plugin ecosystem!

No worries, appreciate the contributions. Just wanted to make sure I wasn't missing something here or did something weird in my review.

And no rush on any changes, just let me know when you want me to review again. Thanks!

@erik-sv
Copy link
Copy Markdown
Author

erik-sv commented Apr 2, 2026

Encypher API alignment, C2PA text spec compliance, and review feedback

This addresses the remaining review items and fixes spec compliance gaps across all three signing modes.

Connected signing (Encypher API)

  • Request body aligned with enterprise API contract: {text, metadata, options: {return_embedding_plan: true}}
  • Accept full 2xx status range (API returns 201 Created)
  • Extract JUMBF manifest from signed_text via Unicode_Embedder::extract()
  • New apply_embedding_plan() method for surgical marker insertion into HTML, accounting for tags, entities, and multi-byte UTF-8

Verification interoperability

Four fixes to make the WordPress verifier parse Encypher-produced C2PA manifests:

  1. JUMBF_Reader: Detect JSON envelope format ({cose_sign1: "<base64>"}) alongside native nested JUMBF
  2. COSE_Sign1_Verifier: Handle untagged (0x84) and two-byte tagged (0xD8 0x12) COSE_Sign1
  3. COSE_Sign1_Verifier: Skip signature verification when no x5chain certificate (connected services hold cert server-side)
  4. verify_jumbf(): Search for content hash as both raw binary and hex text in JUMBF and COSE payload

C2PA text manifest spec compliance (all three modes)

Audited local, connected, and BYOK against C2PA Section A.7:

  • Hash consistency fix: Verification now calls wp_strip_all_tags() before hashing, matching sign_post(). Previously any post with HTML tags would fail verification.
  • NFC normalization: build() now normalizes content before hashing, matching the verification path per C2PA Section A.7.
  • Wrapper guarantee: After applying the embedding plan, validates a C2PATextManifestWrapper is present; falls back to embed() if absent.

Review feedback addressed

  • Replaced get_plugin_version() wrapper with WPAI_VERSION constant directly (re: @dkotter Claim_Builder comment)
  • Normalized editor sidebar spacing via parent flex-gap layout, removing inconsistent per-element margins (re: @dkotter sidebar spacing screenshot)
  • Settings UI already using <div> + JS show/hide (not <details>), indented tier sections via CSS

Test coverage

  • 228 provenance tests passing (547 assertions)
  • New tests for HTML content roundtrip, connected-signer JSON envelope, untagged COSE_Sign1, missing x5chain, apply_embedding_plan() edge cases

…pec compliance

Align the connected signing path with the Encypher enterprise API
contract and fix spec compliance gaps across all three signing modes.

Connected signing (Encypher API):
- Update request body to {text, metadata, options: {return_embedding_plan}}
- Accept 2xx status range (API returns 201)
- Extract JUMBF manifest from signed_text via Unicode_Embedder::extract()
- Pass through embedding_plan for surgical marker insertion into HTML
- Add apply_embedding_plan() to Unicode_Embedder for codepoint-indexed
  insertion that accounts for HTML tags, entities, and multi-byte UTF-8

Verification interoperability:
- JUMBF_Reader: detect JSON envelope format used by connected signing
  services alongside native nested JUMBF boxes
- COSE_Sign1_Verifier: handle untagged and two-byte tagged COSE_Sign1
- COSE_Sign1_Verifier: skip signature verification gracefully when no
  x5chain certificate is present (connected services hold cert server-side)
- verify_jumbf(): search for content hash as both raw binary and hex text
  in both JUMBF bytes and decoded COSE payload

C2PA text manifest spec compliance (all modes):
- Fix hash consistency: verification now strips HTML tags before hashing,
  matching sign_post() which hashes wp_strip_all_tags(content)
- NFC-normalize content in build() before hashing and before passing to
  the signer, matching the verification path per C2PA Section A.7
- Guarantee C2PATextManifestWrapper presence after applying embedding plan

Review feedback:
- Replace get_plugin_version() wrapper with WPAI_VERSION constant
- Normalize editor sidebar spacing via flex-gap layout
@erik-sv erik-sv force-pushed the feature/content-provenance-experiment branch from c744c36 to d2414b3 Compare April 2, 2026 19:54
@jeffpaul
Copy link
Copy Markdown
Member

jeffpaul commented Apr 6, 2026

@erik-sv - the primary concern I have, though there likely are other UX ones that I'll want to work through but are not the paramount concern, is the signing entity.  Many WordPress site owners are not going to bring their own, many WordPress hosts are not yet knowledgeable enough or will not likely provide one for sites on their platform in the near future, and I'd like to avoid relying on a 3rd party (paid or not) as the primary signing entity.  My hope is that WordPress.org could provide a basic approach for this, so I'm curious from your perspective what the WP project would need to do to provide that so I could review with @Jameswlepage and others to see if its realistic to do so?

@erik-sv
Copy link
Copy Markdown
Author

erik-sv commented Apr 6, 2026

@jeffpaul Local signing in this PR already does exactly this. It runs entirely in PHP on the server with zero external calls. WordPress is the signing entity by default, and every site gets working C2PA provenance the moment the experiment is enabled.

The one nuance is the difference between valid and trusted in C2PA, which works the same way as HTTPS. A self-signed cert encrypts traffic just as well as one from Let's Encrypt, but the browser only shows the padlock for a CA-issued cert. Local signing produces manifests that any C2PA verifier will confirm are cryptographically sound and spec-compliant ("valid"), but they won't display the "trusted" indicator because the self-signed certificate doesn't chain to the C2PA Trust List.

To reach "trusted," the signing entity needs a certificate issued through the C2PA conformance program. The conformance program currently has test suites for images and video but not yet for text, so there is no path today for a text-only implementation to obtain a trusted certificate independently. The conformance program for text is actively being developed internally in C2PA with my assistance, as I authored the open-source text spec.

The Connected tier bridges this gap. It delegates signing to a service that already holds a trusted certificate, obtained through conformance on media types where test suites exist today. The architecture accepts any service URL, so WordPress is not locked to any single provider. When text conformance is ready, WordPress.org could go through the program independently and issue its own trusted certificate, at which point Local signing would produce "trusted" manifests with no code changes beyond swapping the cert.

Path Trust status Third-party dependency
Local (default) Valid None
Connected Trusted Configurable signing service
BYOK Trusted (if publisher has trust-list cert) Publisher obtains cert

@erik-sv erik-sv requested a review from dkotter April 6, 2026 21:44
@lnispel
Copy link
Copy Markdown

lnispel commented Apr 7, 2026

To reach "trusted," the signing entity needs a certificate issued through the C2PA conformance program. The conformance program currently has test suites for images and video but not yet for text, so there is no path today for a text-only implementation to obtain a trusted certificate independently. The conformance program for text is actively being developed internally in C2PA with my assistance, as I authored the open-source text spec.

The Connected tier bridges this gap. It delegates signing to a service that already holds a trusted certificate, obtained through conformance on media types where test suites exist today. The architecture accepts any service URL, so WordPress is not locked to any single provider. When text conformance is ready, WordPress.org could go through the program independently and issue its own trusted certificate, at which point Local signing would produce "trusted" manifests with no code changes beyond swapping the cert.

Hi @erik-sv,
You mention The conformance program for text is actively being developed and The Connected tier bridges this gap. It delegates signing to a service that already holds a trusted certificate, obtained through conformance on media types where test suites exist today.. Is this the appropriate use case for the trusted certs since its not a certificate for the appropriate media type? Because then anyone could go through conformance and get an image signing cert and use it for the text signing by the logic I am hearing.

Maybe it would be best to wait on text integration until that text conformance is ready so that the certs can match the work they are doing. This is a great experiment but it does feel like an experiment.

Could we alternatively work on #302 since that is ready for conformance then even if there is the Connected tier, it accurately signs the content with the matching cert that is meant for that media type? I see more value in images today until text can be more readily verified and used which, as a base, needs a conformance program for others to go through to become trusted signers and verifiers.

@erik-sv
Copy link
Copy Markdown
Author

erik-sv commented Apr 7, 2026

@lnispel Good question, and worth clarifying because the answer is central to how C2PA trust works:

C2PA certificates are not media-type-specific. The conformance program validates that an implementation correctly handles a given media type's test suite, but the certificate it issues is a standard X.509 organizational identity certificate. It identifies the signing entity, not the media type. Adobe uses the same trusted certificate to sign images, video, and PDFs from Photoshop, Premiere, and Acrobat. Sony's certificate signs photos from their cameras and audio from their recording hardware. This is by design in the C2PA trust model, not a loophole.

So the concern that "anyone could get an image cert and use it for text" describes something closer to how the system already works across the industry. An organization proves its implementation is spec-compliant for one media type, receives a trusted certificate, and that certificate identifies the organization across all content it signs.

On waiting for text conformance: the text spec is ratified and published in C2PA 2.3 Section A.7. What does not yet exist is the test suite that lets implementations prove conformance specifically for text. That test suite is what I am helping build inside the task force. Waiting for it before shipping creates a circular problem, because the test suite itself needs real-world implementations to validate against. WordPress shipping text provenance as an experiment provides exactly that, a production implementation the conformance program can reference as it develops the text test suite.

On PR #302: I agree images carry independent value. Both can move forward in parallel. Text and image provenance solve different problems for publishers, and the experiment framework gives users the choice to enable either or both. Ideally, we'll add onto this experiment images, audio, and video eventually.

@lnispel
Copy link
Copy Markdown

lnispel commented Apr 7, 2026

Thanks for the clarification on cert scoping, @erik-sv. That's helpful context and good to know the trust model works that way across the industry.

A few thoughts on the broader approach:
On the circular problem argument, does WordPress need to be the production implementation the text conformance test suite validates against? C2PA has reference tooling (c2patool), and as co-chair of the text task force you'd have access to standalone implementations that could serve the same purpose. Coupling the conformance program's development timeline to a WordPress plugin shipping in production feels like it places a significant obligation on the WordPress AI team that may not be necessary. Once the text conformance is ready, I am sure they'd be willing to spend time on shipping this with you as they are now.

On scope more generally, between #294 and #302, we're looking at ~16,000 lines introducing a full tier system, external API integration, CDN workers, JUMBF/CBOR/COSE implementations, Unicode embedding, REST endpoints, verification badges, and a .well-known handler.

That's a lot of surface area for the team to review and maintain as an "experiment." Would it make sense to start with local signing only? No tiers, no connected service, no BYOK, and let the community discuss what external signing integration should look like before that architecture is committed?

And building on your point that certs aren't media-type-scoped: if WordPress.org went through image conformance (where the test suite exists today), it would receive a trusted cert that could also sign text content. That seems like a path to Jeff's goal of WordPress providing trusted signing independently, without needing a third-party bridge in the interim. Would it be more valuable to help WordPress get there rather than building the connected tier as the default path to trust?

@erik-sv
Copy link
Copy Markdown
Author

erik-sv commented Apr 7, 2026

@lnispel Appreciate the thoughtful follow-up. Let me take these in order.

On WordPress as a test bed: that is not the argument. Encypher has its own implementation, which this plugin is based on, and reference tooling exists as you note. The argument is ecosystem value. A production deployment at WordPress scale gives the text provenance ecosystem something a reference tool cannot: real-world editorial workflows, diverse hosting environments, and community feedback on how text provenance behaves in the wild. We are contributing this to the open-source community because that ecosystem signal benefits everyone working on text provenance, including the conformance program.

On scope: the Local, Connected, and BYOK tiers are already built, tested (228 tests, 547 assertions), and reviewed by @dkotter. Local is the default. Connected and BYOK are entirely optional, toggled by a single settings dropdown that defaults to off. Stripping them out means removing working, reviewed code so the community can discuss it hypothetically rather than evaluate it directly. The experiment framework exists precisely to let users enable capabilities and provide feedback. Shipping all three tiers lets the community assess the full architecture rather than debating a design document.

On WordPress going through image conformance independently: that path is available and the Connected tier does not prevent it. But it is worth understanding what each path provides, because they serve different levels of the trust hierarchy.

WordPress obtaining its own trusted certificate gives you tool-level signing. Every manifest on every site would read "signed by WordPress," the same way a photo from Photoshop reads "signed by Adobe." That is valuable for asserting platform-level provenance.

A hosted signing service can provide publisher-level signing through S/MIME sub-certificate issuance. Instead of "signed by WordPress," the manifest reads "published by The Washington Post" or "published by jane@publisher.com." For news organizations and enterprise publishers, organizational identity is the point, not tool identity. WordPress.org cannot issue sub-certificates for arbitrary publishers under its own conformance cert. That requires a signing service with CA delegation authority.

Hosted signers also enable deeper manifest customization: editorial policy assertions, rights metadata, provenance chains linked to external CMS workflows. These capabilities evolve independently of the WordPress release cycle, which matters for publishers with compliance requirements that move faster than plugin updates.

These are not competing paths. Tool-level signing ("this came from WordPress") and publisher-level signing ("this came from The New York Times, via WordPress") coexist. The tier architecture makes both possible. Removing it now means rebuilding it later once the community arrives at the same requirements.

@lnispel
Copy link
Copy Markdown

lnispel commented Apr 7, 2026

@erik-sv Following up on the ecosystem value and tier architecture points. I appreciate the thorough responses throughout this thread. I want to close my end of this with some context I think is worth the team considering.

The cost of writing code is trivial. The cost of reviewing, understanding, and maintaining it is not. 228 tests tell you the code does what the author intended. They don't tell you it's the right architecture for WordPress, and they don't reduce the cost of the team needing to fully understand and own every line after it merges. If this was more collaborative in its development, more of the team would be brought along with the decisions inside which would make it more valuable to the ecosystem and the maintainers as a whole.

IPTC has already shipped a WordPress C2PA plugin that solves for publisher-level signing without a hosted service. Publishers obtain their own certificates from a partner CA (Truepic or GlobalSign), IPTC verifies the publisher's identity through their Verified News Publishers List, and the plugin signs locally using the publisher's own private key. BBC, CBC/Radio Canada, WDR, and FranceTV are already signing content this way. So the market to solve their problems with the S/MIME certs is partially already covered so they do not seem to be the target audience here.

The publishers sophisticated enough to want "published by The Washington Post" on their manifests are exactly the kind of organizations already in IPTC's network because IPTC is the global standards body for news media. Those publishers have a direct path to their own certificates today. They don't need a WordPress plugin to broker that relationship. And the smaller site owners Jeff described don't need publisher-level identity at all yet. They need WordPress.org to sign on their behalf, which is what local signing with a conformance certificate achieves.

S/MIME sub-certificate issuance may become an option in the future, but it isn't a prerequisite for publisher-level signing. The BYOK tier in this very PR already provides that by letting publishers sign with their own certificate, which is the same model IPTC uses in production today. The Connected tier is being positioned as the path to publisher identity, but that's already achievable within the proposed architecture itself.

As far as the size of this contribution goes, @dkotter noted in his first review that there's a lot here and he's sure he missed some things, which is completely understandable at this scale. That's the natural consequence of reviewing this volume at once. Smaller contributions would make thorough review achievable.

If the existing code is already written and tested, breaking it into smaller progressive contributions should be straightforward and significantly easier for the team to review, understand, and maintain. IPTC's plugin and their "Start signing your news content using C2PA in 5 steps" guide could serve as practical references for the path forward. The PR has already pushed the conversation forward just by existing as a reference. It doesn't need to merge in its entirety to have done its job.

Good luck with this work!

@erik-sv
Copy link
Copy Markdown
Author

erik-sv commented Apr 7, 2026

@lnispel A few corrections on the IPTC comparison, since it introduces new claims the team should evaluate accurately.

IPTC's WordPress plugin signs images, not text. It wraps c2patool, a Rust binary that must be compiled or installed on the server. That is a fundamentally different architecture from this PR, which implements C2PA signing in pure PHP with zero external dependencies. The two plugins solve different problems for different media types with different deployment constraints.

The publishers in IPTC's network, BBC, CBC/Radio-Canada, WDR, FranceTV, are enterprise news organizations with dedicated IT teams who can manage certificate procurement and binary installation. That is not the user population @jeffpaul described. Jeff's question on March 17 was how to provide signing for the broad WordPress ecosystem, and his response to the BYOK-only model was direct: "Option 2 is almost certainly a non-starter for the majority (or at least statistically significant) of WordPress installs." The Connected tier exists to address that gap, not to replace BYOK, which remains available for publishers who want organizational identity.

On scope: @dkotter has reviewed this PR in detail and provided 21 items of feedback, all addressed. The code is ready for the maintainers to evaluate as a complete architecture. Breaking a reviewed, tested, and cohesive system into sequential PRs does not reduce complexity. It distributes the same review surface across multiple threads while removing the ability to evaluate the tiers as a unified design.

This PR does need to merge to have done its job. An experiment that exists only as a reference is not an experiment. The WordPress AI experiment framework is designed for code that ships, collects feedback, and iterates. I will defer to @jeffpaul, @Jameswlepage, and @dkotter on how they want to proceed.

@jeffpaul
Copy link
Copy Markdown
Member

jeffpaul commented Apr 9, 2026

  1. For the settings, please align the dropdowns & checkboxes with the experiment title & description, plus remove the bold font treatment for the dropdown & checkbox labels
Screenshot 2026-04-08 at 7 43 49 PM
  1. I think, but curious for @dkotter @Jameswlepage @JasonTheAdams input here, that the Connected and BYOK signing tiers might perhaps be good cases for a Connector for those and then those dropdown options could select from enabled, related connectors?
Screenshot 2026-04-08 at 7 45 17 PM Screenshot 2026-04-08 at 7 45 24 PM
  1. In the Content Provenance sidebar panel, some UI tweaks would be helpful:
    a. "Not Signed"/etc ideally middle aligned with the badge icon
    b. "Signed with local key." feels off with "Not Signed" state, perhaps the copy for that state should be "When signed, will use a local key."?
Screenshot 2026-04-08 at 7 51 58 PM
  1. I'm testing using WordPress 7.0-RC2, this PR branch, and the Twenty Twenty-Five theme (aka TT5) and am not able to see the badge on the frontend (above, below, or in-line). We should ensure we're testing with at least TT5 and ensuring badges render as expected there; any additional we test with and verify from the Popular listing from WPORG would be great.

@jeffpaul jeffpaul requested a review from JasonTheAdams April 9, 2026 02:39
@dkotter
Copy link
Copy Markdown
Collaborator

dkotter commented Apr 9, 2026

  1. For the settings, please align the dropdowns & checkboxes with the experiment title & description, plus remove the bold font treatment for the dropdown & checkbox labels

Note that with #340 and #376 now merged, this settings page is rendered differently and as such, how these individual settings are set/rendered will need to change (apologies for the extra work there). #376 should provide an example of how this is now handled within the Content Classification experiment

  1. I think, but curious for @dkotter @Jameswlepage @JasonTheAdams input here, that the Connected and BYOK signing tiers might perhaps be good cases for a Connector for those and then those dropdown options could select from enabled, related connectors?

I don't think the BYOK will necessarily work there but potential for the Connected option to live there, though I think the ideal is to have that support multiple signing services not just Encypher (which is a separate question we probably need to discuss) and that may complicate making this a Connector.

  1. In the Content Provenance sidebar panel, some UI tweaks would be helpful:
    a. "Not Signed"/etc ideally middle aligned with the badge icon
    b. "Signed with local key." feels off with "Not Signed" state, perhaps the copy for that state should be "When signed, will use a local key."?

Also just need more spacing here, everything just runs together.

And just my opinion but not sure we need that "Signed with local key" warning? Just seems like an attempt to get people to switch to the Connected approach and probably causes more confusion than anything. I'd recommend either removing that or putting that as a tooltip under an icon, like the warning icon.

  1. I'm testing using WordPress 7.0-RC2, this PR branch, and the Twenty Twenty-Five theme (aka TT5) and am not able to see the badge on the frontend (above, below, or in-line). We should ensure we're testing with at least TT5 and ensuring badges render as expected there; any additional we test with and verify from the Popular listing from WPORG would be great.

Was going to flag this as well. In my testing I don't ever see anything output on the front-end.

One other bug I noticed, if I have content that was published prior to activating this experiment, if I open that item in the editor but I don't save anything (say I just refresh), it seems the content automatically is signed, even though we claim that only happens on publish and updates.

@dkotter
Copy link
Copy Markdown
Collaborator

dkotter commented Apr 9, 2026

The cost of writing code is trivial. The cost of reviewing, understanding, and maintaining it is not. 228 tests tell you the code does what the author intended. They don't tell you it's the right architecture for WordPress, and they don't reduce the cost of the team needing to fully understand and own every line after it merges. If this was more collaborative in its development, more of the team would be brought along with the decisions inside which would make it more valuable to the ecosystem and the maintainers as a whole.

As far as the size of this contribution goes, @dkotter noted in his first review that there's a lot here and he's sure he missed some things, which is completely understandable at this scale. That's the natural consequence of reviewing this volume at once. Smaller contributions would make thorough review achievable.

If the existing code is already written and tested, breaking it into smaller progressive contributions should be straightforward and significantly easier for the team to review, understand, and maintain.

I do agree with most everything noted in these comments. I would love to see multiple, smaller PRs that make this much easier to review and test and feel confident about merging things in. Wondering if there's a way to easily do this? I don't want to cause undue effort at this point but it is more likely that this will get merged in if we can break it down into smaller pieces, maybe a PR for just local signing and other PRs for connected and BYOK signing?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Type] Enhancement New feature or request

Projects

Status: Needs review

Development

Successfully merging this pull request may close these issues.

5 participants