=== TIP Protocol ===
Contributors: theailaborg
Tags: ai disclosure, content provenance, content authenticity, trust badge, publisher
Requires at least: 6.0
Tested up to: 7.0
Requires PHP: 8.1
Stable tag: 2.5.1
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Prove who made your work and how. Sign posts, songs, videos and research on WordPress, disclose AI use, and let anyone verify it.

== Description ==

TIP Protocol puts your name and the date on everything you publish with WordPress, in a way anyone can check.

When you press Publish, the plugin creates a signed public record of the post. The record says who made it, when it was published, and whether AI was involved. It also holds a fingerprint of the words, images, audio and video in the post. Anyone holding a copy can compare it with the record and see whether it was changed. Readers get a small badge under the post and can check the record in any browser, with nothing to install.

Every signed post becomes a [Verifiable Asset](https://theailab.org/verifiable-assets). A Verifiable Asset is any piece of digital content that carries a CTID (Content TIP ID): a permanent, signed public record of who made it, when, how it was made, and a fingerprint that shows whether it has changed since.

= Who is it for =

Anyone who publishes on WordPress and wants credit for their own work.

* Songwriters and singers. Post lyrics, a demo or a release page with the audio attached. The record names you, shows the date you published, and fingerprints up to two audio files per post.
* Filmmakers and video creators. Post a trailer, a short or behind-the-scenes stills. Up to two videos and six images per post are fingerprinted along with the text.
* Journalists and newsrooms. Sign every article under the reporter's byline and the outlet's name, and tell readers whether AI helped write it.
* Researchers and scientists. Get a signed, dated record of when your findings, preprints or lab notes went public on your site.
* Schools, colleges and universities. Sign course pages, announcements and faculty work under the institution's name, and label AI-assisted material openly.
* Law firms and legal publishers. Sign client alerts, articles and public notices, so anyone can later check that a copy matches what you published and when.
* Bloggers, writers and digital creators. Show readers that the post on your site is the original, signed by you.
* Brands and marketing teams. Show customers how each piece was made, including where AI helped.
* Government, nonprofit and public-interest sites. Let readers confirm a notice really came from you.

= How it works =

1. Install the plugin and connect your TIP-ID. A person gets a personal Author ID at [vp.theailab.org](https://vp.theailab.org). An organization gets its ID from The AI Lab after a review.
2. Write and publish as usual. Label the post Original Human, AI-Assisted, AI-Generated or Mixed.
3. Confirm with Face ID, Touch ID, Windows Hello, a fingerprint or a security key. The plugin signs the post and records it on the TIP network.
4. A badge appears under the post. Anyone can click it to see who made it, when, and how.

= What it proves, and what it does not =

A signed post proves who published it, when, how it was made, and whether it has changed since. It does not prove that what the post says is true, it does not stop anyone from copying your work, and it does not stop AI companies from training on it.

= Under the hood =

* Verifiable AI content labels. Tag every post as Original Human (OH), AI-Assisted (AA), AI-Generated (AG), or Mixed (MX). The label is cryptographically signed, so it is not a self-claim that anyone can copy. Readers, search engines, and automated verifiers can all confirm it.
* Cryptographic publisher identity. A site-level TIP-ID and optional per-author TIP-IDs sign your content with post-quantum ML-DSA-65 signatures (FIPS 204). Readers see a verified trust badge; verifiers see a tamper-proof signature trail back to your domain.
* No browser extension required. Reader-facing verification works in any modern browser through HTTP response headers, HTML meta tags, JSON-LD provenance, and an accessible bundled `<tip-badge>` web component. Your audience does not need to install anything.
* Plays nicely with your SEO stack. When Yoast SEO or Rank Math is active, TIP Protocol merges provenance into the existing JSON-LD graph instead of competing with it. No duplicate schema, no conflicting article markup.
* Versioned and correction-aware. Each registration is versioned. An article that gets corrected, updated, or expanded later still resolves to a clean provenance trail with a clear change history.
* Encrypted-at-rest identities. Private signing keys never leave WordPress. They are AES-256-GCM encrypted with PBKDF2-derived keys using WordPress salts as input, and the admin UI never echoes them back to the browser.
* Open, free, and global. The TIP Protocol specification is open. The plugin is GPL-2.0-or-later. The workflow is the same whether you publish from Tokyo, Berlin, Sao Paulo, Lagos, Mumbai, or New York.

= Key capabilities =

* Register posts, pages, news items, events, products, and other public editor-supported content types on the TIP network with one click.
* Emit TIP verification response headers and provenance meta tags from WordPress on every public page.
* Enrich article JSON-LD schema with provenance fields, with smart handoff to Yoast SEO and Rank Math when they already manage the site schema graph.
* Render accessible verification badges with a bundled local web component so no third-party CDN is required by default.
* Manage publisher and creator TIP identities with encrypted private keys stored inside WordPress.
* Import reviewed organization TIP-IDs from The AI Lab, or personal Author IDs exported from vp.theailab.org, through the built-in `.tip.json` connection flow. Locked files open in the browser with a date (date of birth for a person, date of incorporation for an organization).
* Domain binding via `.well-known/tip-protocol.json` or DNS TXT records so the network can confirm a publisher legitimately claims a domain.
* Multi-author byline rosters with per-author roles (reporter, editor, photographer, columnist, guest, contributor) and optional per-author co-signatures.
* Audit log of registrations with CTID, change type, version history, signing officer roster, and registration status visible inside the admin.
* GDPR-friendly. Integrates with WordPress personal data export and erasure tooling.
* Biometric signing on every publish. Each signature is confirmed with Face ID, Touch ID, Windows Hello, a fingerprint or a security key (WebAuthn passkeys) on a device the writer added with their WordPress password. Publishing itself is never blocked.

= How TIP Protocol differs from other provenance systems =

TIP Protocol is built for the publishing surface that already exists in WordPress, not for the camera-to-cloud capture flow that systems like C2PA Content Credentials were designed around. Where C2PA focuses on signing images and videos at the moment of capture, TIP Protocol focuses on signing the final published article (text, media manifest, author roster, origin label) at the moment a publisher hits Publish in WordPress. Both approaches can coexist; TIP Protocol fills the editorial gap.

= About the AI Trust Council =

The AI Trust Council is the governance body that maintains the Trust Identity Protocol specification, sets the criteria for the Global Seal of Trust certification, and curates the AI Trust Registry of verified publishers. Operated under The AI Lab Intelligence Unobscured, the AI Trust Council keeps AI disclosure standards, content provenance methods, and reader-facing trust signals open, auditable, and aligned with how content actually moves across the modern internet. Specification updates, scoring rule changes, and Global Seal of Trust certification announcements are coordinated through the AI Trust Council and published on theailab.org.

= About The AI Lab =

The AI Lab Intelligence Unobscured is the research and standards organization behind Trust Identity Protocol, the AI Trust Council, the AI Trust Registry, and the Global Seal of Trust certification program. Founded by Dinesh Mendhe, The AI Lab builds open infrastructure for the AI-native internet: cryptographic content provenance, verified human identity, AI disclosure labeling, and the verification systems that make those signals trustworthy to readers, search engines, and platforms worldwide. The #HumanOrAI public campaign extends the same goal into everyday discourse: every published piece should answer "human or AI?" with a verifiable signal, not a self-claim. Learn more at theailab.org.

= Integrations and compatibility =

* Yoast SEO and Rank Math: TIP Protocol detects either plugin and merges provenance into its existing JSON-LD schema graph instead of emitting a competing article schema block. No duplicate markup, no schema conflict.
* WooCommerce: works with WooCommerce product pages and any other public editor-supported custom post type. Sign product descriptions, sponsored articles, and editorial product reviews with the same workflow as regular posts.
* Page builders: Elementor, Beaver Builder, Bricks, Divi, and similar tools render TIP-signed content correctly because the badge web component and provenance meta tags emit at the WordPress template layer, beneath the page builder's rendering pipeline.
* Multisite: tested with both subdomain and subdirectory multisite installations. Each subsite manages its own publisher TIP-ID and contributor registry independently.
* Caching plugins: WP Rocket, W3 Total Cache, LiteSpeed Cache, WP Super Cache, and similar work without configuration. The bundled trust badge is a small web component that caches cleanly along with the rest of the page.
* Block editor and classic editor: both editors are fully supported with sidebar and metabox controls for AI origin labels and multi-author bylines.
* WebAuthn-capable browsers: biometric confirmation before every signature, for personal and organization TIP-IDs alike (Touch ID, Face ID, Windows Hello, fingerprint readers, hardware security keys). Current Chrome, Edge, Safari and Firefox all support it. wp-admin must load over HTTPS.

= TIP Protocol glossary =

* TIP-ID: a unique cryptographic identifier for a verified publisher, platform, or individual author. Format: tip://id/REGION-xxxxxxxxxxxxxxxx. Used as the signing identity for every piece of registered content.
* CTID (Content TIP ID): the per-piece-of-content identifier produced when a publisher registers a post on the TIP network. Persistent across content updates so a corrected article still resolves to its original provenance trail.
* Verifiable Asset: any piece of digital content that carries a CTID. Every post signed with this plugin is one. See https://theailab.org/verifiable-assets
* CNA-2.2: the current canonical normalization algorithm that defines exactly which bytes get signed. Cross-implementation compatibility hinges on every signer (WordPress plugin, browser extension, mobile clients) producing the same canonical bytes from the same inputs.
* ML-DSA-65: the post-quantum digital signature algorithm (FIPS 204, formerly known as CRYSTALS-Dilithium) that TIP Protocol uses for content signatures. Resistant to attacks from both classical and quantum computers.
* SHAKE-256: the extendable-output hash function (FIPS 202) used to derive the canonical hash that ML-DSA-65 signs.
* Origin code / AI disclosure label: one of four labels publishers attach to every signed piece of content. Original Human (OH), AI-Assisted (AA), AI-Generated (AG), Mixed (MX). The label is part of the signed payload, so it cannot be modified after publication without invalidating the signature.
* Trust badge: the reader-facing verification element rendered as the bundled tip-badge web component. Shows the verified publisher, origin label, trust score, and verification state.
* AI Trust Registry: the public registry of verified publishers and platforms maintained by the AI Trust Council. Used by reader-side verifiers to confirm a TIP-ID maps to a known organization.
* Global Seal of Trust: the certification program publishers earn by adopting the AI Trust Council's open verification criteria.
* Publisher Mode and Personal Mode: the two attribution patterns the plugin supports. Publisher Mode signs with an organization TIP-ID and credits an authors roster. Personal Mode signs with an individual creator's TIP-ID under attribution self.
* Domain binding: the proof a publisher controls the domain on which signed content appears. Done via either a .well-known/tip-protocol.json endpoint or a DNS TXT record at _tip-protocol.your-domain.com.

= Resources =

* WordPress.org plugin page: https://wordpress.org/plugins/tip-protocol/
* The AI Lab homepage: https://theailab.org
* Personal Author TIP-IDs: https://vp.theailab.org
* Source code: https://plugins.svn.wordpress.org/tip-protocol/
* Support forum: https://wordpress.org/support/plugin/tip-protocol/
* Verifiable Assets explained: https://theailab.org/verifiable-assets
* The AI Lab on LinkedIn: https://www.linkedin.com/company/the-ai-lab-org/
* The AI Lab on YouTube: https://www.youtube.com/@TheAILabOrg
* The AI Lab on X: https://x.com/theailaborg
* The AI Lab on Instagram: https://www.instagram.com/theailaborg/
* The AI Lab on Facebook: https://www.facebook.com/profile.php?id=61589147876006
* The AI Lab on GitHub: https://github.com/theailaborg

== Legal ==

This plugin is distributed under `GPL-2.0-or-later`.

Bundled legal and notice files:
* `LICENSE.txt` for the GPL software license notice
* `NOTICE.txt` for the bundle notice summary shown in the admin UI
* `PATENTS.txt` for informational patent disclosure
* `TRADEMARKS.txt` for informational trademark notice

Plugin attribution is optional and can be enabled in settings.

== Installation ==

1. Upload the plugin folder to `/wp-content/plugins/`.
2. Activate the plugin in WordPress.
3. Open `TIP Protocol` in the WordPress admin menu.
4. Import a reviewed `.tip.json` package for a publisher TIP-ID or creator TIP-ID.
5. Verify your domain and review the publishing defaults.
6. Publish content and confirm the TIP badge, headers, and provenance metadata on the front end.

== Frequently Asked Questions ==

= What is TIP Protocol and what does the plugin do? =

TIP Protocol (Trust Identity Protocol) is an open content-provenance specification. The WordPress plugin lets publishers and creators sign their published content with a cryptographic identity, label whether AI was used to write it, and surface a verifiable trust badge to readers. It works as an editorial layer on top of WordPress without requiring a browser extension.

= How does TIP Protocol handle AI-generated content? =

Every post can carry one of four origin labels: Original Human (OH), AI-Assisted (AA), AI-Generated (AG), or Mixed (MX). The label is part of the cryptographically signed payload, so it cannot be modified after publication without invalidating the signature. Readers and verifiers see a verified AI-disclosure label, not a self-claim.

= Does TIP Protocol replace C2PA or Content Credentials? =

No. They solve adjacent problems. C2PA and Content Credentials focus on signing images and video at capture time, typically inside a camera or generative tool. TIP Protocol focuses on signing the final published article (text, media manifest, author roster, AI label) at the moment a publisher hits Publish in WordPress. They can coexist on the same site.

= What is a TIP-ID? =

A TIP-ID is a unique cryptographic identifier in the format `tip://id/REGION-xxxxxxxxxxxxxxxx`. Publishers receive a reviewed publisher TIP-ID from The AI Lab. Individual writers can generate a personal creator TIP-ID on vp.theailab.org. Both types are imported into WordPress as a signed `.tip.json` package.

= Where do editors set the AI / origin label? =

In the block editor, open the post sidebar and use the TIP Protocol panel. In the classic editor, use the TIP Origin Declaration metabox. The label can also be confirmed or changed in the biometric sign popup that opens right after you publish.

= Why does TIP Protocol ask for Face ID, Touch ID or my fingerprint when I publish? =

Every signature is confirmed on one of your devices with Face ID, Touch ID, Windows Hello, a fingerprint or a security key, so a signature needs both your WordPress account and a device you set up. Right after you publish, a popup asks you to confirm, then signs the post and registers it on the TIP network. The first time, the popup asks for your WordPress password and sets up the device; you are emailed whenever a device is added or removed. Your fingerprint or face never leaves the device. WordPress stores each device's public key, its name, and when it was added and last used. You can add or remove devices in your WordPress profile, and administrators can remove a user's device if it is lost.

When a post signs with its author's personal TIP-ID (sites without a site-wide TIP-ID), only that author can confirm the signature. Retracting a post (moving it to the trash) and updating its address after a permalink change are signed automatically, because they add no new authorship claim.

= What happens when I publish from the mobile app, the REST API, WP-CLI or a scheduled post? =

The post publishes normally. It is not signed yet, because those tools cannot show a biometric prompt, so it waits as "awaiting biometric signature" and appears under Waiting for a signature in the TIP Protocol dashboard widget. The next time its author opens the post in the WordPress editor, the sign popup opens once, and the TIP Protocol panel keeps a Sign with biometrics button until it is signed. Scheduling a post from the editor opens the popup straight away and signs the post with the address it will have once live, so it is signed before it goes live. Private posts are never prompted, because signing registers a post on the public TIP network; you can still sign one from the panel.

Sites that sign in through single sign-on, where users may not know a WordPress password, can turn off the password step for adding devices with the `theailab_webauthn_require_password` filter.

= Does biometric signing need HTTPS? =

Yes. Browsers only allow passkeys, Face ID, Touch ID and Windows Hello on secure pages, so wp-admin must load over HTTPS (plain http on localhost also works for development). The plugin shows an admin notice when it detects that wp-admin is not on HTTPS.

= Can I use TIP Protocol on a multi-author blog or newsroom? =

Yes. A site can carry a single verified publisher TIP-ID for the organization, plus per-writer creator TIP-IDs for individual bylines. The plugin supports byline ordering, multiple co-authors per post, role tagging (reporter, editor, photographer, columnist, guest, contributor), and optional per-author co-signatures.

= Does this work with WooCommerce, multisite, custom post types, and page builders? =

Yes. TIP Protocol registers any public editor-supported content type, which includes WooCommerce products, custom post types, news/event types, and pages built with Elementor, Beaver Builder, Bricks, and similar tools. Multisite installations are supported.

= Does the plugin add duplicate SEO schema if I already use Yoast SEO or Rank Math? =

No. TIP Protocol detects active major SEO plugins. When Yoast SEO or Rank Math is present, TIP Protocol merges TIP provenance into their existing JSON-LD graph instead of emitting a competing article-schema block. When neither is active, TIP Protocol emits its own complete provenance-enriched article schema.

= Does the public verification badge require a third-party CDN? =

No. The badge ships as a bundled local web component by default. A CDN option is available in plugin settings if you explicitly choose it, but the default works fully offline and respects strict CSP environments.

= What happens to private signing keys stored in WordPress? =

Private keys are encrypted at rest with AES-256-GCM. The key-derivation input includes WordPress salts plus a PBKDF2 stretch (200,000 iterations of SHA-256). The admin UI never exposes the stored encrypted private key back to the browser. Decryption only happens server-side at the moment of signing.

= What file should I import into WordPress to connect an identity? =

A reviewed `.tip.json` package that contains `tip_id`, `public_key`, `private_key`, `algorithm`, `region`, `created_at`, and (for organizational identities) `vp_id`. Publisher packages come from The AI Lab after organization review. Personal creator packages are exported from vp.theailab.org and can be DOB-locked for additional protection.

= Is this plugin free and open source? =

Yes. The plugin is licensed under GPL-2.0-or-later. The TIP Protocol specification is open. The plugin can be used commercially on any number of sites.

= Where is the TIP network and where does my data go? =

By default the plugin talks to the TIP node at `https://node.theailab.org` for registration and verification calls. You can configure a different node URL in plugin settings if you operate a self-hosted TIP node. Signing a post sends its address, its text, content hashes, the origin label, author TIP-IDs, and signatures to the TIP node, which uses the text to check the content (for example the AI-origin prescan). Biometric data is never sent anywhere.

= Will TIP Protocol slow down my site? =

No. Reader-facing badges use a small bundled web component (about 8 KB), all provenance meta is emitted server-side as HTTP headers and HTML meta tags during the normal page render, and verification lookups for trust scores are cached. There is no extra browser fetch on every page load.

== Accessibility ==

TIP Protocol includes keyboard-accessible verification badges, focus-visible controls, descriptive editor help text, semantic public trust UI, and screen-reader-friendly verification details. The public badge details panel supports keyboard open/close behavior and Escape to dismiss.

== External services ==

This plugin connects to external TIP services to deliver its core provenance features.

= TIP node API =

The plugin sends registration, verification, and trust-score requests to the configured TIP node endpoint.

Data sent:
* publisher or creator TIP-ID
* content registration payloads
* canonical, exact, and perceptual hashes
* content signatures
* verification lookups for trust scores

Service provider: The AI Lab Intelligence Unobscured, Inc.
Service URLs: `https://node.theailab.org` by default, or your configured TIP node URL
Service purpose: content registration, provenance verification, trust score retrieval

= Optional badge CDN =

If you switch the badge delivery mode from `Bundled local copy` to `CDN`, reader browsers will load the public badge script from the TIP badge CDN.

Data sent:
* standard browser request metadata needed to fetch the script

Service provider: The AI Lab Intelligence Unobscured, Inc.
Service URL: `https://badge.theailab.org`
Service purpose: optional remote delivery of the TIP badge web component

== Privacy ==

TIP Protocol adds suggested privacy-policy text in WordPress and integrates with WordPress personal data export and erasure tools for stored user TIP identity data.

== Upgrade Notice ==

= 2.5.1 =

Organizations with no personal byline can now sign posts as their own author. Organization TIP-ID files now ask for the date of incorporation instead of a date of birth.

= 2.5.0 =

Every TIP signature now needs biometric confirmation (Face ID, Touch ID, Windows Hello, fingerprint or security key). After publishing, a popup asks you to confirm; adding a device asks for your password. wp-admin must use HTTPS. Unsigned posts stay live until signed.

= 1.5.0 =

Renames all internal identifiers from the legacy "tip_" prefix to "theailab_" to comply with WordPress.org plugin guidelines. Stored data is automatically migrated on upgrade. Backward-compatible shortcode aliases preserved.

= 1.4.0 =

Adds typed publisher / platform / personal identities, central officer governance policy, CNA-2.1 signed payload, ML-DSA-65 vendor import fix, hardened REST permissions, and accessibility improvements.

= 1.3.0 =

Aligns WordPress hashing with the current developer guide, including guide-compliant content-string building, `tipmedia` body indicators, and `CNA-MIX-1` combined hashing when attached media participates in the registration hash.

= 1.2.3 =

Switches TIP identity setup to an import-only workflow, adds `.tip.json` package imports, and aligns publisher binding headers to the registered permalink.

= 1.0.2 =

Improves store readiness with local default asset delivery, richer schema, privacy-policy integration, stronger badge accessibility, and WordPress.org packaging files.

== Changelog ==

= 2.5.1 =
* Fix. An organization TIP-ID with no personal byline can sign. When no writer is credited on a post, the organization is listed as its own author, the same way the TIP browser extension and the verification portal sign for an organization. Before this, the TIP network refused every such post because it requires at least one author, and the error sent publishers to an "Allow author-less posts" setting that the settings screen never showed. Posts that credit a writer are unchanged and still name that person as the byline.
* Fix. The page markup credits an organization that signed as its own author as an Organization, not as a Person named after the WordPress user who pressed Publish.
* Fix. Importing an organization TIP-ID file now asks for the organization's date of incorporation, which is the date that locks it. The unlock screen used to describe every locked file as a personal Author ID locked with a date of birth, so organizations typed the wrong date and the file looked broken. Personal Author ID files still ask for the date of birth.
* Tests. 697 to 716 assertions across 22 files, all passing. They cover the organization-as-author context, the validators that check it, and the page markup. The signed payload for an organization signing as itself was compared byte for byte with the payload the TIP network's own code builds from the same input, and an organization file locked with the network's own key-file code was unlocked by the plugin's import code.
* Verified against the TIP node repository as of 2026-10-04. No change to the signing recipe or any endpoint.

= 2.5.0 =
* New (security, behaviour change). Biometric signing on every publish. Every content signature, for personal and organization TIP-IDs alike, now requires a WebAuthn user-verification confirmation (Face ID, Touch ID, Windows Hello, a fingerprint reader or a security-key PIN) by the WordPress user who triggers it. There is no per-user or per-site switch. The earlier opt-in step-up from 2.1.0 is retired.
* New. Biometric sign popup in both the block editor and the classic editor. It opens right after a post is first published or scheduled, lets the writer confirm the AI-disclosure label, and walks first-time users through adding their device. Users who sign from a new computer can add it from the same popup. The popup is keyboard accessible, keeps focus inside, announces progress to screen readers and closes with Escape.
* New design. The popup, the editor sign panel and the profile device list follow a quiet editorial style, with one typeface, ink on light grey, hairline rules and no shadows, gradients or icons.
* Security. Signing has a single enforcement point. The registrar refuses to decrypt a private key unless the current request carries a single-use proof that the same user just passed a verified WebAuthn assertion for that exact post. Publish hooks, the legacy /register and /update REST routes, cron, WP-CLI, XML-RPC, application-password REST calls and the prescan "accept" link cannot sign. Retracting a post and updating its address after a permalink change remain automatic, because they add no new authorship claim.
* Security. Adding or removing a signing device requires the account's WordPress password (five wrong attempts lock the step for 15 minutes), and the account owner is emailed on every change. Without this, anyone holding a session cookie could add their own authenticator. Administrators can remove a user's device from that user's profile. Sites that sign in through single sign-on can turn off the password step with the `theailab_webauthn_require_password` filter.
* Security. When a post signs with its author's personal TIP-ID (no site-wide TIP-ID), only that author can confirm the signature. An editor's confirmation no longer authorizes another person's key.
* Security. Challenges and tickets are now claimed atomically, so two concurrent requests can never spend the same one. Several challenges can be open at once, so two editor tabs no longer cancel each other. A signature counter that drops back to 0 is treated as a possible cloned key. Device data from the browser is size-limited, and only P-256 and 2048 to 4096-bit RSA keys are accepted. The biometric REST routes require the content-signing capability.
* Publishing is never blocked. Posts published from tools that cannot show a biometric prompt (mobile apps, REST integrations, WP-CLI, scheduled publishing) go live as usual, wait as "awaiting biometric signature", and are listed under Waiting for a signature in the dashboard widget until someone signs them from the editor. Private posts are never prompted, because signing registers a post on the public TIP network.
* Fix. Scheduled posts are signed with the address they will have once live. WordPress gives scheduled posts a plain ?p=ID link, which would otherwise have been signed into the record. When a scheduled post goes live, its registered address is also checked and repaired if the slug changed.
* Fix. A label change on a registered post (accepting the AI prescan suggestion) only ever uses the lean origin update; it can no longer turn into a second full registration. The label is changed only after the signature succeeds, so cancelling leaves the post as it was and the suggestion can be accepted again.
* Fix. The label chosen in the block editor sidebar is now the one the sign popup and the pre-publish check show.
* Fix. Safari compatibility. Ceremony options are fetched before the click and refreshed before they expire, and the sign button waits until they are ready, so Safari, which only allows passkey prompts in direct response to a click, no longer refuses the confirmation. This also applies to adding a device on the profile page.
* Fix. The relying party is the wp-admin host. Each device records the address it was set up for, and devices set up for another address are listed but not offered for signing, so the popup asks to add this device instead of failing.
* Fix. The first-publish hook covers every public post type. The previous hooks only fired for posts and pages, so custom post types such as WooCommerce products never reached the publish flow. The editor panels no longer load in the site editor.
* Fix. A site with no TIP-ID connected is not asked for biometrics on publish. The editor shows a link to connect a TIP-ID instead.
* Fix. TIP-IDs with three-letter regions (for example tip://id/IND-...), which exist on the TIP network, can now be imported. The importer previously accepted only two-letter regions.
* Fix. The biometric audit record is written to the local transaction log for every signature. Earlier releases stored it in post meta that nothing read.
* Privacy. WordPress's personal data export now includes each user's signing devices, and erasure removes them; audit records of posts already signed are kept with the post's transaction log. The suggested privacy policy text describes passkeys, the audit record, and that a signed post's text is sent to the TIP node. The FAQ previously said the article body was not sent; that was wrong and is corrected.
* Improved. Plain-English messages for the TIP network's 412 byline checks, for expired sessions, and for every WebAuthn failure. Admin notices when wp-admin is not served over HTTPS or the server lacks the OpenSSL extension. Uninstall removes passkey records network-wide on multisite, plus pending sign prompts.
* Tests. 559 to 697 assertions across 22 files, all passing. The WebAuthn verifier is driven with real P-256 keys and signatures through every rejection path, including replay, missing user verification, wrong origin or relying party, altered data, a foreign key, a cloned-key counter, password-less enrollment, unsupported key types and concurrent claims. Source contracts pin where the signing gate, the ownership check and the relabel rule sit. Key checks were confirmed by mutation testing. The flow was exercised end to end in a real WordPress install in both editors.
* Verified against the node, verification-provider and browser-extension repositories as of 2026-10-04. No contract-affecting upstream change. The canonical payload, hash and signature recipe, the author-signed actions and the .tip.json export are unchanged, and the node neither requires nor accepts any biometric field, so this release changes no signed bytes.

= 2.4.1 =
* Fix (forward-compatibility, protocol-critical): registrations of posts without attached media no longer send empty media-fingerprint fields to the TIP network. The network's latest schema validates the media fingerprint against a strict 64-character format and treats an empty string as malformed, which would have rejected every text-only post registration once that node release deploys. The plugin now omits the media fields entirely when a post has no attached media, which the network explicitly treats as valid. Posts with attached media are unaffected, and because these fields sit outside the signed payload, no signature bytes change and all existing registrations remain exactly as they are.
* Improved: plain-English explanation for the network's malformed-media-fingerprint rejection, pointing at the actual causes (manifest tampering by another plugin) rather than surfacing a raw error code.
* Tests: 549 to 559 assertions, all passing, including coverage that text-only registrations omit the empty media fields while media-carrying registrations preserve them byte-for-byte.
* Verified against the node and verification-provider repositories as of 2026-08-20: this schema bounding is the only contract-affecting upstream change; the signing algorithm, canonical payload, identity import format, and every endpoint the plugin calls are unchanged.

= 2.4.0 =
* New: AI-prescan verdict on the edit screen. After each registration the plugin reads back the network's asynchronous AI check and, if the content was flagged while labeled Original Human, shows a clear warning with the network's own explanation, the zero-penalty decision deadline, and a one-click "Change label to AI-Assisted" action. Relabeling inside the window costs nothing; ignoring a strong flag can escalate to a network reviewer, so the plugin now makes that choice visible instead of silent.
* New: near-duplicate advisory. When the network's similarity sweep matches your newly registered post against existing registered content, a non-blocking amber notice lists the closest matches. The registration itself succeeded and keeps its own record; the notice is context, not an error, and can be dismissed.
* New: "Preview what will be signed" expander in the classic editor metabox. Shows the exact normalized text and hash that will be signed, so writers can see why smart-quote changes, markup edits, or whitespace tweaks after publishing can invalidate the badge. Read-only and loaded on demand.
* New: domain binding renewal. The plugin now re-verifies the domain binding twice daily (when due) instead of trusting a one-time check forever. A transient network blip never drops the verified state: the status only downgrades after five consecutive failures, mirroring the network's own failure budget.
* New: share-card warming. After each registration the plugin pings the verification provider's card renderer so findtip.org share links unfurl correctly on the very first scrape instead of showing an empty card that social platforms then cache.
* Reliability: the read-back is fully non-blocking with capped retries; every new surface is advisory and can never affect registration state or editing.
* Tests: 521 to 549 assertions across 19 files, all passing, including behavioural coverage for the read-back scheduler, retry caps, verdict projection, and the share-card ping.
* Roadmap: per-post-type default signer and the perceptual text fingerprint envelope remain planned for a future release.

= 2.3.2 =
* Fix (protocol-critical): origin updates and retractions now use the TIP node's author-signed action contract. The node verifies these post-registration actions against a small fixed field set signed by the registering identity, with the content ID bound into the signature server-side so a captured signature cannot be replayed against different content. Earlier releases sent a differently shaped body for both actions: the node rejected every origin update and every retraction at validation, and retractions silently fell back to local-only. Both actions now reach the network correctly.
* New: automatic permalink repair. Changing a registered post's slug or permalink now proposes an owner-signed registered-URL update to the TIP network, so the public record follows the post instead of pointing at a URL that no longer resolves. The repair compares the recorded URL against the current canonical permalink on every post update, which also heals older registrations whose stored URL predates the canonicalization fix in 2.3.1. Failures are logged and retried on the next edit; they never block editing or flip the post's registration status.
* Compatibility: the wire envelope now explicitly tolerates the network's new optional parent-context URL field on content records, and the drift-guard test suite locks in that tolerance. Content registered by this plugin is unaffected; the field exists for reply-type content registered by other TIP clients.
* Improved: three new plain-English error explanations, including a targeted message when the site's current signing identity differs from the identity that originally registered a post (previously this surfaced as a misleading "session expired" message).
* Tests: 495 to 521 assertions across 18 files, all passing. New coverage pins the exact signed bytes for all three author actions so the field-set regression that motivated this release cannot return.
* Verified against the node, verification-provider, and browser-extension repositories as of 2026-08-15: the content registration contract, the .tip.json export format, and the signing algorithm are all unchanged — existing registrations and imported identities are unaffected.

= 2.3.1 =
* Fix (protocol-critical): registered URLs are now WHATWG-canonicalized before signing. The registered URL is part of the cryptographically signed payload, so the TIP node cannot normalize it and instead rejects any URL that is not already in canonical form. WordPress permalinks frequently are not: a site whose front page URL has no trailing slash, a host with any uppercase letter, an explicit :80 or :443 port, an internationalized domain, or a post slug containing non-ASCII characters would all have been refused by the network. The plugin now converts each URL to the exact same canonical form the rest of the TIP ecosystem produces, verified byte-for-byte against 32 reference cases covering Latin, accented, Cyrillic, CJK, and internationalized-domain inputs.
* Fix: identity imports no longer discard issuer data. When unlocking a date-of-birth protected .tip.json from vp.theailab.org, the plugin previously hardcoded the identity type and dropped the verification-provider ID and display name that the file actually carried. All three are now read from the file and passed through, so the issuer binding is preserved and writers appear under their real name in bylines instead of a bare TIP-ID.
* Improved: added a plain-English explanation for the network's non-canonical-URL rejection, so the rare remaining cases (a permalink with an embedded username and password, or a non-http scheme) produce actionable guidance rather than a raw error code.
* Tests: 447 to 495 assertions across 17 files, all passing. New coverage includes a URL canonicalization suite whose expectations were generated from the reference implementation, plus explicit guards for the byte-safety bug that would silently corrupt non-ASCII permalinks.
* No changes to signing keys, the canonical payload shape, or stored data. Existing registrations are unaffected.

= 2.3.0 =
* New: Friendly error map (Theailab_Error_Map). Every user-visible error from the TIP node is now translated from opaque codes ("content_hash_mismatch", "signature verification failed", "url_already_registered", 409, 429, 5xx, network failures, invalid TIP-ID format, revoked identity, missing vp_id, missing registered_urls, auth/nonce, reserved identity type) into plain English with an actionable next step. The raw node message is still preserved in the transaction log for developer debugging.
* New: Real content retraction. Prior to this release the plugin treated the node's /v1/content/:ctid/retract endpoint as unsupported and only recorded retractions locally. When a signed post is trashed the plugin now signs a canonical CONTENT_RETRACTED payload and submits it to the node so the DAG record reflects the retraction. Local state still flips even if the network call fails, so trashing always drops the reader-facing badge.
* New: Revocation feed subscription. Hourly WP-Cron beat calls the node's /v1/revocations?since=<cursor> endpoint and, if the site's Publisher TIP-ID or any personal Author TIP-ID in the contributor registry appears in the revocation list, marks it locally revoked. The registrar refuses to sign with a revoked identity, so publishers stop producing signatures the node would reject.
* New: Reserved-identity-type gate. Importer now correctly flags syndicator / institution / service / collective .tip.json packages as reserved for a future protocol release, with a distinct error message instead of falling through to the generic "unknown tip_id_type" branch. New THEAILAB_IDENTITY_TYPES_RESERVED constant documents the reserved set.
* New: Milestone counter. Non-blocking admin notice at the 1st, 10th, 100th, 1,000th, and 10,000th successful registration on the site.
* Improved: Tier color hex sync. Consolidated the trust-tier hex palette (Highly Trusted, Trusted, Verified, Caution, Not Trusted) into shared THEAILAB_TIER_COLOR_* constants that mirror the TIP node's getTier() palette. The Verified tier hex now matches the node (#A88B15) instead of a locally-drifted #C9A84C. Added brand accents THEAILAB_BRAND_GOLD / THEAILAB_BRAND_GOLD_DARK / THEAILAB_BRAND_NAVY for cross-surface consistency.
* Improved: Copy pass replacing internal jargon "Origin code" with plain English "How was this written?" across the classic-editor metabox, the settings help panels, and the button strings. Consistent with the block-editor sidebar phrasing.
* Improved: Field-hint microcopy under the origin-code selector explaining that the label is part of the signed record and cannot be silently changed later.
* Reliability: Test suite grew from 406 to 444+ assertions with new test-error-map.php (18) and test-reserved-tip-id-type.php (20).
* Roadmap: Six additional concepts from the node + browser-extension survey are earmarked for follow-up releases: preview canonical form expander (v2.3.1), content read-back + near-duplicate advisory (v2.3.1), domain binding renewal cron (v2.3.2), prescan tier notice + change-origin grace CTA (v2.3.2), publisher-mode gating by post type (v2.3.3), perceptual text fingerprint envelope with MinHash (v2.3.3).

= 2.2.2 =
* Plugin-directory listing: appended five new subsections to the Description body to broaden search discoverability without changing the existing positioning. New sections: About the AI Trust Council (governance body entity), About The AI Lab (research and standards organization entity, names Dinesh Mendhe as inventor), Integrations and compatibility (Yoast SEO, Rank Math, WooCommerce, multisite, caching plugins, page builders, WebAuthn browsers), TIP Protocol glossary (11 named-term definitions covering TIP-ID, CTID, CNA-2.2, ML-DSA-65, SHAKE-256, origin codes, trust badge, AI Trust Registry, Global Seal of Trust, Publisher Mode and Personal Mode, domain binding), and Resources (4 external link targets). No code changes.

= 2.2.1 =
* Follow-up review of the 2.2.0 wire-envelope refactor caught two issues that the initial test pass missed.
* Fix: The update-content-origin path on the legacy CNA-2 signer route was missing the same legacy-required-fields fallback that the register path has. When `sign_with_context()` returns an empty `$signature['payload']` (legacy CNA-2 case), the wire body sent to the node was missing `signer_tip_id`, `authors`, `registered_urls`, `content_hash`, and `cna_version`. The update path now layers those required fields on top of the helper output exactly as the register path does — same shape, same field names. Legacy CNA-2 update calls work again.
* Fix: Removed duplicate `content_hash` and `cna_version` fields from the update path's auxiliary array. Both were already in the canonical signed payload for the CNA-2.2 case, and the legacy fallback supplies them for the CNA-2 case. Sourcing each field from exactly one place removes the silent-override risk that would have appeared if the canonical builder ever changed those fields' semantics independently.
* Tests: Extended `tests/test-wire-envelope-drift.php` from 34 to 46 assertions covering the legacy CNA-2 update fallback shape (all spec-required fields present under the new canonical names; drift-guard fields confirmed stripped even on the legacy path) and the no-aux-override invariant (canonical content_hash + cna_version win when aux tries to set them).

= 2.2.0 =
* Fix (signing): Align HTTP wire envelope sent to the TIP node with the current CONTENT_SIGNING.md spec. The update-content-origin call previously shipped three legacy fields the node now rejects on verification: `author_tip_id` (replaced by canonical `signer_tip_id` plus `authors[]`), `public_key` (node now resolves from the DAG identity record; sending it duplicates state), and `registered_url` singular (renamed to `registered_urls` plural and already in the canonical signed payload). The new private helper `Theailab_Content_Registrar::build_wire_envelope()` anchors every wire body on the canonical signed payload and strips the three legacy keys regardless of caller input. Both the register and update code paths now use it. This was the silent root cause of "Content signature verification failed" reports on personal Author IDs whose verification chain was otherwise cryptographically valid.
* Fix (signing): Personal-mode self-author entries now use the Phase 1 spec values `role: "byline"` + `key_mode: "attribution"` (previously `role: "reporter"` + `key_mode: "self_signing"`). The publisher's ML-DSA signature IS the author's signature in self-mode, so the author entry is marked `attribution` and `signed: false`. Earlier values were a leftover from a draft spec; the node verifies signatures over signed bytes that include the authors[] entry, so the wrong role/key_mode produced silently mis-verifying signatures.
* Fix (signing): 409 Conflict responses from the TIP node (signal: the same signer_tip_id + ctid pair is already in flight in the mempool) are now mapped to a distinct `theailab_api_conflict_pending` error code and surfaced to the post as `processing` status, not `error`. The previous behavior flipped the post status to `error` on every transient mempool overlap and required an admin to manually retry.
* Tests: New `tests/test-wire-envelope-drift.php` (34 assertions) locks in the wire envelope contract — the three legacy keys are stripped even when callers try to pass them. New `tests/test-self-author-shape.php` (12 assertions) locks in `role=byline` + `key_mode=attribution` for personal-mode self across all three code paths. 46 new assertions total.
* No breaking changes for users. The plugin still accepts the same `.tip.json` packages, still emits the same admin UI, still renders the same `<tip-badge>`. The only behavior change is that signatures now verify against the current spec — which is what users expected to happen all along.

= 2.1.7 =
* Plugin-directory listing: removed the second Contributor handle (`theailab`) that WordPress.org's importer flagged as an unknown user. Only `theailaborg` was the real WordPress.org account; the second handle was a leftover from an earlier intent to register a sister account. No code changes.

= 2.1.6 =
* Plugin-directory listing: complete readme rewrite to improve discoverability on WordPress.org search and external search engines. Sharper short description, new "Why publishers and creators use TIP Protocol" value-prop block, new "Who is this for" industry-targeting block, expanded FAQ with high-volume questions (AI-generated content handling, C2PA / Content Credentials comparison, multi-author and multisite workflows, security model, network endpoints), updated tag list (ai disclosure, content provenance, content authenticity, trust badge, publisher). No code changes.
* Removed the local-development / Docker section from the user-facing readme. That content now lives only in the repository docs where it belongs; it was diluting the SEO signal of the public plugin page and listed local-development credentials that should not appear on a public listing.

= 2.1.5 =
* Security: Restructured the byline metabox nonce check (admin/class-tip-metabox.php) into the canonical isset → wp_verify_nonce → current_user_can sequence, with each gate an independent early-return so the conditional logic cannot be bypassed. Sanitized $_POST input at the boundary with explicit wp_unslash + is_array check before the per-entry sanitize_text_field loop.
* Security: Sanitized every $_SERVER access. includes/class-tip-webauthn.php and includes/class-tip-visitor-context.php now apply sanitize_text_field( wp_unslash( ... ) ) in a single expression so the WordPress coding-standard analyser sees the sanitization chain, and the phpcs:ignore comments that previously masked these reads have been removed.
* Security: Rewrote the legacy-slug admin redirect (admin/class-tip-admin.php redirect_legacy_menu_slug) to use an explicit allow-list of forwardable query params (tab, section, paged), each sanitized via sanitize_text_field and rawurlencoded before reaching add_query_arg. The previous raw $query_args = $_GET assignment is replaced. Capability check moved above all $_GET access so unauthorized requests never touch the superglobal.
* Security: Added a defence-in-depth current_user_can( theailab_set_origin ) gate to Theailab_Admin::build_editor_state() before reading $_GET['post']; absint() now wraps the post-ID lookup explicitly.
* Prefix uniqueness: Removed the legacy un-prefixed shortcode registrations ([tip-badge], [download_button]) and the legacy register_block_type call for tip/verification-badge. Replaced the alias approach with a the_content filter (Theailab_Public::rewrite_legacy_tokens) that rewrites legacy tokens to their prefixed names at render time, so existing post content authored against earlier versions keeps rendering after upgrade. Updated the Gutenberg JS to register theailab/verification-badge, theailab-origin-panel, and theailab-pre-publish-check (previously tip/verification-badge, tip-origin-panel, tip-pre-publish-check).
* No functional changes for end users.

= 2.1.4 =
* Compatibility: Bump "Tested up to" header to 7.0 (current WordPress release). No code changes; the WordPress.org automated scanner blocks uploads whose declared compatibility falls below the latest WP version, and this metadata bump unblocks the resubmission of the 2.1.3 prefix-uniqueness fixes.

= 2.1.3 =
* Compliance: Address WordPress.org Plugin Directory review feedback on prefix uniqueness. Three PHP-level identifiers carried short or generic prefixes that could collide with other plugins:
  * Bootstrap function `tip_protocol()` renamed to `theailab_tip_bootstrap()`. A guarded backward-compat alias preserves the old name for any third-party code or theme snippet that referenced it.
  * Gutenberg block namespace `tip/verification-badge` renamed to `theailab/verification-badge`. The legacy block name is also re-registered as an alias so existing post content that embeds the old block continues to render unchanged.
  * Admin menu slug `tip-protocol` (previously routed to admin.php?page=tip-protocol) was already moved to `theailab` in an earlier release; this version adds a 301 redirect from the legacy URL to the new one so bookmarked admin URLs keep working.
  * Shortcodes `[tip-badge]` and `[download_button]` continue to render via backward-compat aliases registered alongside the canonical `[theailab-tip-badge]` and `[theailab_download_button]` names.
* No functional changes for end users. Existing content keeps working; the renames are purely to satisfy the WordPress.org plugin uniqueness rule.
* Bump version forces a cache-buster URL refresh.

= 2.1.2 =
* UI fix: The scope picker dropdown clipped the descenders of "publishers" because the wp-components SelectControl forced a fixed height that conflicted with the picker's padding. Replaced the height constraint with `height: auto`, increased line-height to 1.5, switched to a custom-drawn navy chevron with explicit right-padding so the option text isn't crowded, and bumped vertical padding to 12px. The dropdown now renders the full option text cleanly at any zoom level.
* Sign reliability: Added a server-side self-verification step right after each ML-DSA signature is produced and before the registration request is sent to the TIP node. If the stored public key doesn't correspond to the private key the plugin just signed with — most often because a personal Author ID was imported with a derived (not original) public key — the user gets a precise local error pointing at the cause and remediation, instead of the opaque "Content signature verification failed" the node returns.
* Sign reliability: The registration payload sent to the TIP node now includes the `public_key` field. Without it, the node had to look up the TIP-ID's public key on the DAG to verify, which fails for newly-imported personal Author IDs that haven't been published to the network yet. Including the key gives the node a deterministic verification path; the node should still cross-check this against its DAG record when one exists.
* Sign reliability: When the TIP node rejects a registration with a "signature verification" error AND the local self-verify passed, the editor now surfaces a guided message explaining that the TIP-ID likely isn't yet published to the TIP DAG (and pointing the user at vp.theailab.org), instead of relaying the raw node error verbatim.
* Bump version forces a cache-buster URL refresh.

= 2.1.1 =
* UI: The "What kind of identity is this?" dropdown on the Connect-an-Identity card was easy to overlook even though it controls the most important decision in the import flow (publisher vs. personal). Wrapped it in a high-contrast picker with a colored border, gradient background, "REQUIRED" tag, large icon (🏢 / 👤), and a prominent uppercase label. The picker color-shifts to blue when "Site identity" is selected and green when "Personal Author ID" is selected, so the user gets instant visual confirmation that their choice registered.
* Bump version forces a cache-buster URL refresh.

= 2.1.0 =
* Feature: Optional WebAuthn / biometric step-up for personal-mode signing. Writers who choose to can now require a fingerprint, Face ID, Windows Hello, or hardware security-key tap before each post is signed with their personal Author TIP-ID. Configured per-user in WordPress profile, defaults OFF for everyone, and is never a barrier — publisher-mode signing, scheduled posts, WP-CLI, REST automation, and cron-driven workflows always bypass step-up. If a user enables the toggle but has no enrolled key, signing still proceeds; if they remove their last key, the toggle auto-disables.
* New REST surface under /theailab/v1/webauthn/ (status, register/begin, register/complete, auth/begin, auth/complete, required, credentials/remove). All routes are nonce-protected and operate only on the calling user's row.
* Server-side verifier uses native PHP openssl_verify() with public keys exported directly from the browser via AuthenticatorAttestationResponse.getPublicKey() — no third-party dependency, no CBOR parser, supports ES256 and RS256 algorithms, enforces userVerified flag and signCount monotonic check.
* Editor integration: Both classic and Gutenberg editors detect a 428 Precondition-Required response from /origin/register, run the WebAuthn assertion ceremony, and retry with the resulting single-use ticket. Errors are surfaced via the existing inline-status banner with clear remediation guidance.
* Security audit: WebAuthn step-up is a defense-in-depth layer over the existing capability/nonce model — it gates the API call but does not replace any existing check, and the actual content signature remains ML-DSA-65 over the canonical hash, server-side, exactly as before.

= 2.0.11 =
* Fix: When the TIP node returned a structured error response (e.g. `{"error":{"message":"…"}}`), the plugin's API client cast the nested object to a string and surfaced the literal word "Array" in the editor — users saw "Couldn't sign this post / Array" with no actual reason. The client now walks the common shapes (`error` as string or `{message,detail,description,code,reason,details[]}`, top-level `message`, plural `errors[]`) and falls back to a short raw body snippet or a status-code message, so the real reason from the node always reaches the user.
* Bump version forces a cache-buster URL refresh.

= 2.0.10 =
* Fix: Personal Author IDs exported from vp.theailab.org (DOB-locked `tip-key-export-v2` files) failed to import with `TIP-PKG-2 packages must declare vp_id.`. Personal identities are self-claimed and never carry a publisher review, so the importer no longer requires `vp_id` for `tip_id_type === "personal"`. Publisher and platform identities continue to require a well-formed `vp_id`, and personal exports that do include one are still validated against `THEAILAB_VP_ID_REGEX`.
* Bump version forces a cache-buster URL refresh.

= 2.0.9 =
* Apply the v2.0.8 inline status banner pattern to every action button across the admin UI: Connect identity, Inspect article, Verify domain (HTTP / DNS), Save settings (Network / Publishing defaults), Add/Save writer to editorial team, Refresh roster, classic-editor "Register Content as ..." button, and the Gutenberg sidebar Sign button. Errors and successes now appear as prominent red / green banners with icon + title + detail, immediately at the click site, instead of as small notices at the top of cards the user has scrolled past.
* New reusable `InlineStatus` React component centralizes the banner so all panels share one look and one set of accessibility semantics (`role="alert"` for errors with `aria-live="assertive"`, `role="status"` for successes with `aria-live="polite"`).
* Classic-editor metabox: status banner moved from below the Register button to above it (the user's eye is on the button), and upgraded from plain text to the same banner style.
* Bump version forces a cache-buster URL refresh.

= 2.0.8 =
* Fix two issues that surfaced after the v2.0.7 DOB-unlock improvements: (1) the synthetic post-unlock package shipped with `package_version: "tip-key-export-v2-unlocked"`, which the importer rejected with `Unsupported .tip.json package_version`. Synthetic packages now use the canonical `TIP-PKG-2` version string. (2) The import error notice rendered as a small banner at the top of the import card, far from the Connect button the user just clicked, so it was easy to miss. The error / success message now renders as a prominent red (or green) banner immediately above the Connect button with an icon, title, and dismiss button.
* Bump version forces a cache-buster URL refresh.

= 2.0.7 =
* Fix import for `.tip.json` files where `publicKey` is empty in the clear (a known property of vp.theailab.org's `tip-key-export-v2` exports when the user's record had no stored public key at export time). The server-side `/identity/import` endpoint now derives the public key from the private key via the bundled `bin/tip-ml-dsa.mjs` helper (`action: derive-public`) before passing the package to the key manager. The derived key is injected at top-level `public_key` and into `identity.public_key` (mirroring the original path) so downstream parsing succeeds whether the importer reads from one or the other.
* Loosen the client-side dropzone parser to accept files with an empty public_key value as long as a private_key is present. The Connect button is no longer disabled by this condition; the server completes the derivation during import.
* Bump version forces a cache-buster URL refresh.

= 2.0.6 =
* Fix DOB unlock for `tip-key-export-v2` files. After reading the actual producer code at vp.theailab.org (`settings.html` + `src/crypto.js`), the format is now pinned exactly: byte layout `[salt:16][iv:12][ct+gcm-tag]`, PBKDF2-SHA-256 with 200,000 iterations, AES-256-GCM, password is the user's date of birth normalized to 8 digits in `MMDDYYYY` order, and the plaintext is the raw private-key hex string (not a JSON wrapper). Previous fallback-by-trying-many-layouts code is replaced with this single deterministic decryption. Wrong DOB now produces a clear "That date of birth doesn't unlock this file" message.
* Replace the `<input type="date">` (which forced YYYY-MM-DD) with a `<input type="text" placeholder="MM/DD/YYYY">` that auto-inserts slashes as the user types — same behavior as the producer's input field. The Unlock button stays disabled until exactly 8 digits are entered.
* Decrypted private-key hex is wrapped in a synthetic parser-friendly package using the `tip_id` and `publicKey` fields from the outer file; the existing parser then takes over and unlocks the Connect button as it would for any `.tip.json` file.
* Gutenberg sidebar: panel renamed from "TIP Origin" to "TIP Protocol". Field label changed from "Origin code" to "How was this written? Human or AI". Option labels (`OH - Original Human / AA - AI-Assisted / AG - AI-Generated / MX - Mixed`) and the Register button label remain unchanged. The "Next update type" field is moved into a collapsible "Advanced options" disclosure (collapsed by default) so writers see only the question that matters.
* Bump version forces a cache-buster URL refresh.

= 2.0.5 =
* Add support for the new `tip-key-export-v2` personal Author ID file format from vp.theailab.org. These files are encrypted with the user's date of birth using PBKDF2 (SHA-256, 200,000 iterations) + AES-256-GCM. The plugin now detects the locked format, shows a dedicated "This file is locked" card with a date-of-birth input, decrypts the file client-side via Web Crypto API, and hands the unwrapped public_key + private_key to the existing import flow. The DOB never leaves the browser. Site identity files (publishers) continue to use the existing flat / `identity.*` format with no DOB step.
* Replace the muted yellow warning notice with a prominent RED error banner for hard parse failures (invalid JSON, missing required fields, encrypted blob without unlock support). The error appears immediately under the dropzone with a red icon, red title, and the diagnostic detail.
* Bump version forces a cache-buster URL refresh.

= 2.0.4 =
* Connect button stayed disabled because the .tip.json parser only checked 6 hard-coded paths for the public key and private key. Expanded the parser to recognize 21 paths each (top-level snake/camel/lowercase variants, `identity.*`, `keys.*`, `data.*`, `key_package.*` wrappers). Now handles TIP-PKG-1, TIP-PKG-2, vp.theailab.org exports, AI-Lab-issued packages, and reasonable variants of all of the above.
* When a required field is missing, the error message now lists the fields the parser actually detected in the file, instead of just saying "field X missing". This makes it possible to diagnose schema mismatches without opening DevTools.
* Bump version forces a cache-buster URL refresh.

= 2.0.3 =
* Fix Connect button not working after .tip.json upload. The double-prompt confirmation modal was using `wp.components.Modal`, which can render as undefined in certain wp-components versions and prevent the entire panel from re-rendering. Replaced with a self-contained `<div>` overlay using pure CSS (no wp-components dependency), so the modal always renders regardless of host WordPress version.
* The modal still supports clicking the backdrop to dismiss, an explicit close button, ARIA dialog semantics, and the same content (file name, detected Author ID, identity type, scope-mismatch warning).
* Bump version forces a cache-buster URL refresh.

= 2.0.2 =
* Fix REST nonce parameter mismatch that broke every nonce-protected admin call (identity import, identity remove, contributor CRUD, byline save, origin save, "Register Content" button, settings save, domain verify). The PHP validator was renamed to read `theailab_nonce` in v1.5.0 but the JavaScript continued to send `tip_nonce`. Renamed the parameter on the JS side in `admin/js/tip-admin.js`, `admin/js/tip-gutenberg.js`, and `admin/js/tip-origin-classic.js`.
* Add `tests/test-rest-nonce-consistency.php` (10 assertions) so this regression cannot recur silently. Test suite is now 344 assertions across 11 standalone files.
* Add a double-prompt confirmation modal before the .tip.json import API call fires. The modal shows the file name, the detected Author ID and identity type from the file, the scope the user selected (Site identity vs Personal Author ID), and a plain-language explanation of what each scope does. When the file's identity type does not match the chosen scope (e.g., a personal-typed file imported as a site identity, or a publisher-typed file imported as personal), the modal displays a warning notice explaining what the mismatch will do. The user must explicitly confirm before the import proceeds.
* Bump version (also forces cache-buster URL refresh for any browsers still holding the broken 2.0.1 JS).

= 2.0.1 =
* Address WordPress.org plugin review feedback on prefix uniqueness:
  - Remove legacy unprefixed shortcode aliases `[tip-badge]` and `[download_button]`. The canonical shortcodes `[theailab-tip-badge]` and `[theailab_download_button]` continue to work.
  - Rename admin menu slug from `tip-protocol` to `theailab` (admin URL is now `wp-admin/admin.php?page=theailab`).
  - Rename the screen-hook check accordingly.
  - Rename WordPress script and style handles from `tip-protocol-*` to `theailab-*` (`theailab-admin`, `theailab-editor`, `theailab-origin-classic`, `theailab-gutenberg`, `theailab-public`, `theailab-badge`).
  - Rename Privacy Exporter / Eraser registration key from `tip-protocol-identity` to `theailab-identity`.
  - Rename admin app DOM root id from `tip-protocol-admin-root` to `theailab-admin-root`.
  - Rename classic-editor metabox ID, form-field IDs, and `name` attributes from `tip-origin-*` and `tip_origin_*` to `theailab-origin-*` and `theailab_origin_*`.
* Verify all REST permission_callbacks use `current_user_can()` with prefixed custom capabilities (`theailab_manage_settings`, `theailab_register_content`, `theailab_set_origin`, `theailab_view_dashboard`).
* No functional changes: the protocol, signing, byline picker, contributor registry, and CNA-2.2 wire format are unchanged.

= 2.0.0 =
* Add **Contributor Registry**: an org-scoped registry of personal TIP-IDs (journalists, editors, photographers, freelancers) under a publisher / platform identity. Manage from Settings → Contributors. Each contributor record carries a TIP-ID, display name, role, optional WordPress user link, key-custody mode (attribution-only or self-signing), and optional public key.
* Add **per-post byline picker**: a new section in the classic-editor metabox and the Gutenberg sidebar lets editors choose a primary author plus optional co-authors from the contributor registry. The selection is persisted as ordered post meta `_theailab_author_tip_ids`.
* Auto-fill the byline from the WordPress post-author when that user has a contributor-registry mapping. Editors can override.
* Bump canonical normalization to **CNA-2.2**: the publisher-signed payload now carries an ordered `authors[]` array (primary at index 0) with per-author `key_mode` and `signed` flags. Solo-author posts and authorless hosted content remain supported.
* Add CNA-2.2 crypto helpers: `build_cna_2_2_payload()`, `sign_publisher_v3()`, `verify_publisher_v3()`, `sign_co_signer()`, `verify_co_signer()`. CNA-2.1 verification preserved for backward compatibility (`THEAILAB_CNA_VERSION_PREVIOUS`).
* Add `co_signatures[]` collection: when a contributor is in self-signing mode, the registrar records a pending co-signature placeholder so the node can ingest the contributor's own ML-DSA-65 signature out-of-band.
* Add REST routes:
  * `GET/POST /theailab/v1/contributors` — list or add a contributor
  * `PATCH/DELETE /theailab/v1/contributors/{tip_id}` — update or remove
  * `GET/POST /theailab/v1/post-authors/{post_id}` — read or set the byline
* Extend public output: schema.org JSON-LD `author` becomes an array of Person objects when multiple authors are bound; new meta tags `tip:co-authors` and `tip:authors-count`.
* Add new tests: `test-contributor-registry.php` (55 assertions), `test-cna-2-2-payload.php` (10 assertions covering canonical determinism + alphabetical key ordering with `authors[]`).
* Register `_theailab_author_tip_ids` post meta with REST schema so the Gutenberg sidebar can read/write it through `core/editor`.
* Storage: new option `theailab_contributors` (autoload no), new user meta `theailab_contributor_tip_id` (back-pointer for fast WP-user → contributor lookup).

= 1.5.0 =
* Rename all plugin-internal identifiers from `tip_` / `TIP_` to `theailab_` / `THEAILAB_` to comply with WordPress.org plugin guideline on prefix uniqueness (4+ char distinct prefix). Affects classes, constants, functions, options, capabilities, post meta keys, user meta keys, cron events, transients, REST namespace, and database tables.
* Add automatic one-time migration in the activator: copies legacy `tip_*` options, capabilities, post meta, user meta to the new `theailab_*` keys; renames `wp_tip_content` → `wp_theailab_content` and `wp_tip_transactions` → `wp_theailab_transactions`; unschedules legacy cron events.
* Register backward-compatible shortcode aliases: `[tip-badge]` continues to render alongside the new canonical `[theailab-tip-badge]`.
* Sanitize `$_SERVER` reads with `sanitize_text_field` immediately after `wp_unslash` to satisfy the WordPress coding standard's "sanitize early" rule for superglobals.
* Rename `WP_Error` codes from the legacy prefix to the canonical prefix.
* Preserve TIP Protocol JSON wire-format field names (`tip_id`, `tip_id_type`, `tip_ids`, `tip_key`) which are protocol-spec values defined in the patent and not plugin-internal identifiers.

= 1.4.0 =
* Add typed identity taxonomy (`personal`, `publisher`, `platform`) with reserved values for future Phase 2 release
* Add relational `attribution_mode` field (`self`, `employed`, `hosted`) carried in CNA-2.1 canonical content payload
* Add Signing Officer roster awareness with central genesis-node policy (`disabled` / `optional` / `required`); Phase 1 default is `optional` so manual publisher onboarding works without a roster
* Add per-post `edit_post` / `read_post` permission checks to `/tip/v1/register`, `/update`, `/status`, `/origin/save`, `/origin/register`
* Move temporary crypto scratch files from `get_temp_dir()` to `wp-content/uploads/tip-protocol-private/` with `.htaccess`, `web.config`, and `index.php` deny files plus chmod 0600/0700
* Drop `JSON_UNESCAPED_SLASHES` from `application/tip+json` and `application/ld+json` script-tag output to prevent `</script>` break-out
* Fix vendored `@noble/post-quantum` ML-DSA-65 imports so the post-quantum signing helper resolves dependencies without `node_modules`
* Add identity removal: REST endpoint `/tip/v1/identity/remove` plus admin UI controls for both Publisher and Creator identities, with typed-confirmation guard for the destructive Publisher case
* Add fluid typography across the admin UI; new `560px` mobile breakpoint; `:focus-visible`, `prefers-reduced-motion`, and `prefers-contrast` support; WCAG 2.5.5 minimum touch-target sizing
* Remove `load_plugin_textdomain()` (auto-loaded by WordPress 4.6+ for directory-hosted plugins)
* Bump `Contributors` to include `theailaborg`

= 1.3.0 =
* Align WordPress registration hashing with the current developer hashing guide
* Build the content hash input from a structured WordPress content string instead of raw rendered body text alone
* Add guide-compliant body cleaning with `tipmediaimage`, `tipmediavideo`, and `tipmediaaudio` indicators
* Add `CNA-MIX-1` top-level content hashing when attached media participates in the registration hash
* Store and expose `text_canonical_hash` plus attached-media hash fields in the local record and public TIP manifest
* Add test coverage for content-string ordering, guide body cleaning, and mixed-hash composition

= 1.2.3 =
* Change TIP identity setup to import-only onboarding for reviewed publisher and VP creator identities
* Add `.tip.json` package import support and clearer rejection for browser-connected WebAuthn blobs
* Add local sign-and-verify checks for imported key material before WordPress stores it
* Upgrade key-at-rest encryption to a TIP-ID-specific SHAKE-256 plus PBKDF2 AES-256-GCM payload while keeping backward decryption support
* Emit the canonical `registered_url` directly in `X-TIP-Content-Bind`

= 1.0.2 =
* Add richer fallback schema and merge TIP provenance into Yoast SEO and Rank Math graphs without duplicating article JSON-LD
* Improve badge and public trust UI accessibility with stronger semantics, focus handling, and keyboard-safe details
* Add privacy-policy guidance plus personal data exporter and eraser integration for stored user TIP identity data
* Bundle the official TIP mark locally for admin branding and WordPress.org packaging
* Default the public badge script to the bundled local copy and add release packaging hardening files

= 1.0.1 =
* Simplify plugin branding to TIP Protocol with Publisher Provenance for WP as the subtitle
* Align content hashing and verification badges with the CNA-2 registration spec

= 1.0.0 =
* Initial release
