feat: Content Provenance experiment (C2PA 2.3 §A.7 text authentication)#294
feat: Content Provenance experiment (C2PA 2.3 §A.7 text authentication)#294erik-sv wants to merge 13 commits intoWordPress:developfrom
Conversation
5aadfe6 to
6f950d1
Compare
Codecov Report❌ Patch coverage is 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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
235f898 to
83e9dea
Compare
|
Plugin Check failure appears to be pre-existing on trunk, happy to investigate if needed. |
|
@erik-sv mind updating to branch from 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:
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. |
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!
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. |
|
@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 ProgramThe 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 & TrustThis 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.
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."
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. |
|
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. |
|
@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
b96f9e2 to
9758181
Compare
|
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 Unlinked AccountsThe 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. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
Both PRs are now rebased onto latest What changed in this rebase:
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. |
- 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
Update: Native C2PA signing + ingredient chains + sidebar fixTwo commits pushed since the last update:
|
| 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.
|
@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:
Most items were already in place from earlier iterations. The items that required code changes: removed duplicate |
| $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 ); |
There was a problem hiding this comment.
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?
@erik-sv Thanks for the changes here. I am a little confused on this statement:
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 ( |
dkotter
left a comment
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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:
- I'd remove the
detailsandsummarytags here so settings aren't collapsed - 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 - Indent these tables a bit to align with the rest of the content
Quick example:
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| > | ||
| { __( 'Connect a signing service \u2192', 'ai' ) } |
There was a problem hiding this comment.
Needs an extra space otherwise it bumps into the text above
| return null; | ||
| } | ||
| return ( | ||
| <Notice |
| } | ||
| setIsSigning( true ); | ||
| setError( '' ); | ||
| runAbility( 'c2pa/sign', { post_id: postId } ) |
There was a problem hiding this comment.
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 }`, |
There was a problem hiding this comment.
This may be expected but seems this Verify button doesn't actually verify anything instead it just returns the status.
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 |
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>
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! |
Encypher API alignment, C2PA text spec compliance, and review feedbackThis addresses the remaining review items and fixes spec compliance gaps across all three signing modes. Connected signing (Encypher API)
Verification interoperabilityFour fixes to make the WordPress verifier parse Encypher-produced C2PA manifests:
C2PA text manifest spec compliance (all three modes)Audited local, connected, and BYOK against
Review feedback addressed
Test coverage
|
…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
c744c36 to
d2414b3
Compare
|
@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? |
|
@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.
|
Hi @erik-sv, 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. |
|
@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. |
|
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 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? |
|
@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. |
|
@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! |
|
@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. |
|
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
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.
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.
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. |
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? |





Changes since last review
develop(0.6.0)Abstract_ExperimenttoAbstract_FeatureAPI — class signature and registration updated to match the 0.6.0 feature modelWell_Known_Handlertest coverage — was 0% before this rebase; now coveredwpai_prefix convention — e.g.wpai_title_generation_result(waswp_ai_experiment_*)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
c2pa.created/c2pa.editedactions and provenance-chain ingredient referencesc2pa/signandc2pa/verifyAbilities — any plugin can callwp_do_ability('c2pa/sign', ['text' => …])/.well-known/c2padiscovery endpoint — C2PA §6.4 compliant JSON documentAI fragment provenance (latest commit)
wpai_title_generation_result,wpai_excerpt_generation_result,wpai_summarization_result,wpai_review_notes_result,wpai_alt_text_resultsign_ai_fragmentssetting — when enabled, Content Provenance intercepts these filters and embeds a C2PA manifest into each AI-generated fragment before it reaches the editorPost 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]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| ESigning tiers
WordPress Abilities API
Fragment hook usage for third-party plugins
Files changed
includes/Experiments/Content_Provenance/Content_Provenance.phpincludes/Experiments/Content_Provenance/C2PA_Manifest_Builder.phpincludes/Experiments/Content_Provenance/Unicode_Embedder.phpincludes/Experiments/Content_Provenance/Well_Known_Handler.php/.well-known/c2paendpointincludes/Experiments/Content_Provenance/Verification_Badge.phpincludes/Experiments/Content_Provenance/Signing/Signing_Interface.phpincludes/Experiments/Content_Provenance/Signing/Local_Signer.phpincludes/Experiments/Content_Provenance/Signing/Connected_Signer.phpincludes/Experiments/Content_Provenance/Signing/BYOK_Signer.phpincludes/Abilities/Content_Provenance/C2PA_Sign.phpc2pa/signAbilityincludes/Abilities/Content_Provenance/C2PA_Verify.phpc2pa/verifyAbilityincludes/Abilities/Title_Generation/Title_Generation.phpwpai_title_generation_resultfilterincludes/Abilities/Excerpt_Generation/Excerpt_Generation.phpwpai_excerpt_generation_resultfilterincludes/Abilities/Summarization/Summarization.phpwpai_summarization_resultfilterincludes/Abilities/Review_Notes/Review_Notes.phpwpai_review_notes_resultfilterincludes/Abilities/Image/Alt_Text_Generation.phpwpai_alt_text_resultfiltersrc/experiments/content-provenance/index.jsincludes/Experiment_Loader.phpwebpack.config.jstests/Integration/…/Content_ProvenanceTest.phptests/Integration/…/C2PA_Sign_Test.phpdocs/experiments/content-provenance.mddocs/experiments/content-provenance-developer.mdTest plan
composer test -- --filter Content_Provenance— all 64 tests pass_c2pa_manifestmeta is setc2pa.editedaction + ingredient reference to previous manifestwp_do_ability('c2pa/sign', ['text' => 'hello'])inwp shell— returns signed textwp_do_ability('c2pa/verify', ['text' => $signed])— returnsverified: true/.well-known/c2pa— returns valid JSON discovery documentwpai_title_generation_resultin a test plugin → confirm callback receives correct argsRelated