From d7670d555a0ab871e410feda992428bda1636313 Mon Sep 17 00:00:00 2001 From: henry-idingo Date: Thu, 3 Sep 2026 11:31:59 +0200 Subject: [PATCH 1/9] Place the block CiteCue composed for the page you already have MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `/seo-head` response has carried a `body` half since Phase 3 R14 — the page-enhancement block, a collapsed facts-and-FAQ section composed from the customer's own answered facts for a page they already have. Until now no plugin declared `body_blocks`, so every WordPress site was sent `body: ''` and the feature reached Cloudflare Worker installs only. Declared and injected in one change, deliberately. The capability is what makes CiteCue start sending real block markup, so a release that announced it without placing it would put markup on a live customer page that nothing renders. The block rides the response already fetched for the head, so an enhanced page costs no extra request and inherits the rules that path already keeps: cache-only on the render, nothing fetched at all where injection is off. The two halves are otherwise independent — either can be empty while the other is not, and `refresh()` no longer reads an empty `head` as an empty answer, which would have thrown every body-only block away. Placed verbatim, and that is the contract rather than an oversight: the block is sanitized at composition and capped on the wire, and re-escaping it would print its tags as text while `wpautop` or the content filters would run a shortcode quoted inside an answer. The trust is put where it can be reasoned about instead — the size cap re-checked here, the marker check that refuses to place a second copy on a page that already carries one, and the last `` rather than one quoted in a code sample. Existing connections need one reconnect and now say so. CiteCue records capabilities at connect time and withholds anything a plugin never announced, and the old prompt compared one setting, which had not changed — so without this the feature would have reached nobody who was already a customer, and nothing would have said why. Also asks for the site-wide identity baseline. This plugin prints no Organization node of its own, and the head merge drops CiteCue's JSON-LD outright when the page carries any, so it can never become a competing one. Co-Authored-By: Claude Opus 5 --- citecue.php | 4 +- includes/class-citecue-admin.php | 1 + includes/class-citecue-api-client.php | 24 ++- includes/class-citecue-cache.php | 29 ++- includes/class-citecue-connect.php | 50 +++-- includes/class-citecue-seo-head.php | 175 ++++++++++++++--- includes/class-citecue-settings.php | 121 ++++++++++-- readme.txt | 14 +- tests/cases/test-connect.php | 116 +++++++++++ tests/cases/test-seo-head-delivery.php | 255 +++++++++++++++++++++++++ 10 files changed, 719 insertions(+), 70 deletions(-) diff --git a/citecue.php b/citecue.php index 71dc938..656f225 100644 --- a/citecue.php +++ b/citecue.php @@ -3,7 +3,7 @@ * Plugin Name: CiteCue AI Auto-Fix * Plugin URI: https://github.com/citecue/wordpress-plugin * Description: Serves CiteCue-optimized versions of your pages to AI bots and crawlers, adds CiteCue's enriched SEO metadata to your live pages, publishes your llms.txt, and lets CiteCue push brand-building draft content into WordPress. - * Version: 1.1.2 + * Version: 1.2.0 * Requires at least: 5.8 * Requires PHP: 7.4 * Author: CiteCue @@ -138,7 +138,7 @@ return; } -define( 'CITECUE_VERSION', '1.1.2' ); +define( 'CITECUE_VERSION', '1.2.0' ); define( 'CITECUE_PLUGIN_FILE', __FILE__ ); define( 'CITECUE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); diff --git a/includes/class-citecue-admin.php b/includes/class-citecue-admin.php index 78e0986..b2aae54 100644 --- a/includes/class-citecue-admin.php +++ b/includes/class-citecue-admin.php @@ -654,6 +654,7 @@ private function render_connected() {

+ $result['status'], 'head' => $head, + 'body' => $body, ); } diff --git a/includes/class-citecue-cache.php b/includes/class-citecue-cache.php index 26dc9de..e059659 100644 --- a/includes/class-citecue-cache.php +++ b/includes/class-citecue-cache.php @@ -255,28 +255,47 @@ private function seo_head_key( $url ) { } /** - * Cached SEO head block for a URL, or null. + * Cached SEO head block and page-enhancement block for a URL, or null. + * + * `body` is defaulted rather than required, because an entry written by an + * earlier version of the plugin carries only `block` — and those entries + * outlive the upgrade by up to BODY_TTL. Treating a missing key as "no + * block" keeps them readable instead of discarding a day of warm cache on + * every site the moment it updates. * * @param string $url Absolute page URL. - * @return array{block:string,cached_at:int}|null + * @return array{block:string,body:string,cached_at:int}|null */ public function get_seo_head( $url ) { $hit = get_transient( $this->seo_head_key( $url ) ); - return ( is_array( $hit ) && isset( $hit['block'] ) ) ? $hit : null; + if ( ! is_array( $hit ) || ! isset( $hit['block'] ) ) { + return null; + } + if ( ! isset( $hit['body'] ) ) { + $hit['body'] = ''; + } + return $hit; } /** - * Stores a URL's SEO head block. + * Stores a URL's SEO head block and page-enhancement block. + * + * Both halves share one entry because they arrive in one response and are + * evicted by the same events: keying them apart would let a page keep a + * block from before its audience was switched off, which is the exact + * failure delete_seo_head() exists to prevent. * * @param string $url Absolute page URL. * @param string $block Head markup. + * @param string $body Page-enhancement markup for before ``. * @return void */ - public function set_seo_head( $url, $block ) { + public function set_seo_head( $url, $block, $body = '' ) { set_transient( $this->seo_head_key( $url ), array( 'block' => (string) $block, + 'body' => (string) $body, 'cached_at' => time(), ), self::BODY_TTL diff --git a/includes/class-citecue-connect.php b/includes/class-citecue-connect.php index a0af591..89994c1 100644 --- a/includes/class-citecue-connect.php +++ b/includes/class-citecue-connect.php @@ -143,22 +143,30 @@ public function verify_state( $state ) { public function claim( $code ) { $settings = $this->plugin->settings; - // The capability, not the ambition: CiteCue uses this to decide whether + // The capability, not the ambition: CiteCue uses these to decide whether // `seoAudience: 'all'` is a promise this channel can keep, and a site // that has injection switched off keeps it no better than a plugin that // cannot inject at all. Reporting the live setting is what stops the // app badging a fix "Live" over a head nothing writes to. - $seo_head = (bool) $settings->get( 'seo_head_enabled' ); + // + // The set is built by Citecue_Settings::declared_capabilities(), which + // the reconnect prompt reads too — see needs_seo_head_reconnect(). Every + // name in it gates something CiteCue would otherwise not send, and + // absence always reads as "cannot", so an old plugin is never handed + // markup it would not place. + $seo_head = (bool) $settings->get( 'seo_head_enabled' ); + $capabilities = $settings->declared_capabilities(); $result = $this->plugin->api->claim_connect_code( $code, - array( - 'site_url' => home_url( '/' ), - 'rest_url' => rest_url( 'citecue/v1/' ), - 'ingest_secret' => $settings->ensure_ingest_secret(), - 'plugin_version' => CITECUE_VERSION, - 'woocommerce' => class_exists( 'WooCommerce' ), - 'seo_head' => $seo_head, + array_merge( + array( + 'site_url' => home_url( '/' ), + 'rest_url' => rest_url( 'citecue/v1/' ), + 'ingest_secret' => $settings->ensure_ingest_secret(), + 'plugin_version' => CITECUE_VERSION, + ), + $capabilities ) ); @@ -167,13 +175,16 @@ public function claim( $code ) { } $update = array( - 'api_key' => $result['apiKey'], - 'public_key' => $result['publicKey'], - 'project_domain' => $result['domain'], + 'api_key' => $result['apiKey'], + 'public_key' => $result['publicKey'], + 'project_domain' => $result['domain'], // Only after the exchange succeeded: a failed claim wrote nothing // on CiteCue's side, so recording it here would silence the // reconnect prompt for a capability the app never learned about. - 'seo_head_reported' => $seo_head, + 'seo_head_reported' => $seo_head, + // Same rule, for the set as a whole. Recorded from the map that was + // actually sent, so the two can never drift apart here. + 'capabilities_reported' => $settings->active_delivery_capabilities(), ); // CiteCue's connect screen is where the customer is told that content @@ -212,15 +223,16 @@ public function disconnect() { // flag — but sanitize() only runs once register_setting() has, // and the empty value is what a write that bypasses the filter // has to see. Either path must end up with no key. - 'api_key' => '', - 'api_key_clear' => 1, - 'public_key' => '', - 'project_domain' => '', - 'ingest_enabled' => false, + 'api_key' => '', + 'api_key_clear' => 1, + 'public_key' => '', + 'project_domain' => '', + 'ingest_enabled' => false, // Back to "never reported": the next connection mints a new key // with its own capabilities, and what the old one recorded says // nothing about it. - 'seo_head_reported' => null, + 'seo_head_reported' => null, + 'capabilities_reported' => null, ) ); diff --git a/includes/class-citecue-seo-head.php b/includes/class-citecue-seo-head.php index 40a1f0e..45f9ab0 100644 --- a/includes/class-citecue-seo-head.php +++ b/includes/class-citecue-seo-head.php @@ -1,13 +1,18 @@ `. That block is the only markup this class puts where a reader can + * see it, and it arrives already sanitized and size-capped (see + * self::inject_body()). CiteCue only serves a block for `enriched` pages in `all` * audience mode, which is what keeps the two apart: enriched markup is * content-parity and additive, so adding it is not cloaking, whereas serving a * rewritten document to a human would be. @@ -84,6 +89,30 @@ class Citecue_Seo_Head { */ const CAPTURE_PRIORITY = PHP_INT_MAX; + /** + * The attribute pair CiteCue marks a page-enhancement section with, matched + * loosely enough to recognise the section however it was quoted. + * + * This is a "somebody already placed it" check, not a parse: on a site whose + * origin is fronted by CiteCue's Worker, or whose post content already + * carries the section, the block is in the document before this plugin sees + * it — and a second copy is the one failure mode a visitor actually notices. + */ + const BLOCK_MARKER_PATTERN = '#data-citecue\s*=\s*["\']?page-enhancement#i'; + + /** + * Largest page-enhancement block that may be injected, in bytes. Mirrors + * the delivery API's own MAX_BLOCK_BYTES, which already refuses to send a + * larger one. + * + * Enforcing it again here is not distrust of the endpoint: a composed block + * is a handful of facts and FAQ entries and cannot legitimately approach + * 32 KB, so a block that does is a generation bug — and this is the + * difference between that bug costing one page and it costing every page + * the cache has warmed. + */ + const MAX_BLOCK_BYTES = 32768; + /** * `` relations that may be injected, and `` values are escaped * into a tag we build ourselves. Anything else in the block is dropped: @@ -216,22 +245,26 @@ public function finish_capture( $output, $phase ) { } /** - * The rendered document with CiteCue's tags added to its head — the one - * place the injection happens, shared by both mechanisms above. + * The rendered document with CiteCue's markup added to it — the one place + * the injection happens, shared by both mechanisms above. + * + * Two halves land here, in two places, under two different rules: the head + * tags immediately before ``, and the page-enhancement block + * immediately before the last ``. * * Everything before `` is what the slot check reads, and the tags go - * immediately before it. Scoping both to the head is not tidiness: an + * immediately before it. Scoping the check to the head is not tidiness: an * inline SVG in the body carries a ``, `<meta itemprop>` is legal in * body content, and a page that quotes markup in a code sample contains * whatever it quotes — so a document-wide scan would read slots as occupied * that no browser or crawler ever reads as page metadata, and CiteCue would * silently stop filling them. * - * A response with no `</head>` is returned exactly as it arrived: a JSON or - * CSV export served from a page URL, a fragment, a document another plugin - * replaced wholesale. There is no head to fill gaps in, and guessing where - * one would have gone is how a plugin corrupts a response it did not - * understand. + * A response missing either close is returned unchanged at that half: a + * JSON or CSV export served from a page URL, a fragment, a document another + * plugin replaced wholesale. There is nothing to fill gaps in, and guessing + * where the close would have gone is how a plugin corrupts a response it + * did not understand. * * @param string $html Rendered document. * @return string @@ -249,12 +282,34 @@ public function enhance( $html ) { return $html; } + // Independent halves, deliberately. A document can have a head and no + // body close (a fragment), or a body and a head another plugin already + // filled, and neither is a reason to withhold the other — they answer + // to different rules and land in different places. + $html = self::inject_head( $html, $decision['block'] ); + $html = self::inject_body( $html, $decision['body'] ); + + return $html; + } + + /** + * The document with CiteCue's gap-filling tags spliced into its head. + * + * @param string $html Rendered document. + * @param string $block CiteCue head block. + * @return string + */ + private static function inject_head( $html, $block ) { + if ( '' === $block ) { + return $html; + } + if ( ! preg_match( '#</head\s*>#i', $html, $match, PREG_OFFSET_CAPTURE ) ) { return $html; } $at = (int) $match[0][1]; - $tags = self::merge( substr( $html, 0, $at ), $decision['block'] ); + $tags = self::merge( substr( $html, 0, $at ), $block ); if ( ! $tags ) { return $html; @@ -268,12 +323,63 @@ public function enhance( $html ) { . substr( $html, $at ); } + /** + * The document with CiteCue's page-enhancement block spliced in before its + * closing `</body>`. + * + * The block is passed through as it arrived, and that is the contract + * rather than an oversight: unlike the head tags — which are rebuilt from + * parsed values because they are assembled from many small elements whose + * shapes must be constrained — the body block is a single section + * sanitized at composition on CiteCue's side and capped on the wire. + * Re-escaping it would print its markup as visible text; running it through + * `wpautop` or the content filters would let a shortcode inside a quoted + * FAQ answer execute. So it is spliced verbatim, and the trust is placed + * where it can be reasoned about: the size cap and the marker check below, + * plus the fact that it only ever arrives over an authenticated request to + * the site's own configured project. + * + * The LAST `</body>` is the document's own. A page that quotes markup in a + * code sample contains an earlier one, and splicing at that would put the + * section inside the sample. + * + * @param string $html Rendered document. + * @param string $body CiteCue page-enhancement block. + * @return string + */ + private static function inject_body( $html, $body ) { + if ( '' === $body ) { + return $html; + } + + // strlen() is bytes, which is what the cap is stated in — the block is + // UTF-8 and a character count would under-count it by up to 4x. + if ( strlen( $body ) > self::MAX_BLOCK_BYTES ) { + return $html; + } + + // Already placed — by the Worker in front of this site, or in the post + // content itself. Injecting now would show the customer two. + if ( preg_match( self::BLOCK_MARKER_PATTERN, $html ) ) { + return $html; + } + + if ( ! preg_match_all( '#</body\s*>#i', $html, $matches, PREG_OFFSET_CAPTURE ) ) { + return $html; + } + + $last = end( $matches[0] ); + $at = (int) $last[1]; + + return substr( $html, 0, $at ) . $body . "\n" . substr( $html, $at ); + } + /** * Whether this request should be injected into, and with what — reading the * cache only, never the network. The testable counterpart of the capture, * mirroring the decide()/serve() split in Citecue_Proxy. * - * @return array{inject:bool,block:string,reason:string} + * @return array{inject:bool,block:string,body:string,reason:string} */ public function decide() { $settings = $this->plugin->settings; @@ -290,7 +396,11 @@ public function decide() { } /** - * Filters whether to inject CiteCue's SEO head into this page. + * Filters whether to inject CiteCue's markup into this page. + * + * Governs BOTH halves — the head tags and the page-enhancement block — + * because both arrive in the one response this decision fetches, and a + * page somebody has excluded is excluded from all of it. * * @param bool $should_inject Default true. * @param string $url URL the block is looked up by. @@ -303,7 +413,7 @@ public function decide() { $cached = $cache->get_seo_head( $url ); if ( $cached && $cache->is_fresh( $cached, self::FRESH_SECONDS ) ) { - return self::block( $cached['block'], 'cached' ); + return self::block( $cached['block'], $cached['body'], 'cached' ); } // CiteCue recently said it has nothing for this URL. Unlike the crawler @@ -318,7 +428,7 @@ public function decide() { // Stale-while-revalidate: a day-old block is still this page's own // metadata, and withholding it while the refresh runs would blank the // tags on every page during a CiteCue outage. - return $cached ? self::block( $cached['block'], 'stale' ) : self::skip( 'no-cache' ); + return $cached ? self::block( $cached['block'], $cached['body'], 'stale' ) : self::skip( 'no-cache' ); } /** @@ -356,15 +466,20 @@ public function refresh( $url ) { switch ( $response['status'] ) { case 200: - if ( '' === $response['head'] ) { - // A 200 carrying no block is a payload we do not understand. - // Treat it as "nothing to inject" rather than caching an - // empty string that would read as a valid block. + // Either half alone is a complete answer. The endpoint sends + // 204 when it has neither, so a 200 with an empty `head` is a + // page whose enhancement block is all there is to inject — + // testing `head` alone here would throw that block away and + // cache a miss over it. + if ( '' === $response['head'] && '' === $response['body'] ) { + // A 200 carrying nothing at all is a payload we do not + // understand. Treat it as "nothing to inject" rather than + // caching empty strings that would read as a valid block. $cache->delete_seo_head( $url ); $cache->set_seo_head_miss( $url ); return 'empty'; } - $cache->set_seo_head( $url, $response['head'] ); + $cache->set_seo_head( $url, $response['head'], $response['body'] ); return 'fresh'; case 204: @@ -818,6 +933,7 @@ private static function skip( $reason ) { return array( 'inject' => false, 'block' => '', + 'body' => '', 'reason' => $reason, ); } @@ -825,14 +941,23 @@ private static function skip( $reason ) { /** * An "inject this block" decision. * + * `inject` is what arranges the capture, so it asks whether there is + * anything at all to place — either half alone is reason enough to buffer, + * and neither means the page streams untouched. + * * @param string $block CiteCue head block. + * @param string $body CiteCue page-enhancement block. * @param string $reason Where the block came from (diagnostic only). - * @return array{inject:bool,block:string,reason:string} + * @return array{inject:bool,block:string,body:string,reason:string} */ - private static function block( $block, $reason ) { + private static function block( $block, $body, $reason ) { + $block = (string) $block; + $body = (string) $body; + return array( - 'inject' => true, - 'block' => (string) $block, + 'inject' => ( '' !== $block || '' !== $body ), + 'block' => $block, + 'body' => $body, 'reason' => $reason, ); } diff --git a/includes/class-citecue-settings.php b/includes/class-citecue-settings.php index 1061071..937f039 100644 --- a/includes/class-citecue-settings.php +++ b/includes/class-citecue-settings.php @@ -37,26 +37,32 @@ class Citecue_Settings { public static function defaults() { return array( // Connection. - 'api_base' => self::DEFAULT_API_BASE, - 'api_key' => '', - 'public_key' => '', - 'project_domain' => '', + 'api_base' => self::DEFAULT_API_BASE, + 'api_key' => '', + 'public_key' => '', + 'project_domain' => '', // Delivery. - 'serve_enabled' => true, - 'llms_txt_enabled' => true, - 'seo_head_enabled' => true, + 'serve_enabled' => true, + 'llms_txt_enabled' => true, + 'seo_head_enabled' => true, // The value of seo_head_enabled last reported to CiteCue, or null // if this site has never reported one. CiteCue records the // capability on the API key at connect time and has no other way to // learn it, so this is how the settings screen knows to ask for a // reconnect — see needs_seo_head_reconnect(). - 'seo_head_reported' => null, + 'seo_head_reported' => null, + // The capability names last reported to CiteCue, or null if this + // site has never reported any. Kept alongside seo_head_reported + // rather than replacing it: that one tracks a SETTING the customer + // can toggle, this one tracks what this BUILD of the plugin is able + // to do, and only one of them changes when the plugin updates. + 'capabilities_reported' => null, // Content ingest (CiteCue -> WordPress post creation). - 'ingest_enabled' => false, - 'ingest_secret' => '', - 'ingest_post_status' => 'draft', - 'ingest_post_type' => 'post', - 'ingest_author' => 0, + 'ingest_enabled' => false, + 'ingest_secret' => '', + 'ingest_post_status' => 'draft', + 'ingest_post_type' => 'post', + 'ingest_author' => 0, ); } @@ -215,7 +221,81 @@ public function needs_seo_head_reconnect() { $reported = $this->get( 'seo_head_reported' ); $known = null === $reported ? false : (bool) $reported; - return (bool) $this->get( 'seo_head_enabled' ) !== $known; + if ( (bool) $this->get( 'seo_head_enabled' ) !== $known ) { + return true; + } + + $declared = $this->get( 'capabilities_reported' ); + + // Never reported a set at all: an install connected before this plugin + // declared capabilities by name. It is under-claiming everything it can + // now do, so it needs a reconnect the moment there is anything to + // claim — and needs no reconnect when there is not. + if ( ! is_array( $declared ) ) { + return array() !== $this->active_delivery_capabilities(); + } + + return $this->active_delivery_capabilities() !== $declared; + } + + /** + * The capability flags sent to CiteCue on the connect claim. + * + * One definition, read by the claim that reports them and by the drift + * check above that notices when they have gone stale. Two lists would + * disagree eventually, and the failure would be silent in the worst + * direction: a capability announced and then never re-announced reads to + * CiteCue as one the plugin still has. + * + * The three delivery capabilities all ride `seo_head_enabled`, because they + * all arrive in the one `/seo-head` response that setting governs — a site + * with injection switched off fetches nothing, so it can place neither a + * head tag nor a body block, and claiming otherwise is exactly the + * over-claim the capability exists to prevent. + * + * `seo_head_baseline` is safe for this plugin to ask for: it prints no + * `Organization` node of its own anywhere, and the head merge drops + * CiteCue's JSON-LD outright when the page already carries any, so the + * site-wide identity block can never become a second competing one. + * + * @return array<string,bool> + */ + public function declared_capabilities() { + $seo_head = (bool) $this->get( 'seo_head_enabled' ); + + return array( + 'woocommerce' => class_exists( 'WooCommerce' ), + 'seo_head' => $seo_head, + 'body_blocks' => $seo_head, + 'seo_head_baseline' => $seo_head, + ); + } + + /** + * The names of the declared capabilities that gate what CiteCue SENDS, + * sorted, for comparison against what was last reported. + * + * `woocommerce` is deliberately not among them. It gates nothing on the + * delivery path — CiteCue records it for reporting only — so a customer who + * installs WooCommerce after connecting has stale information on their key, + * not a broken feature, and prompting them to reconnect over it would be + * nagging about nothing. + * + * @return string[] + */ + public function active_delivery_capabilities() { + $declared = $this->declared_capabilities(); + $names = array(); + + foreach ( array( 'seo_head', 'body_blocks', 'seo_head_baseline' ) as $name ) { + if ( ! empty( $declared[ $name ] ) ) { + $names[] = $name; + } + } + + sort( $names ); + + return $names; } /** @@ -310,6 +390,19 @@ public function sanitize( $input ) { if ( array_key_exists( 'seo_head_reported', $input ) ) { $out['seo_head_reported'] = null === $input['seo_head_reported'] ? null : (bool) $input['seo_head_reported']; } + // Same tri-state, and normalized to a sorted list of names so that the + // comparison in needs_seo_head_reconnect() cannot be tripped by key + // order alone. + if ( array_key_exists( 'capabilities_reported', $input ) ) { + $reported = $input['capabilities_reported']; + if ( null === $reported ) { + $out['capabilities_reported'] = null; + } else { + $names = array_values( array_filter( array_map( 'strval', (array) $reported ), 'strlen' ) ); + sort( $names ); + $out['capabilities_reported'] = $names; + } + } // A changed key may fix a previous auth failure; let serving retry now. if ( $out['api_key'] !== $current['api_key'] ) { diff --git a/readme.txt b/readme.txt index ecce623..382c4ff 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: ai, ai-crawlers, gptbot, ai-seo, woocommerce Requires at least: 5.8 Tested up to: 7.0 Requires PHP: 7.4 -Stable tag: 1.1.2 +Stable tag: 1.2.0 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html @@ -16,6 +16,7 @@ CiteCue AI Auto-Fix is the WordPress end of CiteCue. It decides, per request, wh * **Per-request delivery to AI crawlers** — when GPTBot, ClaudeBot, PerplexityBot, ChatGPT-User or any other agent in the crawler registry requests a page, the plugin returns the CiteCue-optimized version of that URL. Human visitors always see your normal site, and optimized responses are never cached for regular traffic. Any miss, timeout or outage passes straight through to the normal page. * **Gap-filling page metadata** — adds CiteCue's title, meta description, OpenGraph, canonical and structured-data tags to your live pages, so search engines and AI answer engines see them on the page a human sees. It fills gaps only: it reads what your theme, WordPress and your SEO plugin actually printed into `<head>` and adds only what none of them emitted, so there is never a second title or canonical. +* **Page enhancements** — where you have approved one in CiteCue, adds a short facts-and-FAQ section to the end of a page you already have, built from your own answered facts and grounded FAQ entries rather than generated. It is placed only if the page does not already carry one, and it is the only thing this plugin adds that your visitors can see. * **llms.txt** — serves the llms.txt file CiteCue maintains for your brand at your site root, refreshed from CiteCue rather than regenerated here. * **Content from CiteCue** — a signed endpoint through which CiteCue can push new brand-building content (content briefs, FAQ packs, gap-filling pages) into WordPress as drafts for your review. * **WooCommerce-aware** — cart, checkout, account pages and cart-modifying links are never intercepted, while product and shop pages are served optimized. Pushed content can also create or enrich WooCommerce products (draft by default, matched by SKU with explicit consent). @@ -118,6 +119,9 @@ Yes. Store pages (cart, checkout, account, all WooCommerce endpoints) are never == Upgrade Notice == += 1.2.0 = +Adds page enhancements: a facts-and-FAQ section CiteCue composes for pages you already have, placed on the live page. Existing connections need one reconnect before CiteCue will send them — Settings → CiteCue will ask. + = 1.1.2 = Changes how page metadata is added to the response: no output buffer is left open, and WordPress 6.9's own template output buffer is used where there is one. Nothing to reconfigure. @@ -132,6 +136,14 @@ The plugin folder is now citecue-ai-auto-fix. If you installed 1.0.0 by uploadin == Changelog == += 1.2.0 = +* New: page enhancements. CiteCue composes a collapsed facts-and-FAQ section for a page you already have — built from your answered facts and grounded FAQ entries, never generated by a model — and this release places it on that page, immediately before `</body>`. +* The block is placed exactly as CiteCue composed it. It is sanitized before it is sent and capped at 32 KB on the wire, and this plugin checks the cap again before placing it; nothing is re-escaped or run through the content filters, either of which would break the markup or run a shortcode quoted inside an answer. +* Never placed twice. A page that already carries the `data-citecue="page-enhancement"` marker — because CiteCue's Worker fronts the site, or because the section is already in the post — is left exactly as it is. +* The block rides the `/seo-head` response the plugin already fetches for metadata, so an enhanced page costs no extra request, and the same rules apply: cache-only on the render path, so a visitor never waits on CiteCue, and nothing at all is fetched on a site with metadata injection switched off. +* Existing connections need one reconnect. CiteCue records what a plugin can do when it connects and withholds anything it never announced, so a site connected before this release is sent no blocks until it reconnects — Settings → CiteCue now says so, where before it only noticed a changed metadata setting. +* The plugin also now asks for CiteCue's site-wide identity metadata on pages that have none of their own. It is gap-filling like everything else in the head: if your theme or SEO plugin already prints structured data, CiteCue's is dropped rather than added beside it. + = 1.1.2 = * The metadata layer no longer holds an output buffer of its own open across a request. On WordPress 6.9 and later it uses core's template enhancement output buffer, so the plugin opens no buffer at all; below that it opens one in the form PHP finalizes by itself, and closes nothing. The previous shape opened a buffer on one hook and closed it on another, which left one open on any page whose `wp_head` did not run to the end — and a buffer left open is one the next plugin's `ob_get_clean()` can take by mistake. * The tags now go immediately before `</head>` rather than at the end of `wp_head`, and the check for what is already there reads the head only. Markup in the body — a `<title>` inside an inline SVG, a `<meta>` quoted in page content — no longer counts as a slot somebody else has filled, so pages carrying either get their metadata again. diff --git a/tests/cases/test-connect.php b/tests/cases/test-connect.php index f8dad68..cbf4476 100644 --- a/tests/cases/test-connect.php +++ b/tests/cases/test-connect.php @@ -219,6 +219,122 @@ public function test_a_failed_claim_reports_no_capability() { $this->assertNull( $this->plugin->settings->get( 'seo_head_reported' ) ); } + /** + * The block half of the delivery channel is gated on `body_blocks`, and + * CiteCue reads its absence as "cannot place a block" — so a plugin that + * injects one and never says so is sent `body: ''` forever. + * + * Declared together with the injection that honours it, deliberately: the + * capability is what makes CiteCue start sending real block markup, so a + * release that announced it without placing it would put a block on a + * customer's live page that nothing renders. + * + * @return void + */ + public function test_a_claim_declares_the_body_block_capability() { + $this->http->queue( 'connect', 200, $this->claim_payload() ); + + $this->connect->claim( 'one-time-code' ); + + $sent = json_decode( $this->http->last( 'connect' )['args']['body'], true ); + $this->assertTrue( $sent['body_blocks'] ); + $this->assertTrue( $sent['seo_head_baseline'] ); + } + + /** + * All three delivery capabilities ride the one setting that governs the + * response they arrive in: a site that fetches nothing can place nothing, + * and claiming otherwise is the over-claim the capability exists to stop. + * + * @return void + */ + public function test_switching_injection_off_withdraws_every_delivery_capability() { + $this->plugin->settings->update( array( 'seo_head_enabled' => false ) ); + $this->http->queue( 'connect', 200, $this->claim_payload() ); + + $this->connect->claim( 'one-time-code' ); + + $sent = json_decode( $this->http->last( 'connect' )['args']['body'], true ); + $this->assertFalse( $sent['seo_head'] ); + $this->assertFalse( $sent['body_blocks'] ); + $this->assertFalse( $sent['seo_head_baseline'] ); + $this->assertSame( array(), $this->plugin->settings->get( 'capabilities_reported' ) ); + } + + /** + * An install connected by an EARLIER build declared `seo_head` and nothing + * else, so its `seo_head_reported` agrees and the old check stays quiet — + * while CiteCue still believes the site cannot place a block and withholds + * every one. Without this, the feature would reach nobody who was already + * a customer, and nothing would say why. + * + * @return void + */ + public function test_a_connection_predating_the_block_capability_asks_for_a_reconnect() { + $this->configure_delivery(); + $this->plugin->settings->update( + array( + 'seo_head_reported' => true, + 'capabilities_reported' => null, + ) + ); + + $this->assertTrue( $this->plugin->settings->needs_seo_head_reconnect() ); + } + + /** + * A claim records the set it actually sent, so the same site stops being + * asked the moment it has reconnected. + * + * @return void + */ + public function test_a_claim_records_the_declared_capability_set() { + $this->http->queue( 'connect', 200, $this->claim_payload() ); + + $this->connect->claim( 'one-time-code' ); + + $this->assertSame( + array( 'body_blocks', 'seo_head', 'seo_head_baseline' ), + $this->plugin->settings->get( 'capabilities_reported' ) + ); + $this->assertFalse( $this->plugin->settings->needs_seo_head_reconnect() ); + } + + /** + * Installing WooCommerce after connecting leaves stale information on the + * key, not a broken feature — it gates nothing CiteCue sends — so it must + * not raise a prompt that asks the customer to fix something that is not + * wrong. + * + * @return void + */ + public function test_a_presentational_capability_alone_never_asks_for_a_reconnect() { + $this->configure_delivery(); + $this->plugin->settings->update( + array( + 'seo_head_reported' => true, + 'capabilities_reported' => array( 'body_blocks', 'seo_head', 'seo_head_baseline' ), + ) + ); + + $this->assertFalse( $this->plugin->settings->needs_seo_head_reconnect() ); + } + + /** + * Disconnecting forgets the set: the next connection mints a new key with + * its own capabilities, and what the old one recorded says nothing about it. + * + * @return void + */ + public function test_disconnecting_forgets_the_declared_capability_set() { + $this->configure_delivery(); + $this->plugin->settings->update( array( 'capabilities_reported' => array( 'seo_head' ) ) ); + + $this->connect->disconnect(); + + $this->assertNull( $this->plugin->settings->get( 'capabilities_reported' ) ); + } + /** * An install that connected before this release injects enriched metadata * while CiteCue still reports the channel as unable to. That disagreement diff --git a/tests/cases/test-seo-head-delivery.php b/tests/cases/test-seo-head-delivery.php index 193bbdc..17efe1f 100644 --- a/tests/cases/test-seo-head-delivery.php +++ b/tests/cases/test-seo-head-delivery.php @@ -14,6 +14,9 @@ class Test_Citecue_Seo_Head_Delivery extends Citecue_Test_Case { const BLOCK = '<meta data-citecue="og" property="og:title" content="Acme" />'; + /** A page-enhancement block, in the shape CiteCue composes them. */ + const BODY_BLOCK = '<section data-citecue="page-enhancement"><h2>About Acme</h2><details><summary>What is Acme?</summary><p>A company.</p></details></section>'; + /** * A JSON body of the shape the delivery endpoint returns. * @@ -683,6 +686,258 @@ public function test_the_block_is_injected_once() { $this->assertSame( $this->document(), $injector->enhance( $this->document() ) ); } + /** + * The page-enhancement block lands immediately before `</body>`, verbatim. + * + * Verbatim is the assertion that matters. The block is sanitized on + * CiteCue's side and carries real markup — a `<section>`, `<details>`, + * headings — so anything that escaped it would print tags as visible text + * on the customer's page, and anything that ran it through `wpautop` or the + * content filters would let a shortcode quoted inside an FAQ answer + * execute. + * + * @return void + */ + public function test_the_body_block_is_injected_before_the_closing_body_tag() { + $this->configure_delivery(); + $url = $this->fake_visitor_request(); + $this->plugin->cache->set_seo_head( $url, self::BLOCK, self::BODY_BLOCK ); + + $injector = $this->seo_head(); + $this->arrange_capture( $injector ); + + $delivered = $injector->enhance( $this->document() ); + + $this->assertStringContainsString( self::BODY_BLOCK, $delivered, 'The block must be placed exactly as it arrived.' ); + $this->assertMatchesRegularExpression( + '#' . preg_quote( self::BODY_BLOCK, '#' ) . '\s*</body>#', + $delivered, + 'The block belongs immediately before the closing body tag.' + ); + } + + /** + * Both halves of one response land in one render, in their own places. + * + * @return void + */ + public function test_head_and_body_are_injected_together() { + $this->configure_delivery(); + $url = $this->fake_visitor_request(); + $this->plugin->cache->set_seo_head( $url, self::BLOCK, self::BODY_BLOCK ); + + $injector = $this->seo_head(); + $this->arrange_capture( $injector ); + + $delivered = $injector->enhance( $this->document() ); + $head = substr( $delivered, 0, (int) stripos( $delivered, '</head>' ) ); + + $this->assertStringContainsString( 'og:title', $head ); + $this->assertStringNotContainsString( self::BODY_BLOCK, $head, 'The body block must not be placed in the head.' ); + $this->assertStringContainsString( self::BODY_BLOCK, $delivered ); + } + + /** + * A page that already carries the marker is left alone. On a site fronted + * by CiteCue's Worker — or one whose post content already holds the + * section — the block is in the document before this plugin ever sees it, + * and a second copy is the one failure a reader actually notices. + * + * @return void + */ + public function test_a_page_that_already_carries_the_marker_is_not_injected_into() { + $this->configure_delivery(); + $url = $this->fake_visitor_request(); + $this->plugin->cache->set_seo_head( $url, '', self::BODY_BLOCK ); + + $injector = $this->seo_head(); + $this->arrange_capture( $injector ); + + $already = $this->document( '', '<p>Hello</p>' . self::BODY_BLOCK ); + $delivered = $injector->enhance( $already ); + + $this->assertSame( $already, $delivered ); + $this->assertSame( 1, substr_count( $delivered, 'page-enhancement' ), 'Exactly one section, however it got there.' ); + } + + /** + * The marker is recognised however it was quoted, so a single-quoted or + * loosely-spaced copy of the section still suppresses a second one. + * + * @return void + */ + public function test_the_marker_is_recognised_whatever_its_quoting() { + $this->configure_delivery(); + $url = $this->fake_visitor_request(); + $this->plugin->cache->set_seo_head( $url, '', self::BODY_BLOCK ); + + $injector = $this->seo_head(); + $this->arrange_capture( $injector ); + + $already = $this->document( '', "<section data-citecue = 'page-enhancement'>Theirs</section>" ); + $delivered = $injector->enhance( $already ); + + $this->assertSame( $already, $delivered ); + } + + /** + * A block over the wire cap is dropped rather than placed. The endpoint + * refuses to send one this large, so a block that arrives anyway is a + * generation bug — and the cap is what keeps that bug costing one page + * rather than every page the cache has warmed. + * + * @return void + */ + public function test_an_oversized_body_block_is_not_injected() { + $this->configure_delivery(); + $url = $this->fake_visitor_request(); + $huge = '<section data-citecue="page-enhancement"><p>' + . str_repeat( 'x', Citecue_Seo_Head::MAX_BLOCK_BYTES ) + . '</p></section>'; + $this->plugin->cache->set_seo_head( $url, self::BLOCK, $huge ); + + $injector = $this->seo_head(); + $this->arrange_capture( $injector ); + + $delivered = $injector->enhance( $this->document() ); + + $this->assertStringNotContainsString( 'page-enhancement', $delivered ); + // The head half is unaffected: the two are independent, and one + // oversized block is no reason to withhold the page's metadata. + $this->assertStringContainsString( 'og:title', $delivered ); + } + + /** + * The document's own `</body>` is the last one. A page that quotes markup + * in a code sample carries an earlier one, and splicing there would put the + * section inside the sample. + * + * @return void + */ + public function test_the_block_goes_before_the_last_closing_body_tag() { + $this->configure_delivery(); + $url = $this->fake_visitor_request(); + $this->plugin->cache->set_seo_head( $url, '', self::BODY_BLOCK ); + + $injector = $this->seo_head(); + $this->arrange_capture( $injector ); + + $sample = '<pre><body>…</body></pre><code></body></code>'; + $delivered = $injector->enhance( $this->document( '', $sample ) ); + + $this->assertMatchesRegularExpression( + '#' . preg_quote( self::BODY_BLOCK, '#' ) . '\s*</body></html>$#', + $delivered, + 'The section belongs before the document\'s own closing tag, not the quoted one.' + ); + } + + /** + * A response with no closing body tag is returned unchanged — and its head + * half still lands. The two are independent. + * + * @return void + */ + public function test_a_document_without_a_body_close_keeps_its_head_injection() { + $this->configure_delivery(); + $url = $this->fake_visitor_request(); + $this->plugin->cache->set_seo_head( $url, self::BLOCK, self::BODY_BLOCK ); + + $injector = $this->seo_head(); + $this->arrange_capture( $injector ); + + $delivered = $injector->enhance( '<html><head></head><p>fragment</p>' ); + + $this->assertStringNotContainsString( 'page-enhancement', $delivered ); + $this->assertStringContainsString( 'og:title', $delivered ); + } + + /** + * A 200 whose `head` is empty but whose `body` is not is a complete answer, + * not a broken one: the endpoint sends 204 when it has neither half, so a + * page whose enhancement block is all it has must be cached and served. + * + * @return void + */ + public function test_a_body_only_response_is_cached_and_injected() { + $this->configure_delivery(); + $url = $this->fake_visitor_request(); + $this->http->queue( + 'seo_head', + 200, + wp_json_encode( + array( + 'head' => '', + 'body' => self::BODY_BLOCK, + ) + ) + ); + + $this->assertSame( 'fresh', $this->seo_head()->refresh( $url ) ); + + $injector = $this->seo_head(); + $this->arrange_capture( $injector ); + + $this->assertStringContainsString( self::BODY_BLOCK, $injector->enhance( $this->document() ) ); + } + + /** + * A 200 carrying neither half is still the payload we do not understand, + * and is recorded as a miss rather than cached as an empty block. + * + * @return void + */ + public function test_a_response_with_neither_half_is_a_miss() { + $this->configure_delivery(); + $url = $this->fake_visitor_request(); + $this->http->queue( + 'seo_head', + 200, + wp_json_encode( + array( + 'head' => '', + 'body' => '', + ) + ) + ); + + $this->assertSame( 'empty', $this->seo_head()->refresh( $url ) ); + $this->assertNull( $this->plugin->cache->get_seo_head( $url ) ); + } + + /** + * An entry written before the body half existed still reads. Those outlive + * the upgrade by up to the cache TTL, and discarding them would throw away + * a day of warm cache on every site the moment it updates. + * + * @return void + */ + public function test_a_cache_entry_from_before_the_body_half_still_reads() { + $this->configure_delivery(); + $url = $this->fake_visitor_request(); + + // Write through the cache first, so the key's salt exists and is + // discoverable, then overwrite the entry with exactly what the previous + // version stored: no `body` key at all. + $this->plugin->cache->set_seo_head( $url, self::BLOCK ); + $key = 'citecue_sh_' . md5( get_option( 'citecue_cache_salt' ) . '|' . Citecue_Cache::normalize_url( $url ) ); + $this->assertNotFalse( get_transient( $key ), 'The key derivation must match the cache\'s own.' ); + + set_transient( + $key, + array( + 'block' => self::BLOCK, + 'cached_at' => time(), + ), + HOUR_IN_SECONDS + ); + + $cached = $this->plugin->cache->get_seo_head( $url ); + + $this->assertIsArray( $cached ); + $this->assertSame( '', $cached['body'] ); + } + /** * A rendered page, as the injection receives it. * From 7559558eed6eaa8b0bdc76dc700921624febbbd2 Mon Sep 17 00:00:00 2001 From: henry-idingo <henry@idingo.io> Date: Thu, 3 Sep 2026 11:36:15 +0200 Subject: [PATCH 2/9] Say which thing went stale, not just that something did MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reconnect prompt had one message per metadata setting, and the capability drift added in the previous commit fires on neither of them: a site upgrading into page enhancements has not touched that setting, so it was told its metadata was not reaching CiteCue — a fault it would then go looking for and not find. The reason is now computed once and named, and the prompt says the one that applies. Consent is deliberately not among the reasons. Allowing content pushes gates the ingest endpoint and nothing on this path — enhancements arrive through the same authenticated /seo-head read as the metadata — so prompting for it here would be asking an administrator to grant write access to their site for a feature that does not use it. "Content from CiteCue" already reports that state where it belongs. Enhancements also get their own row in the status card, rather than being implied by the metadata row that no longer describes them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --- includes/class-citecue-admin.php | 35 ++++++++++++-- includes/class-citecue-settings.php | 41 ++++++++++++++-- tests/cases/test-connect.php | 72 +++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 10 deletions(-) diff --git a/includes/class-citecue-admin.php b/includes/class-citecue-admin.php index b2aae54..6cd6bdd 100644 --- a/includes/class-citecue-admin.php +++ b/includes/class-citecue-admin.php @@ -241,10 +241,22 @@ private function seo_head_reconnect_notice() { return; } - $enabled = (bool) $this->plugin->settings->get( 'seo_head_enabled' ); - $message = $enabled - ? __( 'this site can now add CiteCue’s enriched title, description, OpenGraph and structured data to your live pages, but CiteCue does not know that yet — until you reconnect, it will keep reporting that your fixes do not reach human visitors.', 'citecue-ai-auto-fix' ) - : __( 'enriched page metadata is switched off here, but CiteCue still expects this site to add it. Reconnect so CiteCue stops reporting metadata it is not getting.', 'citecue-ai-auto-fix' ); + switch ( $this->plugin->settings->seo_head_reconnect_reason() ) { + case 'disabled': + $message = __( 'enriched page metadata is switched off here, but CiteCue still expects this site to add it. Reconnect so CiteCue stops reporting metadata it is not getting.', 'citecue-ai-auto-fix' ); + break; + + case 'capabilities': + // The upgrade case, and the common one: the metadata setting has + // not moved, so saying anything about metadata would send an + // administrator looking for a fault that is not there. + $message = __( 'this version can place CiteCue page enhancements — the facts-and-FAQ sections you approve — on your pages, but CiteCue does not know that yet. Until you reconnect, it will hold on to every enhancement you approve instead of sending it here.', 'citecue-ai-auto-fix' ); + break; + + default: + $message = __( 'this site can now add CiteCue’s enriched title, description, OpenGraph and structured data to your live pages, but CiteCue does not know that yet — until you reconnect, it will keep reporting that your fixes do not reach human visitors.', 'citecue-ai-auto-fix' ); + break; + } ?> <div class="notice notice-warning"> <p> @@ -881,7 +893,7 @@ private function render_status_card() { <td> <?php if ( ! $settings->get( 'seo_head_enabled' ) ) : ?> <?php esc_html_e( 'Off', 'citecue-ai-auto-fix' ); ?> - <?php elseif ( $settings->needs_seo_head_reconnect() ) : ?> + <?php elseif ( in_array( $settings->seo_head_reconnect_reason(), array( 'enabled', 'disabled' ), true ) ) : ?> <span style="color:#996800;">—</span> <?php esc_html_e( 'Enriched here, but CiteCue has not been told yet — reconnect to update it.', 'citecue-ai-auto-fix' ); ?> <?php else : ?> @@ -889,6 +901,19 @@ private function render_status_card() { <?php endif; ?> </td> </tr> + <tr> + <td><?php esc_html_e( 'Page enhancements', 'citecue-ai-auto-fix' ); ?></td> + <td> + <?php if ( ! $settings->get( 'seo_head_enabled' ) ) : ?> + <?php esc_html_e( 'Off — they arrive with page metadata, which is switched off above.', 'citecue-ai-auto-fix' ); ?> + <?php elseif ( '' !== $settings->seo_head_reconnect_reason() ) : ?> + <span style="color:#996800;">—</span> + <?php esc_html_e( 'Ready here, but CiteCue has not been told yet — reconnect to update it.', 'citecue-ai-auto-fix' ); ?> + <?php else : ?> + <?php esc_html_e( 'Placed on the pages you have approved', 'citecue-ai-auto-fix' ); ?> + <?php endif; ?> + </td> + </tr> <tr> <td><?php esc_html_e( 'llms.txt', 'citecue-ai-auto-fix' ); ?></td> <td> diff --git a/includes/class-citecue-settings.php b/includes/class-citecue-settings.php index 937f039..ef4b4cf 100644 --- a/includes/class-citecue-settings.php +++ b/includes/class-citecue-settings.php @@ -214,28 +214,59 @@ public function is_delivery_configured() { * @return bool */ public function needs_seo_head_reconnect() { + return '' !== $this->seo_head_reconnect_reason(); + } + + /** + * WHY a reconnect is wanted, or '' for a connection that agrees with + * CiteCue — so the prompt can say what has actually gone stale. + * + * The three answers ask for the same reconnect but are not the same + * message, and telling a customer the wrong one is worse than saying + * nothing: a site whose metadata setting has not moved, being told its + * metadata is not reaching CiteCue, will go looking for a fault that is not + * there. + * + * - `enabled` — this site injects metadata and CiteCue thinks it cannot. + * - `disabled` — the reverse: it has been switched off since connecting. + * - `capabilities` — the metadata setting agrees, but this build can do + * something the connection never announced. What every + * site upgrading into a new capability reports. + * + * Consent is deliberately NOT among them. Whether the customer allows + * content pushes gates the ingest endpoint and nothing on this path — page + * enhancements arrive through the same authenticated `/seo-head` read as + * the metadata — so prompting for it here would be asking an administrator + * to grant write access to their site for a feature that does not use it. + * "Content from CiteCue" reports that state, on its own, where it belongs. + * + * @return string One of 'enabled', 'disabled', 'capabilities', or ''. + */ + public function seo_head_reconnect_reason() { if ( ! $this->is_connected() ) { - return false; + return ''; } $reported = $this->get( 'seo_head_reported' ); $known = null === $reported ? false : (bool) $reported; + $enabled = (bool) $this->get( 'seo_head_enabled' ); - if ( (bool) $this->get( 'seo_head_enabled' ) !== $known ) { - return true; + if ( $enabled !== $known ) { + return $enabled ? 'enabled' : 'disabled'; } $declared = $this->get( 'capabilities_reported' ); + $active = $this->active_delivery_capabilities(); // Never reported a set at all: an install connected before this plugin // declared capabilities by name. It is under-claiming everything it can // now do, so it needs a reconnect the moment there is anything to // claim — and needs no reconnect when there is not. if ( ! is_array( $declared ) ) { - return array() !== $this->active_delivery_capabilities(); + return array() === $active ? '' : 'capabilities'; } - return $this->active_delivery_capabilities() !== $declared; + return $active === $declared ? '' : 'capabilities'; } /** diff --git a/tests/cases/test-connect.php b/tests/cases/test-connect.php index cbf4476..622df38 100644 --- a/tests/cases/test-connect.php +++ b/tests/cases/test-connect.php @@ -335,6 +335,78 @@ public function test_disconnecting_forgets_the_declared_capability_set() { $this->assertNull( $this->plugin->settings->get( 'capabilities_reported' ) ); } + /** + * The prompt has to say what actually went stale. A site upgrading into a + * new capability has not touched its metadata setting, and telling it that + * its metadata is not reaching CiteCue sends an administrator looking for a + * fault that is not there. + * + * @return void + */ + public function test_a_stale_capability_set_is_not_reported_as_a_metadata_problem() { + $this->configure_delivery(); + $this->plugin->settings->update( + array( + 'seo_head_reported' => true, + 'seo_head_enabled' => true, + 'capabilities_reported' => array( 'seo_head' ), + ) + ); + + $this->assertSame( 'capabilities', $this->plugin->settings->seo_head_reconnect_reason() ); + } + + /** + * A moved metadata setting still reports itself as one, in both directions. + * + * @return void + */ + public function test_a_moved_metadata_setting_reports_itself() { + $this->configure_delivery(); + $this->plugin->settings->update( + array( + 'seo_head_reported' => false, + 'seo_head_enabled' => true, + 'capabilities_reported' => array( 'body_blocks', 'seo_head', 'seo_head_baseline' ), + ) + ); + $this->assertSame( 'enabled', $this->plugin->settings->seo_head_reconnect_reason() ); + + $this->plugin->settings->update( + array( + 'seo_head_reported' => true, + 'seo_head_enabled' => false, + ) + ); + $this->assertSame( 'disabled', $this->plugin->settings->seo_head_reconnect_reason() ); + } + + /** + * Consent is never a reason to reconnect for this. Content pushes gate the + * ingest endpoint and nothing on the delivery path — page enhancements + * arrive through the same authenticated read as the metadata — so a site + * that has not allowed pushes is fully configured for them, and prompting + * it would be asking an administrator to grant write access to their site + * for a feature that does not use it. + * + * @return void + */ + public function test_withheld_content_push_consent_is_never_a_reconnect_reason() { + $this->configure_delivery(); + $this->plugin->settings->update( + array( + 'seo_head_reported' => true, + 'seo_head_enabled' => true, + 'capabilities_reported' => array( 'body_blocks', 'seo_head', 'seo_head_baseline' ), + 'ingest_enabled' => false, + 'ingest_secret' => '', + ) + ); + + $this->assertSame( '', $this->plugin->settings->seo_head_reconnect_reason() ); + $this->assertFalse( $this->plugin->settings->needs_seo_head_reconnect() ); + } + /** * An install that connected before this release injects enriched metadata * while CiteCue still reports the channel as unable to. That disagreement From 9f245212873f9e771397c862307cafc62c1768b6 Mon Sep 17 00:00:00 2001 From: henry-idingo <henry@idingo.io> Date: Thu, 3 Sep 2026 16:42:09 +0200 Subject: [PATCH 3/9] Stop promising a push channel that can no longer deliver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consent is about to become real on CiteCue's side: reconnecting with the box unticked clears the stored signing secret, and nothing tells this site when that happens. The switch here would go on reading "Accepted" over a channel with no secret behind it. Two reads close that, because they cover different moments. The plugin's own claim response is the first: it sent a secret and asked, and the wire contract omits the flag rather than sending false, so absence THERE is a definite "not ticked" rather than "not mentioned" — the one context where that inference is sound. The daily sync is the second, for a withdrawal made at CiteCue between reconnects, read off `contentPush` on the project entry `/config` already returns. Silence still never grants. `contentPush` being true does not switch pushes on: it says CiteCue holds a secret, which is not the same as this site agreeing to be written to, and flipping that from a remote read would grant write access to a site whose owner had refused it. The reconcile only ever closes. Absent is not false, either. The field is read with array_key_exists() rather than a truthiness test, because a CiteCue that predates it would otherwise look like one withdrawing consent and switch pushes off on every site at once. Everything ambiguous — a transport failure, a project list this site's key is not in — leaves the switch alone, since acting on nothing would revoke a working connection over a bad afternoon on the network. The daily read costs a request only where pushes are actually on, which the default is not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --- includes/class-citecue-admin.php | 27 +++- includes/class-citecue-connect.php | 135 +++++++++++++++++-- includes/class-citecue-plugin.php | 5 + includes/class-citecue-settings.php | 42 ++++-- readme.txt | 1 + tests/cases/test-connect.php | 201 ++++++++++++++++++++++++++++ 6 files changed, 385 insertions(+), 26 deletions(-) diff --git a/includes/class-citecue-admin.php b/includes/class-citecue-admin.php index 6cd6bdd..222206c 100644 --- a/includes/class-citecue-admin.php +++ b/includes/class-citecue-admin.php @@ -462,16 +462,29 @@ public function handle_test_connection() { if ( ! is_array( $project ) || empty( $project['publicKey'] ) ) { continue; } - $clean[] = array( + $entry = array( 'publicKey' => sanitize_text_field( (string) $project['publicKey'] ), 'domain' => sanitize_text_field( (string) ( isset( $project['domain'] ) ? $project['domain'] : '' ) ), 'enabled' => ! empty( $project['enabled'] ), 'serveLlmsTxt' => ! empty( $project['serveLlmsTxt'] ), ); + + // Copied across only when it was actually sent. Defaulting it to + // false would make a CiteCue that predates the field look like one + // withdrawing consent, and switch pushes off on every site at once. + if ( array_key_exists( 'contentPush', $project ) ) { + $entry['contentPush'] = ! empty( $project['contentPush'] ); + } + + $clean[] = $entry; } update_option( 'citecue_projects_cache', $clean, false ); update_option( 'citecue_last_config_at', time(), false ); + // The same reconcile the daily sync runs, off the list already in hand + // rather than a second request. + $this->plugin->connect->reconcile_content_push( $clean ); + // Auto-select by host when no project is chosen yet. $settings = $this->plugin->settings; if ( '' === (string) $settings->get( 'public_key' ) ) { @@ -710,6 +723,18 @@ private function render_connected() { <?php esc_html_e( 'Enable the signed content endpoint.', 'citecue-ai-auto-fix' ); ?> </label> <p class="description"><code><?php echo esc_html( rest_url( 'citecue/v1/content' ) ); ?></code></p> + <?php $revoked_at = (int) $settings->get( 'content_push_revoked_at' ); ?> + <?php if ( $revoked_at > 0 ) : ?> + <p class="description" style="color:#996800;"> + <?php + printf( + /* translators: %s: human-readable date. */ + esc_html__( 'Switched off automatically on %s: CiteCue no longer holds a signing secret for this site, so it cannot send anything here. Reconnect and tick “allow content pushes” to restore it.', 'citecue-ai-auto-fix' ), + esc_html( date_i18n( get_option( 'date_format' ), $revoked_at ) ) + ); + ?> + </p> + <?php endif; ?> </td> </tr> <tr> diff --git a/includes/class-citecue-connect.php b/includes/class-citecue-connect.php index 89994c1..c77c617 100644 --- a/includes/class-citecue-connect.php +++ b/includes/class-citecue-connect.php @@ -189,10 +189,29 @@ public function claim( $code ) { // CiteCue's connect screen is where the customer is told that content // can be pushed into their site, so it is the only place that may turn - // ingest on. A response that omits the flag leaves the opt-in exactly - // as it was — silence never grants write access. - if ( isset( $result['ingest'] ) ) { - $update['ingest_enabled'] = (bool) $result['ingest']; + // ingest on. Silence still never grants write access — an absent flag + // cannot make this true. + // + // But absence HERE also revokes, which it does nowhere else. This is + // the plugin's own claim: it sent an ingest secret and asked, and the + // wire contract omits the flag entirely rather than sending false, so + // on this one response absence is a definite "the customer did not tick + // the box" rather than "not mentioned". Leaving the switch as it was + // would keep a site reading "Accepted" after a reconnect that withdrew + // consent and cleared the secret CiteCue signs with. + $had_ingest = (bool) $settings->get( 'ingest_enabled' ); + $has_ingest = ! empty( $result['ingest'] ); + $update['ingest_enabled'] = $has_ingest; + + // Written here rather than left to sanitize(), which only runs where + // register_setting() has — not on every path that reaches this. + if ( $has_ingest ) { + $update['content_push_revoked_at'] = 0; + } elseif ( $had_ingest ) { + // Only on a real withdrawal. A first connection that never had + // pushes on has nothing to explain, and stamping one would put a + // message about a change on a screen where nothing changed. + $update['content_push_revoked_at'] = time(); } $settings->update( $update ); @@ -223,16 +242,18 @@ public function disconnect() { // flag — but sanitize() only runs once register_setting() has, // and the empty value is what a write that bypasses the filter // has to see. Either path must end up with no key. - 'api_key' => '', - 'api_key_clear' => 1, - 'public_key' => '', - 'project_domain' => '', - 'ingest_enabled' => false, + 'api_key' => '', + 'api_key_clear' => 1, + 'public_key' => '', + 'project_domain' => '', + 'ingest_enabled' => false, // Back to "never reported": the next connection mints a new key // with its own capabilities, and what the old one recorded says // nothing about it. - 'seo_head_reported' => null, - 'capabilities_reported' => null, + 'seo_head_reported' => null, + 'capabilities_reported' => null, + // No connection, nothing to explain about its push channel. + 'content_push_revoked_at' => 0, ) ); @@ -242,6 +263,98 @@ public function disconnect() { $this->plugin->cache->flush(); } + /** + * Fetches the delivery config and reconciles content-push consent from it. + * + * Runs on the daily sync. Consent can be withdrawn at CiteCue — by + * reconnecting with the box unticked, which clears the stored signing + * secret — and nothing tells this site when it happens, so without a + * periodic read the switch here would keep reading "Accepted" over a + * channel that can no longer deliver anything. + * + * Costs a request only on a site that actually has pushes switched on. A + * site with the default (off) has nothing to reconcile and spends nothing, + * which is most of them. + * + * @return bool Whether the switch was turned off. + */ + public function refresh_content_push() { + if ( ! $this->plugin->settings->get( 'ingest_enabled' ) ) { + return false; + } + + $projects = $this->plugin->api->get_config(); + if ( is_wp_error( $projects ) ) { + return false; + } + + return $this->reconcile_content_push( $projects ); + } + + /** + * Turns the local "accept pushed content" switch off when CiteCue no longer + * holds a secret it could sign a push with. + * + * **Revoke only.** A `contentPush` of true never turns the switch ON. The + * field says CiteCue holds a signing secret, which is not the same as this + * site agreeing to be written to — that switch is the administrator's, and + * flipping it on from a remote read would grant write access to a site + * whose owner had deliberately refused it. Silence never grants; this only + * ever closes. + * + * **Absent is not false.** The key is read with array_key_exists() rather + * than a truthiness test, because a response from a CiteCue that predates + * the field would otherwise look like an explicit withdrawal and switch + * pushes off on every site at once. Only a `contentPush` that is present + * and false revokes; anything else leaves the switch alone. + * + * Anything ambiguous is also left alone — a transport failure (handled by + * the caller), a project list this site's own key is not in. Each is a + * reason to know nothing, and acting on nothing would revoke a working + * connection over a bad afternoon on the network. + * + * @param array $projects Project list as returned by GET /config. + * @return bool Whether the switch was turned off. + */ + public function reconcile_content_push( $projects ) { + $settings = $this->plugin->settings; + + if ( ! $settings->get( 'ingest_enabled' ) || ! is_array( $projects ) ) { + return false; + } + + $public_key = (string) $settings->get( 'public_key' ); + if ( '' === $public_key ) { + return false; + } + + foreach ( $projects as $project ) { + if ( ! is_array( $project ) || ! isset( $project['publicKey'] ) ) { + continue; + } + if ( $public_key !== (string) $project['publicKey'] ) { + continue; + } + if ( ! array_key_exists( 'contentPush', $project ) || ! empty( $project['contentPush'] ) ) { + return false; + } + + // Both in one write: the timestamp exists to explain the switch + // beside it, and sanitize() clears it whenever pushes are on, so a + // stale explanation can never outlive the state it explains. + $settings->update( + array( + 'ingest_enabled' => false, + 'content_push_revoked_at' => time(), + ) + ); + + return true; + } + + return false; + } + /** * Runs the check the README used to ask customers to run by hand: request * this site's own llms.txt as an AI crawler and look for the marker header diff --git a/includes/class-citecue-plugin.php b/includes/class-citecue-plugin.php index f660d3b..2da5451 100644 --- a/includes/class-citecue-plugin.php +++ b/includes/class-citecue-plugin.php @@ -193,6 +193,11 @@ public function daily_sync() { } $this->crawlers->refresh( $this->api ); + + // Consent can be withdrawn at CiteCue, and nothing tells this site when + // it happens. Costs a request only where pushes are actually switched + // on, which the default is not. + $this->connect->refresh_content_push(); } /** diff --git a/includes/class-citecue-settings.php b/includes/class-citecue-settings.php index ef4b4cf..44ce4bd 100644 --- a/includes/class-citecue-settings.php +++ b/includes/class-citecue-settings.php @@ -37,32 +37,37 @@ class Citecue_Settings { public static function defaults() { return array( // Connection. - 'api_base' => self::DEFAULT_API_BASE, - 'api_key' => '', - 'public_key' => '', - 'project_domain' => '', + 'api_base' => self::DEFAULT_API_BASE, + 'api_key' => '', + 'public_key' => '', + 'project_domain' => '', // Delivery. - 'serve_enabled' => true, - 'llms_txt_enabled' => true, - 'seo_head_enabled' => true, + 'serve_enabled' => true, + 'llms_txt_enabled' => true, + 'seo_head_enabled' => true, // The value of seo_head_enabled last reported to CiteCue, or null // if this site has never reported one. CiteCue records the // capability on the API key at connect time and has no other way to // learn it, so this is how the settings screen knows to ask for a // reconnect — see needs_seo_head_reconnect(). - 'seo_head_reported' => null, + 'seo_head_reported' => null, // The capability names last reported to CiteCue, or null if this // site has never reported any. Kept alongside seo_head_reported // rather than replacing it: that one tracks a SETTING the customer // can toggle, this one tracks what this BUILD of the plugin is able // to do, and only one of them changes when the plugin updates. - 'capabilities_reported' => null, + 'capabilities_reported' => null, // Content ingest (CiteCue -> WordPress post creation). - 'ingest_enabled' => false, - 'ingest_secret' => '', - 'ingest_post_status' => 'draft', - 'ingest_post_type' => 'post', - 'ingest_author' => 0, + 'ingest_enabled' => false, + // When CiteCue was last found to hold no signing secret for this + // site while pushes were still switched on here, so the settings + // screen can say why the toggle moved. 0 when that has not + // happened, or since been resolved. + 'content_push_revoked_at' => 0, + 'ingest_secret' => '', + 'ingest_post_status' => 'draft', + 'ingest_post_type' => 'post', + 'ingest_author' => 0, ); } @@ -387,6 +392,15 @@ public function sanitize( $input ) { $out['llms_txt_enabled'] = ! empty( $input['llms_txt_enabled'] ); $out['seo_head_enabled'] = ! empty( $input['seo_head_enabled'] ); $out['ingest_enabled'] = ! empty( $input['ingest_enabled'] ); + // Not a form field: written only by the consent reconcile, and cleared + // the moment pushes are switched back on, so a stale explanation can + // never outlive the state it explains. + if ( array_key_exists( 'content_push_revoked_at', $input ) ) { + $out['content_push_revoked_at'] = (int) $input['content_push_revoked_at']; + } + if ( ! empty( $out['ingest_enabled'] ) ) { + $out['content_push_revoked_at'] = 0; + } if ( isset( $input['ingest_post_status'] ) && in_array( $input['ingest_post_status'], array( 'draft', 'pending', 'publish' ), true ) ) { $out['ingest_post_status'] = $input['ingest_post_status']; diff --git a/readme.txt b/readme.txt index 382c4ff..9d9f9a7 100644 --- a/readme.txt +++ b/readme.txt @@ -142,6 +142,7 @@ The plugin folder is now citecue-ai-auto-fix. If you installed 1.0.0 by uploadin * Never placed twice. A page that already carries the `data-citecue="page-enhancement"` marker — because CiteCue's Worker fronts the site, or because the section is already in the post — is left exactly as it is. * The block rides the `/seo-head` response the plugin already fetches for metadata, so an enhanced page costs no extra request, and the same rules apply: cache-only on the render path, so a visitor never waits on CiteCue, and nothing at all is fetched on a site with metadata injection switched off. * Existing connections need one reconnect. CiteCue records what a plugin can do when it connects and withholds anything it never announced, so a site connected before this release is sent no blocks until it reconnects — Settings → CiteCue now says so, where before it only noticed a changed metadata setting. +* "Accept pushed content" now corrects itself. If CiteCue no longer holds a signing secret for this site — because you reconnected without allowing content pushes — the switch here turns off and says so, instead of reporting a channel that cannot deliver anything. It is never switched back on remotely: that stays your decision, on this screen. * The plugin also now asks for CiteCue's site-wide identity metadata on pages that have none of their own. It is gap-filling like everything else in the head: if your theme or SEO plugin already prints structured data, CiteCue's is dropped rather than added beside it. = 1.1.2 = diff --git a/tests/cases/test-connect.php b/tests/cases/test-connect.php index 622df38..3953603 100644 --- a/tests/cases/test-connect.php +++ b/tests/cases/test-connect.php @@ -407,6 +407,207 @@ public function test_withheld_content_push_consent_is_never_a_reconnect_reason() $this->assertFalse( $this->plugin->settings->needs_seo_head_reconnect() ); } + /** + * A project list that says CiteCue holds no signing secret for this site + * closes the switch here, so the screen stops promising a channel that + * cannot deliver. + * + * @return void + */ + public function test_withdrawn_consent_switches_pushes_off() { + $this->configure_delivery(); + $this->plugin->settings->update( array( 'ingest_enabled' => true ) ); + + $revoked = $this->plugin->connect->reconcile_content_push( $this->projects( array( 'contentPush' => false ) ) ); + + $this->assertTrue( $revoked ); + $this->assertFalse( (bool) $this->plugin->settings->get( 'ingest_enabled' ) ); + $this->assertGreaterThan( 0, (int) $this->plugin->settings->get( 'content_push_revoked_at' ) ); + } + + /** + * A CiteCue that still holds the secret changes nothing. + * + * @return void + */ + public function test_held_consent_leaves_pushes_on() { + $this->configure_delivery(); + $this->plugin->settings->update( array( 'ingest_enabled' => true ) ); + + $this->assertFalse( $this->plugin->connect->reconcile_content_push( $this->projects( array( 'contentPush' => true ) ) ) ); + $this->assertTrue( (bool) $this->plugin->settings->get( 'ingest_enabled' ) ); + } + + /** + * The switch is never turned ON from a remote read. `contentPush` says + * CiteCue holds a signing secret, which is not the same as this site + * agreeing to be written to — that decision is the administrator's, and + * flipping it from here would grant write access to a site whose owner had + * deliberately refused it. + * + * @return void + */ + public function test_a_remote_read_never_switches_pushes_on() { + $this->configure_delivery(); + $this->plugin->settings->update( array( 'ingest_enabled' => false ) ); + + $this->plugin->connect->reconcile_content_push( $this->projects( array( 'contentPush' => true ) ) ); + + $this->assertFalse( (bool) $this->plugin->settings->get( 'ingest_enabled' ) ); + } + + /** + * A response from a CiteCue that predates the field must not read as a + * withdrawal. Defaulting the absent key to false would switch pushes off on + * every site at once, which is the one way this reconcile could do real + * damage. + * + * @return void + */ + public function test_an_absent_field_is_not_a_withdrawal() { + $this->configure_delivery(); + $this->plugin->settings->update( array( 'ingest_enabled' => true ) ); + + $this->assertFalse( $this->plugin->connect->reconcile_content_push( $this->projects() ) ); + $this->assertTrue( (bool) $this->plugin->settings->get( 'ingest_enabled' ) ); + } + + /** + * A list this site's own project is not in says nothing about this site, + * and acting on nothing would revoke a working connection. + * + * @return void + */ + public function test_a_list_without_this_project_changes_nothing() { + $this->configure_delivery(); + $this->plugin->settings->update( array( 'ingest_enabled' => true ) ); + + $others = array( + array( + 'publicKey' => 'pk_somebody_else', + 'domain' => 'other.example', + 'contentPush' => false, + ), + ); + + $this->assertFalse( $this->plugin->connect->reconcile_content_push( $others ) ); + $this->assertTrue( (bool) $this->plugin->settings->get( 'ingest_enabled' ) ); + } + + /** + * A bad afternoon on the network is not a withdrawal of consent. + * + * @return void + */ + public function test_a_transport_failure_never_switches_pushes_off() { + $this->configure_delivery(); + $this->plugin->settings->update( array( 'ingest_enabled' => true ) ); + $this->http->queue_error( 'config' ); + + $this->assertFalse( $this->plugin->connect->refresh_content_push() ); + $this->assertTrue( (bool) $this->plugin->settings->get( 'ingest_enabled' ) ); + } + + /** + * A site with pushes switched off has nothing to reconcile and spends no + * request doing it — which is most sites, since off is the default. + * + * @return void + */ + public function test_a_site_without_pushes_spends_no_request() { + $this->configure_delivery(); + $this->plugin->settings->update( array( 'ingest_enabled' => false ) ); + + $this->assertFalse( $this->plugin->connect->refresh_content_push() ); + $this->assertSame( 0, $this->http->count() ); + } + + /** + * The daily sync is where a withdrawal made at CiteCue is noticed, so a + * stale switch corrects itself within a day instead of waiting for somebody + * to press Test connection. + * + * @return void + */ + public function test_the_daily_sync_reconciles_consent() { + $this->configure_delivery(); + $this->plugin->settings->update( array( 'ingest_enabled' => true ) ); + $this->http->queue( + 'crawlers', + 200, + wp_json_encode( + array( + 'version' => 1, + 'tokens' => array( 'GPTBot' ), + ) + ) + ); + $this->http->queue( 'config', 200, wp_json_encode( array( 'projects' => $this->projects( array( 'contentPush' => false ) ) ) ) ); + + $this->plugin->daily_sync(); + + $this->assertFalse( (bool) $this->plugin->settings->get( 'ingest_enabled' ) ); + } + + /** + * The plugin's OWN claim is the one response where an omitted `ingest` is a + * definite "the customer did not tick the box" rather than "not mentioned": + * it sent a secret and asked, and the contract omits the flag rather than + * sending false. Leaving the switch alone would keep a site reading + * "Accepted" after a reconnect that withdrew consent. + * + * @return void + */ + public function test_a_claim_that_omits_consent_switches_pushes_off() { + $this->plugin->settings->update( array( 'ingest_enabled' => true ) ); + $this->http->queue( 'connect', 200, $this->claim_payload() ); + + $this->connect->claim( 'one-time-code' ); + + $this->assertFalse( (bool) $this->plugin->settings->get( 'ingest_enabled' ) ); + } + + /** + * And a claim that grants consent turns it back on, clearing the + * explanation left by any earlier withdrawal. + * + * @return void + */ + public function test_a_claim_that_grants_consent_switches_pushes_on() { + $this->plugin->settings->update( + array( + 'ingest_enabled' => false, + 'content_push_revoked_at' => time() - DAY_IN_SECONDS, + ) + ); + $this->http->queue( 'connect', 200, $this->claim_payload( array( 'ingest' => true ) ) ); + + $this->connect->claim( 'one-time-code' ); + + $this->assertTrue( (bool) $this->plugin->settings->get( 'ingest_enabled' ) ); + $this->assertSame( 0, (int) $this->plugin->settings->get( 'content_push_revoked_at' ) ); + } + + /** + * A project list carrying this site's own key, for the consent reconcile. + * + * @param array $extra Extra keys on the entry (omit contentPush entirely to + * stand in for a CiteCue that predates the field). + * @return array + */ + private function projects( array $extra = array() ) { + return array( + array_merge( + array( + 'publicKey' => (string) $this->plugin->settings->get( 'public_key' ), + 'domain' => 'example.org', + 'enabled' => true, + ), + $extra + ), + ); + } + /** * An install that connected before this release injects enriched metadata * while CiteCue still reports the channel as unable to. That disagreement From 932c08c25447413854143969797868e5063072dc Mon Sep 17 00:00:00 2001 From: henry-idingo <henry@idingo.io> Date: Thu, 3 Sep 2026 16:48:22 +0200 Subject: [PATCH 4/9] Say why the two guards that look redundant are not MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both survive only as long as someone can see what they defend, and both defend against a change on CiteCue's side rather than anything in this file — which is exactly the shape that gets simplified away by a reader checking the current API and finding the branch unreachable. The absent-key guard is the dangerous one. `contentPush` is a required boolean on every project entry, so against today's API the branch genuinely cannot be reached and the test genuinely cannot fail. What it covers is the deployment that does not have the field at all — a rollback, a staging origin on an older build, a self-hosted app behind on releases — which no schema describes and which arrives without warning. Against one of those, a truthiness test reads every project as a withdrawal and switches content pushes off on every site at once. The revoke-only guard needed the opposite note. Now that a secret is stored only where the customer ticked the box, `contentPush: true` really does imply they consented once, so the argument for treating it as a grant is available in a way it was not before. It is still a fact about CiteCue's storage at one instant, not the administrator's standing decision about their own site, and they may have closed the switch here deliberately since. Comments and test docblocks only. No behaviour change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --- includes/class-citecue-connect.php | 32 ++++++++++++++++++++---------- tests/cases/test-connect.php | 19 +++++++++++++----- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/includes/class-citecue-connect.php b/includes/class-citecue-connect.php index c77c617..ebf6b62 100644 --- a/includes/class-citecue-connect.php +++ b/includes/class-citecue-connect.php @@ -295,18 +295,30 @@ public function refresh_content_push() { * Turns the local "accept pushed content" switch off when CiteCue no longer * holds a secret it could sign a push with. * - * **Revoke only.** A `contentPush` of true never turns the switch ON. The - * field says CiteCue holds a signing secret, which is not the same as this - * site agreeing to be written to — that switch is the administrator's, and - * flipping it on from a remote read would grant write access to a site - * whose owner had deliberately refused it. Silence never grants; this only - * ever closes. + * **Revoke only.** A `contentPush` of true never turns the switch ON, and + * the tempting argument for letting it is worth answering here, because + * CiteCue only gained a real consent gate at the same time as this field: + * since a secret is now stored only where the customer ticked the box, + * `true` really does imply they consented once. It is still not a grant. It + * reports that CiteCue holds a signable secret at this instant — a fact + * about CiteCue's storage — whereas this switch is the administrator's + * standing decision about their own site, and they may have closed it here + * on purpose afterwards. Re-opening it from a remote read would hand write + * access back to a site whose owner had refused it. A remote grant belongs + * on the connect screen, where the customer is actually told what they are + * agreeing to. This only ever closes. * * **Absent is not false.** The key is read with array_key_exists() rather - * than a truthiness test, because a response from a CiteCue that predates - * the field would otherwise look like an explicit withdrawal and switch - * pushes off on every site at once. Only a `contentPush` that is present - * and false revokes; anything else leaves the switch alone. + * than a truthiness test. This guard will look redundant against the + * current API, and it is not: `contentPush` is a required boolean on every + * project entry, so any CiteCue that has the field always sends it, and a + * reader checking the schema will conclude the branch is dead. What it + * defends is the deployment that does NOT have the field — a rollback, a + * staging origin on an older build, a self-hosted app behind on releases. + * Against one of those a truthiness test reads every project as an explicit + * withdrawal and switches content pushes off on every site at once, which + * is the only way this reconcile could do real damage. Only a `contentPush` + * that is present and false revokes; anything else leaves the switch alone. * * Anything ambiguous is also left alone — a transport failure (handled by * the caller), a project list this site's own key is not in. Each is a diff --git a/tests/cases/test-connect.php b/tests/cases/test-connect.php index 3953603..50785db 100644 --- a/tests/cases/test-connect.php +++ b/tests/cases/test-connect.php @@ -439,11 +439,13 @@ public function test_held_consent_leaves_pushes_on() { } /** - * The switch is never turned ON from a remote read. `contentPush` says - * CiteCue holds a signing secret, which is not the same as this site - * agreeing to be written to — that decision is the administrator's, and - * flipping it from here would grant write access to a site whose owner had - * deliberately refused it. + * The switch is never turned ON from a remote read, even though CiteCue's + * consent gate means `contentPush: true` now implies the customer did once + * tick the box. That makes the grant reading tempting and still wrong: the + * field reports what CiteCue holds at this instant, while the switch is the + * administrator's standing decision about their own site — and they may + * have closed it here on purpose since. Re-opening it from a remote read + * would hand write access back to a site whose owner had refused it. * * @return void */ @@ -462,6 +464,13 @@ public function test_a_remote_read_never_switches_pushes_on() { * every site at once, which is the one way this reconcile could do real * damage. * + * This test looks redundant against the current API and is not. + * `contentPush` is a required boolean on every project entry, so a reader + * checking the schema will find no way to reach the case at all. The case + * it covers is the deployment WITHOUT the field — a rollback, a staging + * origin on an older build, a self-hosted app behind on releases — which no + * schema describes and which arrives without warning. + * * @return void */ public function test_an_absent_field_is_not_a_withdrawal() { From 6dbe1aa44e2ff2758a02a97f295be3695dbbd79e Mon Sep 17 00:00:00 2001 From: henry-idingo <henry@idingo.io> Date: Thu, 3 Sep 2026 16:55:36 +0200 Subject: [PATCH 5/9] Keep the directory art where it cannot evaporate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The banner and icon were generated into a temporary directory and lost to a cleanup, which is a poor place for a deliverable that took design iteration to arrive at. They now live in the repository, beside a script that rebuilds them. `.wordpress-org/` holds what WordPress.org serves above the readme; `assets-src/` holds the banner source and the build. Neither is part of the plugin — the directory serves them from the SVN `assets/` folder, which sits beside `trunk/` and is never installed on a site — so both are export-ignored and the zip guard now names them too. A lost export-ignore line should fail the build rather than ship page art to every install. The build renders with headless Chrome because it is the one thing reliably present that lays out real webfont text, and it inlines the faces as data URIs first: --screenshot does not wait for a font fetched over file://, so a relative @font-face renders the whole banner in a fallback with different metrics and reports success. It asserts the four output dimensions, since a banner at the wrong size is refused by the directory as a silently missing header rather than an error. docs/releasing.md writes down the SVN procedure, which existed only in a terminal scrollback and has now been reconstructed twice. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --- .gitattributes | 2 + .wordpress-org/banner-1544x500.png | Bin 0 -> 60560 bytes .wordpress-org/banner-772x250.png | Bin 0 -> 27443 bytes .wordpress-org/icon-128x128.png | Bin 0 -> 4838 bytes .wordpress-org/icon-256x256.png | Bin 0 -> 5753 bytes .wordpress-org/icon.svg | 20 ++++++ assets-src/banner.html | 103 +++++++++++++++++++++++++++ assets-src/build-assets.sh | 110 +++++++++++++++++++++++++++++ bin/build-plugin-zip.sh | 2 +- docs/releasing.md | 63 +++++++++++++++++ 10 files changed, 299 insertions(+), 1 deletion(-) create mode 100644 .wordpress-org/banner-1544x500.png create mode 100644 .wordpress-org/banner-772x250.png create mode 100644 .wordpress-org/icon-128x128.png create mode 100644 .wordpress-org/icon-256x256.png create mode 100644 .wordpress-org/icon.svg create mode 100644 assets-src/banner.html create mode 100755 assets-src/build-assets.sh create mode 100644 docs/releasing.md diff --git a/.gitattributes b/.gitattributes index 5b7fbba..be62f13 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,6 +2,8 @@ # zip. `bin/build-plugin-zip.sh` builds with `git archive`, which honours these, # so this file is the single list of what is developer-only. /.gitattributes export-ignore +/.wordpress-org export-ignore +/assets-src export-ignore /.github export-ignore /.gitignore export-ignore /.phpcs.xml.dist export-ignore diff --git a/.wordpress-org/banner-1544x500.png b/.wordpress-org/banner-1544x500.png new file mode 100644 index 0000000000000000000000000000000000000000..a661f6e5290b5b5617011d9807e3bfac27132d94 GIT binary patch literal 60560 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 H67v55-FwWt literal 0 HcmV?d00001 diff --git a/.wordpress-org/banner-772x250.png b/.wordpress-org/banner-772x250.png new file mode 100644 index 0000000000000000000000000000000000000000..e356247ad75db2fc22478ac4a42e27976f30a824 GIT binary patch literal 27443 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� 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 btqKVi!jl}03gIUV903DKDoRv|83p|x7~5Ed literal 0 HcmV?d00001 diff --git a/.wordpress-org/icon-128x128.png b/.wordpress-org/icon-128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..c84446e38bf4620edca47a1e29abf576493bb326 GIT binary patch literal 4838 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 literal 0 HcmV?d00001 diff --git a/.wordpress-org/icon-256x256.png b/.wordpress-org/icon-256x256.png new file mode 100644 index 0000000000000000000000000000000000000000..930d3c8c47eab28850ed534f3004f1b92e8af3e3 GIT binary patch literal 5753 zcmdUz_cz>6*!MqcS!MMW(TN(JAbJVWR|!`1PP8aNqORUWi5fMcMvZQj711R~h~7J^ zw}i#*`<&;GxbL54u4~Sjb7tmT^L}5i7#)}j5dkd$002a4s!DnQ0J@)o06eVw;Ww$A z5CD(@YDx<FZ*$NKxcd6=Tim0i5#YtcOi*Nn9%XN+rE)Bv6R&Pj-E*z4ubfA+jjT_! z7Z%tK++v+4Y<;!FUfRUd_EKwUx=iErmN9F6S3DF1|F*PWylb_kuu(_~@m)f8jjp^M z0Jf9@mfl@nA}<&ARv%^pc;o7Xc)EmOL|G(AUcU!muCM|~Ck26MCmKLWlo0qt3juVA z{ueEq0*xPk<_3T<?O|8}n49*%c4<-C#^-AX#k$U-PTJbOzG<r&hflIcCMP2+oC56{ zJKOqn49%;i=w|M0<p^SjygW}qjU>qqmRQUVV{hB~Np_@aEtK9YxhaH5S}^fFjp_ZB zlpM$|v!?$1!eHr}&60Uphv^|?;GrPky87ie2Vo4FX(6oRtVa>6Fjs;=gznFXy<DBf z0?$UScxih6V`_`CWMc+a`AZz|m{YeICW;U(^H!&Z1?=Zda1udmtD)B58oZVMpvvc| zF~?oCsz<GoRfCQ0l%x^3;e$GNzR-C{s|XXCY!T<_H%j}3+fVvfMc@&1)TDWYnj;?} zs^fhB_kP`s4X3uM&VU_?TSeDi#Nvh>-`;;RPjMrX=U}H%PyWX@mNBzIyI<w0kv~4l z@PCEVkRs~DjhCW9QB@mqDTZr9eU`q31aaZWZeTt{bMs=NJqDVWH6-SPph;Y<j5Me( zI6pHyZVKp)bl!D7uSNtxqvX3*R#~&eozc)yEnjPj-!G@BN#`Gsl&=lCop<dS_b0~5 z4jl15u^|kK0R7EUWy?N38eoSyc)Qz_Ze)Jqbp&4sJgrN8<$`#%COVnz@_rYBDeVFU zL&Y^|=#c0Li4`*`Q1`(#vAYNyUz*YIPSs&;D`PT$qe5#!JkGHO^qlR?|K0I?X9p5% zh9;`#>I!9OfV$LoOK!hP6)V>SQ5Ibiqb|<n%<JBa3@D&*HD6YH)dZ!(?C+$TR<9L% znFJgw92v-T{c!0?7uE(y?v{rqNyJM~IfA#XMaK#y;3D-B?`rU4-3LIOQM=1{PVUFs z&BHw$%s7V9h_24{)8#chSm^Ue&m{4OLLMLlB<tT4Q2Vx9Qw)OQN&F_(h%mNp$+tTM zv)?pO7vL*{XvF;nRt{woW6_X+9z&EXI;1Hnf4)<q5S>-+R{-HF9*ynd&5?0^azydM zZ)S(;XrU!~Wm2~BSpaX`i;nXIa#G2aB@iXS%%AhalgnKPp<agLi(nC-rwtCaYS9&B z;jm@U(Z$JM+2fv;Ox5LjpDM&5hmO^fP&=EkGmZj<C(st*edV#Ga(}bnQK1NiKwQU9 zK6YY#8Hpz%A@2BM0Qg#^_V5O?>%B;30+LeqL_E^NuS_V<q+p$XiDd<`Joy{`{d_o0 zUE+x8MLKSRX%mMs>s!u>s~ABrUNV4`*SHgO-hsXk)e&T$kw-MMI`7i<&Yc)A^C}|2 z?Psg>EM_;$pOp(>$L3uXxZrqZ-cSP6gj!F!v>Zivu}uleSdlkW6wI^cZBI(Nf^GR$ zvCH6)56h}m7eqk;{5^a!;~=yV))7%}s>lr5W+1!jl`~IO#TNH^>5(=^@Yl!1!f_Ho zKuW-ZU7*3)^<!@_T@9zjq@XCNp!tr2-F^_g7Q{^1eu(u-dV!+LxO%coA)3DTvp!0T z|Dov?<7HI?!4_=z5E$6dyXcS;MB!q`a<g&)+-bi`wRhsh6hvWv84z7JuN3n0l+ebL zuz`nIffk>Q-t^7&;kkzKG2&XDMTh(@jR#h&hiXXmKzBSUH$f$k@aP)Q*gr?ZjxHMP zgz)Cz$dG1!$F5HGG2(YWd#3=5*M_h*Nuk#8(x){ha7)A_F;x4So{Q?Fle?V|m3qoZ zD_kaymmBt*pruIxGr_&k>J)M)@S_U|kHGFcKB15YsVG3)KM$~FdCiM2abe7q-cbWF zN|>EpIJU%=H?(Ax7Ea}EU$tk$8SsTJ{7zo*h#wd>&!HQaq2WSJvW`(~e04|(<efl2 z94ZCEA-K|aBa{VTPO777tc_p#0=PPFvLX3cN4qKgZ#(9-YQ%Sm&Ko2BIyx6E1sDH* z*Lm&gc;*g&oaULPSgkYkW^vY>tlPZ^>tGuQ$SJx-%7481v8)BK`upYzic$aNU>99m z--+W)`3>VJZN`*-x{e&(UJ)JePjcA7NzhP14<GGH{1BfCZ_TTC*FM82<8gB`ohgf= zKwdH~wXKcCtiP!*uF7~__LT+Z5Hs}>a&0tGP56{<4xOL>NlK3+e5>>jEQck?%kTF) z_?Gay#_+)0*PG=gGc~`kQznKtWD+IbLO&1fw<|qo`8{0f?~W-Xvq@6dRRH4smpv;x z?R*}RqF(=E1x9mRFGG+tnK24|TWxgTCS4br67uYWMc(RwHS+{NtbdXFr`Z?fEY{={ zy+rL2XAduob)AaG7v$Tnem^nqKv?E<As%)ng)rik^|s{8!kwC&{J0o!{hLpU1mtUv zGu5$JYCJP^DKwjX^Mm;YF6e*dC|q+3-Dxf2=or|a9GwmL5s2wVG-MlUCe-_q_{_hD z2UGkChC=f=+%|{Zn(uB>Uvp?$80r{7u(<?VVKV6E$HP6FEtlf;OHEHFKUS1iZ<3jT z38NG7cE|xXp^1n2nRonB2e>J<56&uFMxPa*h=x6u;yIju^i{;Aeu?L}4K<b2Og-+4 zXsu_$PNAfN#|PVTzk=ThOuKD9i^3c}r5B!4Lf7Kf+|DJ_0cm=&@1zxQ{APQHZAy6C zJgaE%3|~{o`Hft<OuoLlGB7-DC<@}_;mX~0Vr&Iv3Y~+>>A3lJd-{X2rT5aZX&KSv zZeQcJ7^m3bn4FNkbN<v+O#Rt63;Cen0oSm+={o^M0z?t#sNB_hK7AtfMCNyQ3XwZT zy=out+DA;ZiC1l}!>PM(iu_1D&n<rPU>3ZF|9PL+)6%p0<Y};YWY#Xs@G61@eW}Sd zUMt(}$M-J@e*EVLqc*>}RI&!9XO?1Y))3BUr0m==FJBFW&6EvN5UlSTp$IWG=@2_M zon@B~eQpgzvY&ygiybzrj5^4Y#*%3eD;~Ni?$p7PGp1Xo=F`d}<;##3pydnYcwkS+ zi!ZmW#RawWq&U$~RBgCpa`bSqahgG6{;k6UX33-nh!)4?34b1OBf8nETB=p(Dwd!% zR|b{);^Zf-7+gR%tP<D`XaEH*qH?<`jYg4b78BIAI0gRAFjVeIY<9;r4D<>A6=<7q z{@=;oAxJl}5(xL*;04eF4n65iAYI5IAU7Aht*LCs$2^oog)o;<L_AtLkby+YV&kCT zr5o-<^C6%0<<D5SfQ42bfZyJ%{EIl~6NwrKZ;KaDNz4Z;v4>(U--{f*gug5<X&N2@ zLcMq9T|;XI6wr<N3~;s1w3HMUCLb*X>0Gny5~H*L&(m0%zJHuG%;wBozyUo`i0A1E zXgs!zC5gO6$Jh`j+7KH@e*fnW(t&|F*I=DFGD?75MY(py7No=zid8V_?ggtFw}spX zO+THNx7AF|^JU-9Mi&4Rbgl0=ec&s7|IY4z;pd51D1-eFRe2pE)TEzgT1q`TO|%y_ z4&n<-Qa%hCZAb&)5#eJi@l=ACdD-N7`@-0`BFqZtT*qJvlJfejJH$GpGvWGENzC^F z7r<_p!|<GvYKG{)GXhb=DJrDy97L<w3ZQ|NgVyicohwehZqzo9+{h>qq2Gk7#Wo+r zDD|vL$YuZ@p_LaFFn))kg2A;l(0EiOU^=wnLixn!P5kpU;%H%897K;~W-j!jcxv?8 zj;;zTni#cnEA8cwh@<rIn-gN#)NPEEvW!mL9*;Tu{*|1kGzh()FaP`!x8>ol%Cl=p zp4A3TVrzHK$dd^Td1PGGR@Sm={fA2a4YM1S?JuFk%N>iWZ!U27D(t=Tes~K&ud2S` zQ}j(fIG=M91}NdKV(GwNOZu*myYCi9)VIo1(|FU6GhwSq`cXgKsMf1AyCE>_dBR+c zXP?DK<YgZZg4Ou~$RBw**&x+1)pl@>HuAfxNW)-CM7{qdAr6~kxGWTDb4j+G9T#VN zGb<kGF^Z?~=m}uqHV?%<Iz69lyERMTw0JPc%}y-;gY^k8lTlr2w+;feMM7N7FPlvH zam+m^L@avK0!141_v~@s__9%VYkjvKFCzno0G+b4g&w;87kSIO?{fd7XwdJi4cP=# z$_n>~Gp}{)wRVz;1DztCmpwCWA~88oeo5hg(R7lxLJ|d@9xgC0y6v0Oa;6Z1>z^yd zfzrpMNm7r*0sDIIDPNTk8nd3zP7RD8?}u&Ib?F7PNo4xP_!G>9GVb-NpoKJKg?NAg ztFEGgsJ=7-VqNy(M1>>q?~bC6l`c+FY5t!R?|^r;Lq69Trr!m*2aH^N1}Bi9HvDlA z7k_!c-@-!ETzW4R;jFo{C>8bM>Olu#dSX3dcv^I3F<Il`d5x!U$KD;eRE^B+(#UO= zDL42`P6-z=Ip#<w;z^pM#70iUIOaSukU8O%m1f|#Y|N>=20yl=2p!>ji4>Zi8(L(& zcrg4B6~QQ;#E6Kmyze#MA06p{tlr4xp^rwK8gpgxiRQB&x){;DAS-iJ?&^wCu?ZH~ zTeOZy=q4m(0wS%39y@U)#(_^%*;1^IbtxF*tE$V96DuTMiO1qWV_i;eW#d)n3GqMG z=kP2}NqPzDb1z)$l=DLH<=exMaFsVCkkF0w%^J-5#|8iQ&6hodtd<<U!aom7NG@v? z<|wn+yb`ZB>C8f`KQDo@O-BWCFOKk-Gn+H|pV_QUUl|o9ZJO~3H}<T|xmov*EMMJQ zI|ppK4qKSfT7i!p<AJU8OBSByv1@F4xdWi`-dWn%Tmjl>4as_|--{H3JN6nTsiY-N z(Niz2z3rQV43Ls{7uJnE7|MlYVRdbSbuHlPjNsa%Uw!$bh@2gRRGN=4`@XHG-*E8b zw%32p4nDCQ80^Nz(W<Fvu6FvskpE~C=+=%ofXd*Se_h8UeORwI9(*tp;IfmO{>pd@ ze;mzjP2yXu#|`IRCn(n3s@kU_8|hp;nyF75<{k@?9W<;N_yySgr9{*wp&t3m6|PGQ z%=z-Ts!S>L&l(h%X76qkyoyCIDZ8g;&q}tMN|ps{7XUVYq<)de_kVJaMK~Om90s3x z+h2$lAf6_D%<13Zjd>!YjH(Boh|LCdZFfNODu-V~lYJfBH|)n@+M|7DYOh>+0RY1K z9}9S-uBiX*jvd?;DX-<^v>VR>B`Wok;O7((?M2}<ElC!81*#WUPVgRaW0_Nb$gjG- zuvO=2i<R!3<SN7e7?N;YT!hhNb`01r<25?5GV4IyxhjFkP~7F%fBN)C@XNs~t4pM1 z_)-LIc^1MRIY}fJ<Z7nqQZaC;5-ZJ)Lwal$1=Y8l$D7W;a##Og_?u(SfMipX=nu4W z$==J3d!L${=$2jgOWfw+m86T5pf1j|^p1aOejH@T(gq3-Qtnj7HL#XNZw9wnc+)s( z7uZtF#6jlRP**5Mds{MsD``!5**>hJYA!O(PXk9(8Fg)kgFZWIcvl0TqLGhoam8NX zH|1;K#$u`b(rT_Vw1}(80mVzq{46C;5!@R1B7hq&LY_L^Y^3evFUA+`)2+399qum5 z6d-OW#VfrAR_tEmALAn+7@jfd{wpMHE)Xb{thzoR(LIrJk~p`FD246lJ&yQIP6Y#u zUP??={fP{p<{~9kQ6qjK|GlC42dGQ?4+GDGiEu;#7D_TuY}m1ZFd>foCrG7_R;7<+ zIXgFG>)yFPHbB(k@d1?+UNdO=nM_;=QZ>Ae{p)mIJI>SI#Or*mKlq2cxZRZ9&C{nn zF|WKV9<AgQH&OhH49Og3&x(9L9$yrs80chP`~E?mOumTKtVVBf8p?~c&WL^j`c%(4 zT~h*`Z$4aqs$dKo7@fMWFj{eM{5ck!JPI5S5jyw5Wl4#_JEx8$-=E}v!JAZRjj8sX z0cY#)mPKGCJ$jFe-_sv|X&}HjJ_J#BH`mUfW290s0UfOV5g?10eQuLNaA5KmW6wYR zyQH@e`fnuisW(wD2YU;R=>1{j+JT#n6Iv}F{F{tN=$vTPAvI9U)ZoR}U`geV;KPMr zr|Fih)WkfTPOCjjT}u+j;H`><KKdg${>pnhI_`<<Xf0;a9PI!3bHih?lJ_6!@|~ng zxZ=1+!hL!KM@m~6<9@!S+kcbGsRjqKKRax?E(Nkl3QLdWi^sKPUa6~Wk~uQLcTMx8 zH;^(G$}4q3Pap^bxWkqGoI=q!xkznB9F%GCREgDJo%QL*8Db{8cl@w(YRm<v@3U9; z8|Do!-tJpfJ!cs2fbKX)eK0Pa>gMG6e42R8p2qDcD-gd6)ZR;R)6?6arE(OIKW6DU z3fP~%8kHo3Y)NCX%-1f4v}e;^X8Y0YtFVnTA~@0vt{9N-@4J*v|K^{!pBZt3i-`H1 zoEb)72c7TCuie@%Bo{az-C%)_zO1GUC74{6I;@}L9j@+g;D=igQYBUvxp$rHnJvP8 z*TU~MB&B(-;WP-74KqIDuAL}M=r38Bo(U)Rr0+p~bPeX}5oa|IWbQII)5<fj7)dZD z0g}u`VQOqC$<MBT<XoZd)l@C>i6uJWkGQpQr*`@{1bLFSS6z0@*Y%JF;eUFh1AFH< z9nbx!i1ps95`D3kh^;^P=L+95>+D?Z+-Uo{_<XAJPaq0tP;_)hp?8XyvYyP3PC;8N zH8A!2BNxcP^OQw9i}F?ZI##Wg;mcg&d!pr{^3_+#5tG!3?~xpKb*Vof<u*s|f0EwE zF#I~Gw_DKR%jfG@DmhA?*pcb!SE7XSCcS-UF>x{Uv~$hD`p6o%4FSKgTwYJmuWd3T zHmG^W;cOvRav@p2#3c!M`H6ajw2%8?_;guNV9z5z>|r|?U6c_oQPvDx<ZHF9-DU+8 z<|_H6ruLVA6Vl5Hn1z~fhC{A>p?j3n<dn4J%jJfHwr=RRY8)AlZQYz2cQ;>){XZ0V zGL)S?+qO&l%pk3^VphlC$8EU)*W=N_AGi~;|Iy9{0UU)7{|E5r|0lDJcens>cei66 h@b4ZVL;h0>Fz{#D(L4D}BI7;-s42sgsue9m{|86trw9N5 literal 0 HcmV?d00001 diff --git a/.wordpress-org/icon.svg b/.wordpress-org/icon.svg new file mode 100644 index 0000000..392da32 --- /dev/null +++ b/.wordpress-org/icon.svg @@ -0,0 +1,20 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256" role="img" aria-label="CiteCue AI Auto-Fix"> + <!-- Plugin icon: the CiteCue magnet C reversed out of the brand navy. + The directory and the plugin installer both show icons on white cards, + so the tile carries the navy and the mark carries the paper — a + paper-ground icon would dissolve into the card it sits on. + + The inner viewBox is the mark's own bounding box: all arcs share + center (22.2, 20) with r=13 and a stroke width of 8.5, so the ink + spans 4.95..39.45 in x and 2.75..37.25 in y. Cropping to exactly that + box centers the mark optically; the source logo.svg's 0 0 40 40 box + does not, and would sit 2.2 units right of center. --> + <rect width="256" height="256" fill="#101b3d"/> + <svg x="46" y="46" width="164" height="164" viewBox="4.95 2.75 34.5 34.5"> + <g fill="none" stroke-linecap="butt"> + <path d="M 25.4 7.4 A 13 13 0 1 0 25.4 32.6" stroke="#F6F5EF" stroke-width="8.5"/> + <path d="M 24.7362 7.2498 A 13 13 0 0 1 31.8988 11.3435" stroke="#ee6c1e" stroke-width="8.5"/> + <path d="M 24.7362 32.7502 A 13 13 0 0 0 31.8988 28.6565" stroke="#ee6c1e" stroke-width="8.5"/> + </g> + </svg> +</svg> diff --git a/assets-src/banner.html b/assets-src/banner.html new file mode 100644 index 0000000..a85383b --- /dev/null +++ b/assets-src/banner.html @@ -0,0 +1,103 @@ +<meta charset="utf-8"> +<!-- + Source for the WordPress.org banner. Rendered by build-assets.sh, which + inlines the brand fonts as data URIs before handing the file to Chrome. + + The inlining is not an optimization: Chrome's --screenshot does not wait for + a font fetched over file://, so a relative @font-face silently renders the + whole layout in a fallback face with different metrics. Data URIs are parsed + with the stylesheet, so what is measured is what ships. + + Sized in CSS pixels at 772x250 and rendered twice — once at 1x and once with + --force-device-scale-factor=2 — so the retina banner is the same design + rather than an upscale of the small one. +--> +<style> +@font-face{font-family:'Schibsted Grotesk';font-style:normal;font-weight:400 900;src:url(data:font/woff2;base64,__SG__) format('woff2')} +@font-face{font-family:'Schibsted Grotesk';font-style:italic;font-weight:400 900;src:url(data:font/woff2;base64,__SGI__) format('woff2')} +@font-face{font-family:'Instrument Sans';font-style:normal;font-weight:400 700;src:url(data:font/woff2;base64,__IS__) format('woff2')} +@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:400 700;src:url(data:font/woff2;base64,__JB__) format('woff2')} + +*{margin:0;padding:0;box-sizing:border-box} +html,body{width:772px;height:250px;overflow:hidden} +body{ + background:#F6F5EF; + position:relative; + font-kerning:normal; + -webkit-font-smoothing:antialiased; + text-rendering:geometricPrecision; +} +.band{position:absolute;top:0;left:0;width:772px;height:5px;background:#101b3d} + +.lockup{position:absolute;left:44px;top:30px;display:flex;align-items:center;gap:9px} +.lockup svg{width:26px;height:26px;display:block} +.wordmark{ + font-family:'Schibsted Grotesk';font-weight:700;font-size:21px; + letter-spacing:-0.02em;color:#101b3d;line-height:1; +} +.wordmark i{font-style:normal;color:#ee6c1e} + +.eyebrow{ + position:absolute;left:44px;top:79px; + font-family:'JetBrains Mono';font-weight:500;font-size:10px; + letter-spacing:0.2em;color:#ee6c1e;line-height:1; +} + +.head{ + position:absolute;left:44px;top:102px; + font-family:'Schibsted Grotesk';font-size:33px;line-height:37px; + letter-spacing:-0.035em;color:#23221C;font-weight:600; +} +.head em{display:block;font-style:italic;font-weight:500;color:#ee6c1e} + +.pills{position:absolute;left:44px;top:192px;display:flex;gap:8px} +.pill{ + font-family:'Instrument Sans';font-weight:500;font-size:11px;color:#23221C; + background:#fff;border:1px solid #E6E3D8;border-radius:14px; + height:27px;padding:0 13px;display:flex;align-items:center;line-height:1; +} + +/* Sized to clear the top band and the lower edge, so the arc reads as the + mark rather than as a cropped shape; only the right side bleeds. Tinted + with explicit warm values rather than `opacity`, which mixes the navy + toward the paper through grey and turns the mark muddy. */ +.mark{position:absolute;right:-34px;top:50%;transform:translateY(-50%);width:196px;height:196px} +.mark svg{width:100%;height:100%;display:block} +</style> + +<div class="band"></div> + +<div class="lockup"> + <svg viewBox="4.95 2.75 34.5 34.5" xmlns="http://www.w3.org/2000/svg"> + <g fill="none" stroke-linecap="butt"> + <path d="M 25.4 7.4 A 13 13 0 1 0 25.4 32.6" stroke="#101b3d" stroke-width="8.5"/> + <path d="M 24.7362 7.2498 A 13 13 0 0 1 31.8988 11.3435" stroke="#ee6c1e" stroke-width="8.5"/> + <path d="M 24.7362 32.7502 A 13 13 0 0 0 31.8988 28.6565" stroke="#ee6c1e" stroke-width="8.5"/> + </g> + </svg> + <span class="wordmark">CiteCue<i>.</i></span> +</div> + +<div class="eyebrow">AI CRAWLERS · LLMS.TXT · METADATA</div> + +<div class="head">Answer AI crawlers<em>with your best page.</em></div> + +<!-- Every token here must exist in Citecue_Crawlers::bundled_tokens(). Google-Extended + is deliberately absent: it is robots.txt-only and never fetches a page, so the + plugin cannot serve it and the banner must not claim otherwise. --> +<div class="pills"> + <span class="pill">GPTBot</span> + <span class="pill">ClaudeBot</span> + <span class="pill">PerplexityBot</span> + <span class="pill">GoogleOther</span> +</div> + +<div class="mark"> + <svg viewBox="4.95 2.75 34.5 34.5" xmlns="http://www.w3.org/2000/svg"> + <g fill="none" stroke-linecap="butt"> + <path d="M 25.4 7.4 A 13 13 0 1 0 25.4 32.6" stroke="#E8E5D9" stroke-width="8.5"/> + <path d="M 24.7362 7.2498 A 13 13 0 0 1 31.8988 11.3435" stroke="#F9DDC7" stroke-width="8.5"/> + <path d="M 24.7362 32.7502 A 13 13 0 0 0 31.8988 28.6565" stroke="#F9DDC7" stroke-width="8.5"/> + </g> + </svg> +</div> diff --git a/assets-src/build-assets.sh b/assets-src/build-assets.sh new file mode 100755 index 0000000..474ca65 --- /dev/null +++ b/assets-src/build-assets.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +# +# Regenerates the WordPress.org directory assets in .wordpress-org/. +# +# These are the banner and icon the plugin directory shows above the readme. +# They are NOT part of the plugin: WordPress.org serves them from the `assets/` +# folder of the SVN repository, which sits beside `trunk/` and `tags/` and is +# never installed on a site. Both directories are export-ignored so they can +# never reach the distributed zip. +# +# Rendering is done by headless Chrome rather than a dedicated rasterizer +# because it is the one thing reliably present on a Mac that can lay out real +# webfont text, and --window-size with --force-device-scale-factor gives exact +# pixel dimensions. The 2x pass is a genuine re-render, not an upscale. +# +# Fonts come from the citecue2 site repository rather than being vendored +# here: they are the same faces the brand uses everywhere, and a second copy +# in a second repo is a second thing to update when they change. +# +# Usage: assets-src/build-assets.sh +# CITECUE_FONT_DIR override the font source directory +# CHROME override the Chrome binary + +set -euo pipefail + +ROOT=$(git rev-parse --show-toplevel) +SRC="$ROOT/assets-src" +OUT="$ROOT/.wordpress-org" + +FONT_DIR=${CITECUE_FONT_DIR:-"$HOME/Sites/citecue2/public/fonts"} +CHROME=${CHROME:-"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"} + +if [ ! -x "$CHROME" ]; then + echo "error: Chrome not found at $CHROME (set CHROME=)" >&2 + exit 1 +fi + +for face in schibsted-grotesk schibsted-grotesk-italic instrument-sans jetbrains-mono; do + if [ ! -f "$FONT_DIR/$face.woff2" ]; then + echo "error: $FONT_DIR/$face.woff2 not found (set CITECUE_FONT_DIR=)" >&2 + exit 1 + fi +done + +WORK=$(mktemp -d) +trap 'rm -rf "$WORK"' EXIT + +# Inline the faces. A relative @font-face would be a separate fetch that +# --screenshot does not wait for, and the banner would silently render in a +# fallback with different metrics. +python3 - "$SRC/banner.html" "$FONT_DIR" "$WORK/banner.html" <<'PY' +import base64, pathlib, sys + +template, font_dir, out = (pathlib.Path(p) for p in sys.argv[1:4]) +html = template.read_text() + +for token, face in ( + ("__SG__", "schibsted-grotesk"), + ("__SGI__", "schibsted-grotesk-italic"), + ("__IS__", "instrument-sans"), + ("__JB__", "jetbrains-mono"), +): + if token not in html: + sys.exit(f"error: {token} missing from {template}") + html = html.replace(token, base64.b64encode((font_dir / f"{face}.woff2").read_bytes()).decode()) + +out.write_text(html) +PY + +shot() { # shot <output> <scale> + "$CHROME" --headless --disable-gpu --no-sandbox --hide-scrollbars \ + --force-device-scale-factor="$2" --window-size=772,250 \ + --screenshot="$1" "file://$WORK/banner.html" >/dev/null 2>&1 +} + +mkdir -p "$OUT" +shot "$OUT/banner-772x250.png" 1 +shot "$OUT/banner-1544x500.png" 2 + +# The icon is already vector; Chrome just rasterizes it at the two sizes the +# directory asks for. +cat > "$WORK/icon.html" <<HTML +<meta charset="utf-8"> +<style>*{margin:0;padding:0}html,body{width:256px;height:256px;overflow:hidden} +img{width:256px;height:256px;display:block}</style> +<img src="file://$OUT/icon.svg"> +HTML +"$CHROME" --headless --disable-gpu --no-sandbox --hide-scrollbars \ + --force-device-scale-factor=1 --window-size=256,256 \ + --screenshot="$OUT/icon-256x256.png" "file://$WORK/icon.html" >/dev/null 2>&1 +sips -Z 128 --out "$OUT/icon-128x128.png" "$OUT/icon-256x256.png" >/dev/null + +# A banner at the wrong size is rejected by the directory, and the failure is +# a silently missing header rather than an error, so the sizes are asserted. +check() { # check <file> <w> <h> + local w h + w=$(sips -g pixelWidth "$1" | awk '/pixelWidth/ {print $2}') + h=$(sips -g pixelHeight "$1" | awk '/pixelHeight/ {print $2}') + if [ "$w" != "$2" ] || [ "$h" != "$3" ]; then + echo "error: $(basename "$1") is ${w}x${h}, expected $2x$3" >&2 + exit 1 + fi + echo " $(basename "$1") ${w}x${h}" +} + +echo "Built $OUT" +check "$OUT/banner-772x250.png" 772 250 +check "$OUT/banner-1544x500.png" 1544 500 +check "$OUT/icon-256x256.png" 256 256 +check "$OUT/icon-128x128.png" 128 128 diff --git a/bin/build-plugin-zip.sh b/bin/build-plugin-zip.sh index 337d761..16003ee 100755 --- a/bin/build-plugin-zip.sh +++ b/bin/build-plugin-zip.sh @@ -78,7 +78,7 @@ if [ -n "$stray_root" ]; then exit 1 fi -stray_dev=$(printf '%s\n' "$entries" | grep -E "^$SLUG/(tests/|bin/|docs/|\.github/|composer\.|phpunit|\.phpcs|README\.md)" || true) +stray_dev=$(printf '%s\n' "$entries" | grep -E "^$SLUG/(tests/|bin/|docs/|assets-src/|\.wordpress-org/|\.github/|composer\.|phpunit|\.phpcs|README\.md)" || true) if [ -n "$stray_dev" ]; then echo "error: development files leaked into the archive:" >&2 printf '%s\n' "$stray_dev" >&2 diff --git a/docs/releasing.md b/docs/releasing.md new file mode 100644 index 0000000..4d64023 --- /dev/null +++ b/docs/releasing.md @@ -0,0 +1,63 @@ +# Releasing to WordPress.org + +The directory serves the plugin from Subversion, not from this repository. +`https://plugins.svn.wordpress.org/citecue-ai-auto-fix` has three folders and +they mean different things: + +| Folder | Contents | Installed on a site? | +|---|---|---| +| `trunk/` | the current source | no — the directory installs from a tag | +| `tags/<version>/` | one frozen copy per release | yes, the one named by `Stable tag` | +| `assets/` | banner, icon, screenshots | no — directory page art only | + +`Stable tag:` in `readme.txt` is what decides which tag is served. Trunk is +not it; a release that updates trunk and forgets the tag serves the old +version, and one that tags without moving `Stable tag` serves nothing new. + +## Before you start + +`bin/build-plugin-zip.sh <ref>` refuses to build when the three version +strings disagree (`citecue.php` header, `CITECUE_VERSION`, `readme.txt` +`Stable tag`), so run it first — it is the cheapest check that a release is +coherent, and it prints exactly what will ship. + +## Publishing a version + +```bash +# 1. A working copy. Empty folders, so this is fast. +svn checkout https://plugins.svn.wordpress.org/citecue-ai-auto-fix /tmp/citecue-svn + +# 2. Trunk, from the tag being released — never from the working tree. +# `git archive` honours .gitattributes export-ignore, so tests, CI config +# and Composer files cannot be swept in by accident. +rm -rf /tmp/citecue-svn/trunk/* +git archive --format=tar v1.2.0 | tar -x -C /tmp/citecue-svn/trunk + +# 3. Directory page art. Only when it has changed. +cp .wordpress-org/* /tmp/citecue-svn/assets/ + +# 4. Stage, then freeze a tag from trunk. +cd /tmp/citecue-svn +svn add --force trunk assets +svn copy trunk tags/1.2.0 + +# 5. One commit for both. Prompts for the wordpress.org password. +svn commit -m "Release 1.2.0" --username citecue +``` + +Commit trunk and the tag together. A trunk-only commit points `Stable tag` at +a tag that does not exist yet, and the directory serves an error to everyone +updating in the window between the two. + +`svn` is not installed on macOS by default: `brew install subversion`. + +## Regenerating the directory art + +`assets-src/build-assets.sh` rebuilds `.wordpress-org/` from +`assets-src/banner.html` and `.wordpress-org/icon.svg`. It needs headless +Chrome and the brand fonts from the citecue2 site repository — see the +script's header for the two overrides. + +Both folders are `export-ignore`d, and `bin/build-plugin-zip.sh` fails if +either ever appears inside the archive: they belong beside the plugin in SVN, +never inside it. From 1d3c61803ab4b38e562701e8cc2242ef8b28afee Mon Sep 17 00:00:00 2001 From: henry-idingo <henry@idingo.io> Date: Thu, 3 Sep 2026 17:33:12 +0200 Subject: [PATCH 6/9] Let a page that quotes the marker still get its block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "already placed" check was a bare substring search, so a page quoting `<section data-citecue="page-enhancement">` in a code sample read as a page that already had one, and would have been denied its enhancement forever. This class already records that lesson about the head — a page that quotes markup contains whatever it quotes — and the body check had not learned it. Scoped to a real opening tag, which escaped markup cannot satisfy because the escaped form has no `<`, and to the whole attribute value rather than a prefix. The value boundary is not hypothetical: CiteCue composes the FAQ payload inside the block as `data-citecue="page-enhancement-faq"`, so a prefix match treats that sibling as the section marker, and the section is what delimits the region CiteCue owns. Both existing guarantees still hold — a real block is still detected however it is quoted, and a duplicate is still refused. Found by CodeRabbit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --- includes/class-citecue-seo-head.php | 16 +++++++++- tests/cases/test-seo-head-delivery.php | 44 ++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/includes/class-citecue-seo-head.php b/includes/class-citecue-seo-head.php index 45f9ab0..a1f4158 100644 --- a/includes/class-citecue-seo-head.php +++ b/includes/class-citecue-seo-head.php @@ -97,8 +97,22 @@ class Citecue_Seo_Head { * origin is fronted by CiteCue's Worker, or whose post content already * carries the section, the block is in the document before this plugin sees * it — and a second copy is the one failure mode a visitor actually notices. + * + * Scoped to a real opening tag, and to the whole attribute value, for the + * reason this class already learned once about the head (see enhance()): a + * page that quotes markup in a code sample contains whatever it quotes. A + * bare substring search reads `<section data-citecue="page-enhancement">` + * in a documentation page as a block already placed, and silently withholds + * that page's enhancement forever. Requiring `<tag …` means escaped markup + * cannot match, because the escaped form has no `<`. + * + * The trailing boundary matters for a live value, not a hypothetical one: + * CiteCue composes the FAQ payload inside the block as + * `data-citecue="page-enhancement-faq"`, so a prefix match would treat that + * sibling as the section marker. The section is what delimits the region + * CiteCue owns, and only the section may answer this question. */ - const BLOCK_MARKER_PATTERN = '#data-citecue\s*=\s*["\']?page-enhancement#i'; + const BLOCK_MARKER_PATTERN = '#<[a-z][^>]*\sdata-citecue\s*=\s*(["\']?)page-enhancement\1[\s/>]#i'; /** * Largest page-enhancement block that may be injected, in bytes. Mirrors diff --git a/tests/cases/test-seo-head-delivery.php b/tests/cases/test-seo-head-delivery.php index 17efe1f..4d62bf3 100644 --- a/tests/cases/test-seo-head-delivery.php +++ b/tests/cases/test-seo-head-delivery.php @@ -780,6 +780,50 @@ public function test_the_marker_is_recognised_whatever_its_quoting() { $this->assertSame( $already, $delivered ); } + /** + * A page that QUOTES the marker in a code sample has not been given a + * block, and must still get one. This is the lesson enhance() already + * records about the head, applied to the body: escaped markup in page + * content is content, not markup, and a substring search cannot tell the + * difference — it would withhold that page's enhancement forever. + * + * @return void + */ + public function test_a_page_quoting_the_marker_still_gets_its_block() { + $this->configure_delivery(); + $url = $this->fake_visitor_request(); + $this->plugin->cache->set_seo_head( $url, '', self::BODY_BLOCK ); + + $injector = $this->seo_head(); + $this->arrange_capture( $injector ); + + $sample = '<p>Add <code><section data-citecue="page-enhancement"></code> to your template.</p>'; + $delivered = $injector->enhance( $this->document( '', $sample ) ); + + $this->assertStringContainsString( self::BODY_BLOCK, $delivered ); + } + + /** + * The FAQ payload CiteCue composes INSIDE the block carries + * `data-citecue="page-enhancement-faq"`. It is a sibling value, not the + * section marker, so on its own it must not read as a block already placed. + * + * @return void + */ + public function test_the_faq_sibling_value_is_not_the_section_marker() { + $this->configure_delivery(); + $url = $this->fake_visitor_request(); + $this->plugin->cache->set_seo_head( $url, '', self::BODY_BLOCK ); + + $injector = $this->seo_head(); + $this->arrange_capture( $injector ); + + $faq_only = '<script type="application/ld+json" data-citecue="page-enhancement-faq">{}</script>'; + $delivered = $injector->enhance( $this->document( '', $faq_only ) ); + + $this->assertStringContainsString( self::BODY_BLOCK, $delivered ); + } + /** * A block over the wire cap is dropped rather than placed. The endpoint * refuses to send one this large, so a block that arrives anyway is a From 0b07b85c7fb421239f5158bfd6178ed56506b1f9 Mon Sep 17 00:00:00 2001 From: henry-idingo <henry@idingo.io> Date: Thu, 3 Sep 2026 17:46:23 +0200 Subject: [PATCH 7/9] Run the injection through a real WordPress, not just the suite The PHPUnit suite calls the injector directly or through a synthetic buffer. That covers the decisions but not what the decisions are for: whether the block reaches a browser at all, through a real theme, a real wp_head()/ wp_footer(), and whichever output-buffer mechanism the WordPress under test provides. On 6.9+ that is core's template enhancement filter, which nothing in the suite exercises end to end. `bin/local-rig.sh` stands up WordPress from the core the tests already vendor, symlinks the plugin in, and points it at a stub CiteCue through the plugin's own CITECUE_API_BASE override. `verify` asserts against rendered pages and tears down; `up` leaves it running to look at. Ten checks, and two of them are the ones this rig exists for. The FAQ payload is real JSON-LD in a real script element, so the block surviving intact is demonstrated rather than argued. And a page that quotes the marker in a code sample still gets its block, which is the CodeRabbit fix proven where it matters rather than in a regex test. The stub also logs what the plugin sent, so the Bearer token and channel header are asserted rather than assumed. bin/ is already export-ignored, so none of this can reach the plugin zip; the guard in build-plugin-zip.sh covers it either way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --- bin/local-rig.sh | 101 +++++++++++++++++++++++++ bin/rig/fake-citecue.php | 110 +++++++++++++++++++++++++++ bin/rig/seed.php | 60 +++++++++++++++ bin/rig/theme-index.php | 27 +++++++ bin/rig/verify.php | 154 ++++++++++++++++++++++++++++++++++++++ bin/rig/wp-config.php.tpl | 34 +++++++++ 6 files changed, 486 insertions(+) create mode 100755 bin/local-rig.sh create mode 100644 bin/rig/fake-citecue.php create mode 100644 bin/rig/seed.php create mode 100644 bin/rig/theme-index.php create mode 100644 bin/rig/verify.php create mode 100644 bin/rig/wp-config.php.tpl diff --git a/bin/local-rig.sh b/bin/local-rig.sh new file mode 100755 index 0000000..8913c27 --- /dev/null +++ b/bin/local-rig.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# +# Stands up a real WordPress with this plugin active, pointed at a stub CiteCue, +# and asserts that a page enhancement actually lands on a rendered page. +# +# The PHPUnit suite calls the injector directly or through a synthetic buffer. +# That covers the decisions but not the thing the decisions are for: whether the +# block reaches a browser, through a real theme, a real `wp_head()`/`wp_footer()` +# and whichever output-buffer mechanism this WordPress provides. On 6.9+ that is +# core's template enhancement filter, which no unit test exercises end to end. +# +# The stub stands in for app.citecue.com because the real acceptance test needs +# a deployed server, a CiteCue account and an approved block. This one needs +# none of those and can run on any branch, at any time. +# +# Usage: +# bin/local-rig.sh up build the rig and leave it running +# bin/local-rig.sh verify build it, assert, tear down, exit non-zero on failure +# bin/local-rig.sh down stop the servers +# +# RIG_DIR where to build it (default: a temp directory) +# RIG_DB_HOST MySQL/MariaDB host:port (default: 127.0.0.1:13306) +# RIG_DB_USER database user (default: root) + +set -euo pipefail + +ROOT=$(git rev-parse --show-toplevel) +RIG_DIR=${RIG_DIR:-"${TMPDIR:-/tmp}/citecue-rig"} +DB_HOST=${RIG_DB_HOST:-127.0.0.1:13306} +DB_USER=${RIG_DB_USER:-root} +API_PORT=13380 +WP_PORT=13390 + +WP_SRC="$ROOT/vendor/roots/wordpress-no-content" +CORE_VERSION_FILE="$WP_SRC/wp-includes/version.php" + +down() { + for port in $API_PORT $WP_PORT; do + pids=$(lsof -ti tcp:"$port" 2>/dev/null || true) + [ -n "$pids" ] && kill $pids 2>/dev/null || true + done + echo "rig stopped" +} + +if [ "${1:-up}" = "down" ]; then + down + exit 0 +fi + +if [ ! -f "$CORE_VERSION_FILE" ]; then + echo "error: WordPress core not found at $WP_SRC" >&2 + echo " run 'composer install' first — the rig borrows the core the tests use." >&2 + exit 1 +fi + +if ! mysqladmin --protocol=TCP --host="${DB_HOST%%:*}" --port="${DB_HOST##*:}" -u "$DB_USER" ping >/dev/null 2>&1; then + echo "error: no database reachable at $DB_HOST as '$DB_USER' (set RIG_DB_HOST / RIG_DB_USER)" >&2 + exit 1 +fi + +down >/dev/null 2>&1 || true +rm -rf "$RIG_DIR" +mkdir -p "$RIG_DIR/fake-api" + +mysql --protocol=TCP --host="${DB_HOST%%:*}" --port="${DB_HOST##*:}" -u "$DB_USER" \ + -e "DROP DATABASE IF EXISTS citecue_rig; CREATE DATABASE citecue_rig;" + +cp -R "$WP_SRC" "$RIG_DIR/wp" +mkdir -p "$RIG_DIR/wp/wp-content/themes/citecue-rig" "$RIG_DIR/wp/wp-content/plugins" +ln -sfn "$ROOT" "$RIG_DIR/wp/wp-content/plugins/citecue-ai-auto-fix" + +cp "$ROOT/bin/rig/theme-index.php" "$RIG_DIR/wp/wp-content/themes/citecue-rig/index.php" +printf '/*\nTheme Name: CiteCue Rig\nVersion: 1.0\n*/\n' > "$RIG_DIR/wp/wp-content/themes/citecue-rig/style.css" +cp "$ROOT/bin/rig/fake-citecue.php" "$RIG_DIR/fake-api/router.php" + +sed -e "s|__DB_HOST__|$DB_HOST|" -e "s|__DB_USER__|$DB_USER|" \ + -e "s|__API_PORT__|$API_PORT|" -e "s|__WP_PORT__|$WP_PORT|" \ + "$ROOT/bin/rig/wp-config.php.tpl" > "$RIG_DIR/wp/wp-config.php" + +( cd "$RIG_DIR/fake-api" && nohup php -S "127.0.0.1:$API_PORT" router.php </dev/null >"$RIG_DIR/fake-api.log" 2>&1 & ) +( cd "$RIG_DIR/wp" && nohup php -S "127.0.0.1:$WP_PORT" </dev/null >"$RIG_DIR/wp.log" 2>&1 & ) + +for _ in $(seq 1 20); do + curl -sf "http://127.0.0.1:$API_PORT/api/delivery/v1/crawlers" >/dev/null 2>&1 && break + sleep 0.5 +done + +RIG_DIR="$RIG_DIR" php "$ROOT/bin/rig/seed.php" + +echo +echo "WordPress : http://127.0.0.1:$WP_PORT/protein-guide/" +echo "Stub API : http://127.0.0.1:$API_PORT (requests logged to $RIG_DIR/fake-api/requests.log)" +echo "Rig : $RIG_DIR" + +if [ "${1:-up}" = "verify" ]; then + echo + status=0 + RIG_DIR="$RIG_DIR" WP_PORT="$WP_PORT" php "$ROOT/bin/rig/verify.php" || status=$? + down >/dev/null 2>&1 + exit $status +fi diff --git a/bin/rig/fake-citecue.php b/bin/rig/fake-citecue.php new file mode 100644 index 0000000..f4d2329 --- /dev/null +++ b/bin/rig/fake-citecue.php @@ -0,0 +1,110 @@ +<?php +/** + * A stand-in for app.citecue.com, for exercising the plugin's delivery paths + * against a real HTTP round trip. Logs every request so the test can assert on + * what the plugin actually sent, not just on what it did with the answer. + */ + +$log = __DIR__ . '/requests.log'; +$path = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH ); + +$headers = function_exists( 'getallheaders' ) ? getallheaders() : array(); +file_put_contents( + $log, + wp_json_encode_fallback( + array( + 'path' => $_SERVER['REQUEST_URI'], + 'method' => $_SERVER['REQUEST_METHOD'], + 'authorization' => $headers['Authorization'] ?? ( $headers['authorization'] ?? '' ), + 'channel' => $headers['X-Citecue-Channel'] ?? ( $headers['x-citecue-channel'] ?? '' ), + ) + ) . "\n", + FILE_APPEND +); + +function wp_json_encode_fallback( $v ) { + return json_encode( $v, JSON_UNESCAPED_SLASHES ); +} + +/** + * The block, in the shape CiteCue composes them: a marked section of grounded + * facts and FAQ entries, carrying its own FAQPage JSON-LD. The script element + * is the part any "sanitize the remote HTML" reflex would destroy, so it is + * here on purpose. + */ +function citecue_block() { + $faq = json_encode( + array( + '@context' => 'https://schema.org', + '@type' => 'FAQPage', + 'mainEntity' => array( + array( + '@type' => 'Question', + 'name' => 'What does Acme make?', + 'acceptedAnswer' => array( + '@type' => 'Answer', + 'text' => 'Protein bars, made in Leeds.', + ), + ), + ), + ), + JSON_UNESCAPED_SLASHES + ); + + return '<section data-citecue="page-enhancement">' + . '<h2>About Acme</h2>' + . '<ul><li>Founded 2019</li><li>Made in Leeds</li></ul>' + . '<details><summary>What does Acme make?</summary><p>Protein bars, made in Leeds.</p></details>' + . '<script type="application/ld+json" data-citecue="page-enhancement-faq">' . $faq . '</script>' + . '</section>'; +} + +header( 'Cache-Control: private, max-age=300' ); + +if ( '/api/delivery/v2/seo-head' === $path ) { + header( 'Content-Type: application/json; charset=utf-8' ); + echo json_encode( + array( + 'head' => '<meta data-citecue="og" property="og:title" content="Acme — protein bars from Leeds" />', + 'body' => citecue_block(), + 'dedupe' => array( + 'ogProperties' => array( 'og:title' ), + 'jsonLdTypes' => array( 'FAQPage' ), + ), + ), + JSON_UNESCAPED_SLASHES + ); + exit; +} + +if ( '/api/delivery/v2/config' === $path ) { + header( 'Content-Type: application/json; charset=utf-8' ); + echo json_encode( + array( + 'projects' => array( + array( + 'publicKey' => 'pk_localtest', + 'domain' => '127.0.0.1', + 'enabled' => true, + 'serveLlmsTxt' => true, + 'contentPush' => true, + ), + ), + ) + ); + exit; +} + +if ( '/api/delivery/v1/crawlers' === $path ) { + header( 'Content-Type: application/json; charset=utf-8' ); + echo json_encode( + array( + 'version' => 1, + 'tokens' => array( 'GPTBot', 'ClaudeBot' ), + ) + ); + exit; +} + +http_response_code( 404 ); +echo 'not_optimized'; diff --git a/bin/rig/seed.php b/bin/rig/seed.php new file mode 100644 index 0000000..0bc1139 --- /dev/null +++ b/bin/rig/seed.php @@ -0,0 +1,60 @@ +<?php +/** + * Installs the rig's WordPress, activates the plugin and puts it in the state a + * real connect claim would leave it in — without driving the browser redirect, + * which is not what this rig is testing. + * + * @package Citecue + */ + +$rig = getenv( 'RIG_DIR' ); +if ( ! $rig ) { + fwrite( STDERR, "error: RIG_DIR not set\n" ); + exit( 1 ); +} + +define( 'WP_INSTALLING', true ); +require_once $rig . '/wp/wp-load.php'; +require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + +if ( ! is_blog_installed() ) { + wp_install( 'CiteCue Rig', 'admin', 'admin@example.test', true, '', 'rig-password' ); +} + +switch_theme( 'citecue-rig' ); +update_option( 'permalink_structure', '/%postname%/' ); +activate_plugin( 'citecue-ai-auto-fix/citecue.php' ); + +if ( ! is_plugin_active( 'citecue-ai-auto-fix/citecue.php' ) ) { + fwrite( STDERR, "error: the plugin did not activate\n" ); + exit( 1 ); +} + +$plugin = Citecue_Plugin::instance(); +$plugin->settings->update( + array( + 'api_key' => 'ck_live_rig', + 'public_key' => 'pk_rig', + 'project_domain' => '127.0.0.1', + 'seo_head_enabled' => true, + 'seo_head_reported' => true, + 'capabilities_reported' => $plugin->settings->active_delivery_capabilities(), + ) +); + +if ( ! get_page_by_path( 'protein-guide' ) ) { + wp_insert_post( + array( + 'post_title' => 'Protein guide', + 'post_name' => 'protein-guide', + 'post_content' => '<p>Everything we know about protein bars, from sourcing to storage.</p>', + 'post_status' => 'publish', + 'post_type' => 'page', + ) + ); +} + +flush_rewrite_rules( true ); + +printf( "WordPress %s, theme citecue-rig, plugin %s\n", get_bloginfo( 'version' ), CITECUE_VERSION ); +printf( "declared capabilities: %s\n", implode( ', ', $plugin->settings->active_delivery_capabilities() ) ); diff --git a/bin/rig/theme-index.php b/bin/rig/theme-index.php new file mode 100644 index 0000000..488679b --- /dev/null +++ b/bin/rig/theme-index.php @@ -0,0 +1,27 @@ +<?php +/** + * The rig's whole theme. Deliberately bare: the assertion is about WHERE the + * block lands, and a document with nothing else in it makes that unambiguous. + * + * @package Citecue + */ +?> +<!DOCTYPE html> +<html <?php language_attributes(); ?>> +<head> +<meta charset="<?php bloginfo( 'charset' ); ?>" /> +<?php wp_head(); ?> +</head> +<body <?php body_class(); ?>> +<main> +<?php +while ( have_posts() ) { + the_post(); + echo '<h1>' . esc_html( get_the_title() ) . '</h1>'; + the_content(); +} +?> +</main> +<?php wp_footer(); ?> +</body> +</html> diff --git a/bin/rig/verify.php b/bin/rig/verify.php new file mode 100644 index 0000000..44787a4 --- /dev/null +++ b/bin/rig/verify.php @@ -0,0 +1,154 @@ +<?php +/** + * Asserts, against a real rendered page, the things the unit suite cannot see: + * that the block reaches the browser at all, through this WordPress's own + * output-buffer mechanism, and lands in the right half of the document. + * + * @package Citecue + */ + +$rig = getenv( 'RIG_DIR' ); +$port = getenv( 'WP_PORT' ); +$url = "http://127.0.0.1:$port/protein-guide/"; + +require_once $rig . '/wp/wp-load.php'; + +$failures = array(); + +/** + * Records one assertion. + * + * @param string $name What is being asserted. + * @param bool $ok Whether it held. + * @param string $note Detail printed either way. + */ +function check( $name, $ok, $note = '' ) { + global $failures; + printf( " %s %s%s\n", $ok ? 'PASS' : 'FAIL', $name, '' !== $note ? " ($note)" : '' ); + if ( ! $ok ) { + $failures[] = $name; + } +} + +/** Fetches the rendered page. */ +function render( $url ) { + $ch = curl_init( $url ); + curl_setopt_array( + $ch, + array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 15, + ) + ); + $html = curl_exec( $ch ); + curl_close( $ch ); + return (string) $html; +} + +/** + * Runs the plugin's due background jobs; the rig disables real WP-Cron. + * + * The cache drop is load-bearing. This process bootstrapped WordPress before + * the cold page view scheduled anything, so its options cache still holds the + * cron array from before — and reading that would find no jobs and silently + * verify nothing. The page view happened in the server's process, not this one. + */ +function run_jobs() { + wp_cache_delete( 'cron', 'options' ); + wp_cache_delete( 'alloptions', 'options' ); + + foreach ( (array) _get_cron_array() as $ts => $hooks ) { + foreach ( $hooks as $hook => $events ) { + if ( 0 !== strpos( $hook, 'citecue' ) ) { + continue; + } + foreach ( $events as $event ) { + do_action_ref_array( $hook, (array) $event['args'] ); + wp_unschedule_event( $ts, $hook, (array) $event['args'] ); + } + } + } +} + +echo "Verifying against $url\n"; + +// A visitor never waits on CiteCue: the first view of an uncached URL injects +// nothing and queues the fetch instead. +$cold = render( $url ); +check( 'cold view injects nothing', false === strpos( $cold, 'data-citecue="page-enhancement"' ) ); + +run_jobs(); + +$warm = render( $url ); +$sections = preg_match_all( '#<section[^>]*data-citecue="page-enhancement"#i', $warm ); +check( 'warm view injects the block exactly once', 1 === $sections, "found $sections" ); + +$head_end = stripos( $warm, '</head>' ); +$body_end = strripos( $warm, '</body>' ); +$at = stripos( $warm, '<section data-citecue="page-enhancement"' ); +check( 'block is in the body, before </body>', $at > $head_end && $at < $body_end ); +check( 'head half injected too', false !== strpos( $warm, 'og:title' ) ); + +// The FAQ payload is the GEO point of the block, and the first casualty of any +// reflex to sanitize the response. +preg_match( '#<script type="application/ld\+json" data-citecue="page-enhancement-faq">(.*?)</script>#s', $warm, $m ); +$ld = isset( $m[1] ) ? json_decode( $m[1], true ) : null; +check( 'FAQPage JSON-LD survives intact', is_array( $ld ) && 'FAQPage' === ( $ld['@type'] ?? '' ) ); + +// Repeat views must not accumulate copies. +$again = preg_match_all( '#<section[^>]*data-citecue="page-enhancement"#i', render( $url ) ); +check( 'a second view does not add a second copy', 1 === $again, "found $again" ); + +// A page that already carries the section keeps exactly the one it has. +$page = get_page_by_path( 'protein-guide' ); +$kept = $page->post_content; +wp_update_post( + array( + 'ID' => $page->ID, + 'post_content' => '<p>Ours.</p><section data-citecue="page-enhancement"><h2>Already here</h2></section>', + ) +); +$owned = render( $url ); +check( + 'a page that already has the block is left alone', + 1 === preg_match_all( '#<section[^>]*data-citecue="page-enhancement"#i', $owned ) + && false !== strpos( $owned, 'Already here' ) + && false === strpos( $owned, 'About Acme' ), + // Guards against passing vacuously: this only means anything while the + // block is cached and would otherwise have been placed. + 'cached block present: ' . ( false !== strpos( $warm, 'About Acme' ) ? 'yes' : 'NO — check is vacuous' ) +); + +// A page that merely QUOTES the marker has not been given a block, and must +// still get one. +wp_update_post( + array( + 'ID' => $page->ID, + 'post_content' => '<p>Add <code><section data-citecue="page-enhancement"></code> to your template.</p>', + ) +); +$quoted = render( $url ); +check( + 'a page quoting the marker still gets its block', + 1 === preg_match_all( '#<section[^>]*data-citecue="page-enhancement"#i', $quoted ) + && false !== strpos( $quoted, 'About Acme' ) +); + +wp_update_post( + array( + 'ID' => $page->ID, + 'post_content' => $kept, + ) +); + +// The request the plugin actually made, not just what it did with the answer. +$log = @file_get_contents( $rig . '/fake-api/requests.log' ); +check( 'delivery read carried the org key as a Bearer token', false !== strpos( (string) $log, 'Bearer ck_live_rig' ) ); +check( 'delivery read identified the channel', false !== strpos( (string) $log, '"channel":"WordPress"' ) ); + +echo "\n"; +if ( $failures ) { + printf( "%d check(s) failed: %s\n", count( $failures ), implode( '; ', $failures ) ); + exit( 1 ); +} +echo "all checks passed\n"; diff --git a/bin/rig/wp-config.php.tpl b/bin/rig/wp-config.php.tpl new file mode 100644 index 0000000..a60db78 --- /dev/null +++ b/bin/rig/wp-config.php.tpl @@ -0,0 +1,34 @@ +<?php +/** Generated by bin/local-rig.sh. Not a real site; do not copy these values. */ +define( 'DB_NAME', 'citecue_rig' ); +define( 'DB_USER', '__DB_USER__' ); +define( 'DB_PASSWORD', '' ); +define( 'DB_HOST', '__DB_HOST__' ); +define( 'DB_CHARSET', 'utf8mb4' ); +define( 'DB_COLLATE', '' ); + +$table_prefix = 'wp_'; + +define( 'WP_DEBUG', true ); +define( 'WP_DEBUG_LOG', true ); +define( 'WP_DEBUG_DISPLAY', false ); + +// The rig runs the plugin's jobs itself, so a page view never triggers one and +// the cold/warm distinction stays under the test's control. +define( 'DISABLE_WP_CRON', true ); + +// Point the plugin at the stub. This is the plugin's own supported override for +// a self-hosted or staging origin — see Citecue_Settings::pinned_api_base(). +define( 'CITECUE_API_BASE', 'http://127.0.0.1:__API_PORT__' ); + +define( 'AUTH_KEY', 'rig' ); define( 'SECURE_AUTH_KEY', 'rig' ); define( 'LOGGED_IN_KEY', 'rig' ); +define( 'NONCE_KEY', 'rig' ); define( 'AUTH_SALT', 'rig' ); define( 'SECURE_AUTH_SALT', 'rig' ); +define( 'LOGGED_IN_SALT', 'rig' ); define( 'NONCE_SALT', 'rig' ); + +define( 'WP_HOME', 'http://127.0.0.1:__WP_PORT__' ); +define( 'WP_SITEURL', 'http://127.0.0.1:__WP_PORT__' ); + +if ( ! defined( 'ABSPATH' ) ) { + define( 'ABSPATH', __DIR__ . '/' ); +} +require_once ABSPATH . 'wp-settings.php'; From 9d5bdf1f01b2b55877cc02e21476ba3297d45735 Mon Sep 17 00:00:00 2001 From: henry-idingo <henry@idingo.io> Date: Thu, 3 Sep 2026 18:23:58 +0200 Subject: [PATCH 8/9] Point the rig at a real CiteCue, and prove the body-only path against one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rig could only talk to its own stub, which meant every assertion rested on my reading of the contract rather than on the server's behaviour. It now takes RIG_API_BASE / RIG_API_KEY / RIG_PUBLIC_KEY and skips the stub entirely, which is also what the post-deploy acceptance test will need. Run against a local build of the merged app, the real endpoint answers `{"head":"","body":"<section data-citecue=\"page-enhancement\">…"}` — a populated body with an empty head, for a project that has a block but no enriched page. That is the exact case the previous `refresh()` discarded by reading an empty head as an empty answer, so the fix is now demonstrated against the server rather than against my guess about it. Two checks had to learn what they were actually asserting: The head half is now conditional on what the server sent, because the two halves are independent and a project with a block and no enriched page is a legitimate answer. Asserting a head unconditionally failed the plugin for the server's correct behaviour. It reads the plugin's own cache for the answer rather than lookup_url(), which derives from $_SERVER and is empty under CLI — that returned '' and skipped the assertion instead of making it, in the mode where it should have run. The channel assertion is built with strtolower() rather than written as a literal. phpcbf's prose sniff rewrites a bare "wordpress" into "WordPress", which silently turned a passing check into one that failed against correct behaviour. The plugin's own sources are unaffected — the header value is lowercase in every one — but a test that phpcbf can invert is a test that will be inverted again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --- bin/local-rig.sh | 45 +++++++++++++++++++++++++++++++-------- bin/rig/seed.php | 4 ++-- bin/rig/verify.php | 37 ++++++++++++++++++++++++++++---- bin/rig/wp-config.php.tpl | 2 +- 4 files changed, 72 insertions(+), 16 deletions(-) diff --git a/bin/local-rig.sh b/bin/local-rig.sh index 8913c27..43fff64 100755 --- a/bin/local-rig.sh +++ b/bin/local-rig.sh @@ -21,6 +21,17 @@ # RIG_DIR where to build it (default: a temp directory) # RIG_DB_HOST MySQL/MariaDB host:port (default: 127.0.0.1:13306) # RIG_DB_USER database user (default: root) +# +# To point at a REAL CiteCue instead of the stub — a local build, a staging +# origin, or app.citecue.com for the post-deploy acceptance test — set all three: +# +# RIG_API_BASE e.g. http://127.0.0.1:8787 +# RIG_API_KEY the org API key (ck_live_...) +# RIG_PUBLIC_KEY the project's public key (pk_...) +# +# The stub is then not started, and the checks that assert on what the plugin +# SENT are skipped — they read the stub's own request log, which a real server +# does not keep for us. set -euo pipefail @@ -31,6 +42,14 @@ DB_USER=${RIG_DB_USER:-root} API_PORT=13380 WP_PORT=13390 +REAL_API=${RIG_API_BASE:-} +API_KEY=${RIG_API_KEY:-ck_live_rig} +PUBLIC_KEY=${RIG_PUBLIC_KEY:-pk_rig} +API_BASE="http://127.0.0.1:$API_PORT" +if [ -n "$REAL_API" ]; then + API_BASE="$REAL_API" +fi + WP_SRC="$ROOT/vendor/roots/wordpress-no-content" CORE_VERSION_FILE="$WP_SRC/wp-includes/version.php" @@ -74,28 +93,36 @@ printf '/*\nTheme Name: CiteCue Rig\nVersion: 1.0\n*/\n' > "$RIG_DIR/wp/wp-conte cp "$ROOT/bin/rig/fake-citecue.php" "$RIG_DIR/fake-api/router.php" sed -e "s|__DB_HOST__|$DB_HOST|" -e "s|__DB_USER__|$DB_USER|" \ - -e "s|__API_PORT__|$API_PORT|" -e "s|__WP_PORT__|$WP_PORT|" \ + -e "s|__API_BASE__|$API_BASE|" -e "s|__WP_PORT__|$WP_PORT|" \ "$ROOT/bin/rig/wp-config.php.tpl" > "$RIG_DIR/wp/wp-config.php" -( cd "$RIG_DIR/fake-api" && nohup php -S "127.0.0.1:$API_PORT" router.php </dev/null >"$RIG_DIR/fake-api.log" 2>&1 & ) +if [ -z "$REAL_API" ]; then + ( cd "$RIG_DIR/fake-api" && nohup php -S "127.0.0.1:$API_PORT" router.php </dev/null >"$RIG_DIR/fake-api.log" 2>&1 & ) +fi ( cd "$RIG_DIR/wp" && nohup php -S "127.0.0.1:$WP_PORT" </dev/null >"$RIG_DIR/wp.log" 2>&1 & ) -for _ in $(seq 1 20); do - curl -sf "http://127.0.0.1:$API_PORT/api/delivery/v1/crawlers" >/dev/null 2>&1 && break - sleep 0.5 -done +if [ -z "$REAL_API" ]; then + for _ in $(seq 1 20); do + curl -sf "$API_BASE/api/delivery/v1/crawlers" >/dev/null 2>&1 && break + sleep 0.5 + done +fi -RIG_DIR="$RIG_DIR" php "$ROOT/bin/rig/seed.php" +RIG_DIR="$RIG_DIR" RIG_API_KEY="$API_KEY" RIG_PUBLIC_KEY="$PUBLIC_KEY" php "$ROOT/bin/rig/seed.php" echo echo "WordPress : http://127.0.0.1:$WP_PORT/protein-guide/" -echo "Stub API : http://127.0.0.1:$API_PORT (requests logged to $RIG_DIR/fake-api/requests.log)" +if [ -n "$REAL_API" ]; then + echo "CiteCue : $API_BASE (real server)" +else + echo "Stub API : $API_BASE (requests logged to $RIG_DIR/fake-api/requests.log)" +fi echo "Rig : $RIG_DIR" if [ "${1:-up}" = "verify" ]; then echo status=0 - RIG_DIR="$RIG_DIR" WP_PORT="$WP_PORT" php "$ROOT/bin/rig/verify.php" || status=$? + RIG_DIR="$RIG_DIR" WP_PORT="$WP_PORT" RIG_API_BASE="$REAL_API" php "$ROOT/bin/rig/verify.php" || status=$? down >/dev/null 2>&1 exit $status fi diff --git a/bin/rig/seed.php b/bin/rig/seed.php index 0bc1139..6941b2e 100644 --- a/bin/rig/seed.php +++ b/bin/rig/seed.php @@ -33,8 +33,8 @@ $plugin = Citecue_Plugin::instance(); $plugin->settings->update( array( - 'api_key' => 'ck_live_rig', - 'public_key' => 'pk_rig', + 'api_key' => getenv( 'RIG_API_KEY' ) ?: 'ck_live_rig', + 'public_key' => getenv( 'RIG_PUBLIC_KEY' ) ?: 'pk_rig', 'project_domain' => '127.0.0.1', 'seo_head_enabled' => true, 'seo_head_reported' => true, diff --git a/bin/rig/verify.php b/bin/rig/verify.php index 44787a4..a177bbe 100644 --- a/bin/rig/verify.php +++ b/bin/rig/verify.php @@ -87,7 +87,18 @@ function run_jobs() { $body_end = strripos( $warm, '</body>' ); $at = stripos( $warm, '<section data-citecue="page-enhancement"' ); check( 'block is in the body, before </body>', $at > $head_end && $at < $body_end ); -check( 'head half injected too', false !== strpos( $warm, 'og:title' ) ); +// Conditional on what the server actually sent. The two halves are +// independent, and a project with a block but no enriched page is a legitimate +// answer — `head: ''` with a populated `body` is exactly what the endpoint +// returns then. Asserting a head unconditionally would fail the plugin for the +// server's correct behaviour. +$cached = Citecue_Plugin::instance()->cache->get_seo_head( $url ); +$sent_head = is_array( $cached ) ? (string) $cached['block'] : ''; +if ( '' === $sent_head ) { + echo " SKIP head half (this fixture's project has no enriched page, so the server sent head: '')\n"; +} else { + check( 'head half injected too', false !== strpos( $warm, 'og:title' ) ); +} // The FAQ payload is the GEO point of the block, and the first casualty of any // reflex to sanitize the response. @@ -142,9 +153,27 @@ function run_jobs() { ); // The request the plugin actually made, not just what it did with the answer. -$log = @file_get_contents( $rig . '/fake-api/requests.log' ); -check( 'delivery read carried the org key as a Bearer token', false !== strpos( (string) $log, 'Bearer ck_live_rig' ) ); -check( 'delivery read identified the channel', false !== strpos( (string) $log, '"channel":"WordPress"' ) ); +// Only the stub keeps a log for us, so these are skipped rather than failed +// when the rig is pointed at a real CiteCue. +if ( getenv( 'RIG_API_BASE' ) ) { + echo " SKIP request-log checks (real CiteCue keeps no log for us)\n"; +} else { + $log = @file_get_contents( $rig . '/fake-api/requests.log' ); + check( 'delivery read carried the org key as a Bearer token', false !== strpos( (string) $log, 'Bearer ck_live_rig' ) ); + // Built with strtolower() rather than written as a literal. The channel is a + // protocol value the plugin sends lowercased, and phpcbf's prose sniff + // rewrites a bare "WordPress" literal into "WordPress" — which silently + // turns this into an assertion that fails against correct behaviour. + $expected_channel = strtolower( 'WordPress' ); + $sent_channel = ''; + foreach ( explode( "\n", trim( (string) $log ) ) as $line ) { + $entry = json_decode( $line, true ); + if ( is_array( $entry ) && false !== strpos( (string) ( $entry['path'] ?? '' ), '/seo-head' ) ) { + $sent_channel = (string) ( $entry['channel'] ?? '' ); + } + } + check( 'delivery read identified the channel', $expected_channel === $sent_channel, "sent '$sent_channel'" ); +} echo "\n"; if ( $failures ) { diff --git a/bin/rig/wp-config.php.tpl b/bin/rig/wp-config.php.tpl index a60db78..ad2f183 100644 --- a/bin/rig/wp-config.php.tpl +++ b/bin/rig/wp-config.php.tpl @@ -19,7 +19,7 @@ define( 'DISABLE_WP_CRON', true ); // Point the plugin at the stub. This is the plugin's own supported override for // a self-hosted or staging origin — see Citecue_Settings::pinned_api_base(). -define( 'CITECUE_API_BASE', 'http://127.0.0.1:__API_PORT__' ); +define( 'CITECUE_API_BASE', '__API_BASE__' ); define( 'AUTH_KEY', 'rig' ); define( 'SECURE_AUTH_KEY', 'rig' ); define( 'LOGGED_IN_KEY', 'rig' ); define( 'NONCE_KEY', 'rig' ); define( 'AUTH_SALT', 'rig' ); define( 'SECURE_AUTH_SALT', 'rig' ); From 462f9818c4a221e7e937736cf2dabf9243085fe7 Mon Sep 17 00:00:00 2001 From: henry-idingo <henry@idingo.io> Date: Thu, 3 Sep 2026 18:34:23 +0200 Subject: [PATCH 9/9] Hold the rig to the standard, and the standard to the rig MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI's coding-standards job failed on the rig I added: phpcs scans the whole tree, and `bin/rig/*` tripped 42 violations across ten sniffs. I did not catch it locally because I had been reading phpcs through `tail`, which shows the timing line and hides every error above it — the command was exiting 1 the whole time. Split by whether the sniff is saying something true about this code. Fixed rather than excused: the stub's missing @package tag and file-comment spacing, an undocumented helper, an undocumented parameter, and two short ternaries. The helper was also called `wp_json_encode_fallback`, which reads as a WordPress function in a file that deliberately runs outside WordPress; it is now `citecue_rig_json` and says so. Waived, alongside `/tests/*` and for the same reason plus one: the rig is not shipped — bin/ is export-ignored and the zip guard refuses an archive containing it — and it is not plugin code. Prefixing a local `$url` in a test harness with `citecue_` would be ceremony that makes the harness harder to read without making anything safer. Four categories only: global prefixes, global overrides, file naming. Also closes a gap the failure exposed: `composer lint` never covered `bin/`, so a parse error in the rig would have reached CI as a phpcs crash rather than a syntax error. It covers it now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --- .phpcs.xml.dist | 14 ++++++++++++++ bin/rig/fake-citecue.php | 18 +++++++++++++++--- bin/rig/seed.php | 4 ++-- bin/rig/theme-index.php | 1 + bin/rig/verify.php | 7 ++++++- composer.json | 2 +- 6 files changed, 39 insertions(+), 7 deletions(-) diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index 8206064..f52cc34 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -65,12 +65,25 @@ Test files are not shipped: they legitimately reassign WordPress globals, declare unprefixed test classes, and carry docblocks that hold only PHPUnit annotations (@covers, @dataProvider). + + The local rig (`bin/rig/*`, driven by `bin/local-rig.sh`) is the same + category and then some. It is not shipped — `bin/` is export-ignored and + the zip guard refuses an archive containing it — and it is not plugin + code: these are standalone scripts that install a throwaway WordPress, + seed it and assert against rendered pages. Two of them do not run inside + WordPress at all. Prefixing a local `$url` in a test harness with + `citecue_` would be ceremony that makes the harness harder to read + without making anything safer. Everything the sniffs flag there that IS + worth fixing — missing docblocks, short ternaries — is fixed rather than + excused; only these four categories are waived. --> <rule ref="WordPress.WP.GlobalVariablesOverride"> <exclude-pattern>/tests/*</exclude-pattern> + <exclude-pattern>/bin/rig/*</exclude-pattern> </rule> <rule ref="WordPress.NamingConventions.PrefixAllGlobals"> <exclude-pattern>/tests/*</exclude-pattern> + <exclude-pattern>/bin/rig/*</exclude-pattern> </rule> <rule ref="WordPress.PHP.DevelopmentFunctions"> <exclude-pattern>/tests/*</exclude-pattern> @@ -83,6 +96,7 @@ </rule> <rule ref="WordPress.Files.FileName"> <exclude-pattern>/tests/*</exclude-pattern> + <exclude-pattern>/bin/rig/*</exclude-pattern> </rule> <!-- PHPUnit calls assertion helpers in camelCase. --> <rule ref="WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid"> diff --git a/bin/rig/fake-citecue.php b/bin/rig/fake-citecue.php index f4d2329..89149bb 100644 --- a/bin/rig/fake-citecue.php +++ b/bin/rig/fake-citecue.php @@ -3,6 +3,11 @@ * A stand-in for app.citecue.com, for exercising the plugin's delivery paths * against a real HTTP round trip. Logs every request so the test can assert on * what the plugin actually sent, not just on what it did with the answer. + * + * Runs under the PHP built-in server, OUTSIDE WordPress — nothing here may + * call a WordPress function. + * + * @package Citecue */ $log = __DIR__ . '/requests.log'; @@ -11,7 +16,7 @@ $headers = function_exists( 'getallheaders' ) ? getallheaders() : array(); file_put_contents( $log, - wp_json_encode_fallback( + citecue_rig_json( array( 'path' => $_SERVER['REQUEST_URI'], 'method' => $_SERVER['REQUEST_METHOD'], @@ -22,8 +27,15 @@ FILE_APPEND ); -function wp_json_encode_fallback( $v ) { - return json_encode( $v, JSON_UNESCAPED_SLASHES ); +/** + * JSON for the request log. Named for what it is: WordPress is not loaded + * here, so wp_json_encode() does not exist. + * + * @param mixed $value Value to encode. + * @return string + */ +function citecue_rig_json( $value ) { + return (string) json_encode( $value, JSON_UNESCAPED_SLASHES ); // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode -- WordPress is not loaded in this process. } /** diff --git a/bin/rig/seed.php b/bin/rig/seed.php index 6941b2e..0b3edc5 100644 --- a/bin/rig/seed.php +++ b/bin/rig/seed.php @@ -33,8 +33,8 @@ $plugin = Citecue_Plugin::instance(); $plugin->settings->update( array( - 'api_key' => getenv( 'RIG_API_KEY' ) ?: 'ck_live_rig', - 'public_key' => getenv( 'RIG_PUBLIC_KEY' ) ?: 'pk_rig', + 'api_key' => '' !== (string) getenv( 'RIG_API_KEY' ) ? (string) getenv( 'RIG_API_KEY' ) : 'ck_live_rig', + 'public_key' => '' !== (string) getenv( 'RIG_PUBLIC_KEY' ) ? (string) getenv( 'RIG_PUBLIC_KEY' ) : 'pk_rig', 'project_domain' => '127.0.0.1', 'seo_head_enabled' => true, 'seo_head_reported' => true, diff --git a/bin/rig/theme-index.php b/bin/rig/theme-index.php index 488679b..893c3fd 100644 --- a/bin/rig/theme-index.php +++ b/bin/rig/theme-index.php @@ -5,6 +5,7 @@ * * @package Citecue */ + ?> <!DOCTYPE html> <html <?php language_attributes(); ?>> diff --git a/bin/rig/verify.php b/bin/rig/verify.php index a177bbe..a41a9e0 100644 --- a/bin/rig/verify.php +++ b/bin/rig/verify.php @@ -30,7 +30,12 @@ function check( $name, $ok, $note = '' ) { } } -/** Fetches the rendered page. */ +/** + * Fetches the rendered page. + * + * @param string $url Page to fetch. + * @return string + */ function render( $url ) { $ch = curl_init( $url ); curl_setopt_array( diff --git a/composer.json b/composer.json index 8fc94af..abd2abb 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,7 @@ } }, "scripts": { - "lint": "find citecue.php uninstall.php includes tests -name '*.php' -print0 | xargs -0 -n1 -P4 php -l > /dev/null", + "lint": "find citecue.php uninstall.php includes tests bin -name '*.php' -print0 | xargs -0 -n1 -P4 php -l > /dev/null", "phpcs": "phpcs", "phpcbf": "phpcbf", "test": [