@canonry/canonry 4.177.0 → 4.177.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,7 @@ Think PostHog, for AI search visibility. Canonry tracks what ChatGPT, Claude, Ge
18
18
  |---|---|
19
19
  | **Measure** | Track mentions and citations across Gemini, ChatGPT, Claude, Perplexity, and local models. Join them with GSC, GA4, Bing, server side traffic, Business Profile, and backlink data. |
20
20
  | **Diagnose** | Crawl the site, score Page Health, inspect evidence, compare competitors, and explain regressions. |
21
- | **Act** | Give your agent the context to make surgical on-site technical SEO changes and coordinate content, indexing, analytics, and paid media. |
21
+ | **Act** | Give your agent the context + skills to make surgical on-site technical SEO changes and coordinate content, indexing, analytics, and paid media. |
22
22
  | **Operate** | Create automation workflows, schedule checks, sync data, send webhooks, and generate client-ready reports. |
23
23
 
24
24
  ### Built-in integrations and workflows
@@ -160,6 +160,13 @@ schedule interval. Use `cnry traffic events`, `cnry traffic sources`, and
160
160
  `cnry traffic status` for every adapter. See
161
161
  `references/server-side-traffic.md` for setup and smoke tests.
162
162
 
163
+ **WordPress safety:** incremental pulls are fixed `[since, until)` windows; a
164
+ new or idle source begins at the plugin's 365-day maximum retention horizon.
165
+ Canonry rejects an endpoint that returns events outside that window. Keep
166
+ existing WordPress rollups intact: generic replace-mode backfill is unavailable
167
+ until a retention-aware repair can prove coverage and declare any unrecoverable
168
+ span.
169
+
163
170
  **Vercel gotcha:** a freshly connected Vercel source captures only going-forward traffic — `lastSyncedAt` is seeded to NOW to avoid the 30-day default window exceeding Vercel's ~14-day request-logs retention (which would otherwise throw on every first sync). Use `cnry traffic backfill <project> --source <id> --days N` for historical recovery. If an idle Vercel/Cloud Run source has been failing long enough that `lastSyncedAt` aged past retention, unstick it with `cnry traffic reset <project> --source <id> --advance-to-now`.
164
171
 
165
172
  ## Local AEO (Google Business Profile)
@@ -151,6 +151,38 @@ The plugin auto-prunes events older than the retention window (default
151
151
  90 days) once per day via WP-Cron. Operators who want a different
152
152
  window change it in `Settings → Canonry Traffic Logger`.
153
153
 
154
+ ### WordPress incremental-window safety
155
+
156
+ Each incremental WordPress sync uses one fixed half-open `[since, until)`
157
+ window. A fresh source, or an idle source without an explicit
158
+ `--since-minutes`, starts with a 365-day horizon, matching the plugin's
159
+ maximum configurable retention. A site configured for less retention simply
160
+ returns its available tail. If the window needs more than one capped drain,
161
+ Canonry persists both its lower and upper bounds with the continuation cursor
162
+ and retries that exact interval until the plugin reports it is terminal. It
163
+ then advances the normal watermark. Every returned event is validated against
164
+ the requested interval; an older or custom extension that ignores `since` or
165
+ `until` fails closed before any rollup is written. Update that extension to a
166
+ bounded-window-capable Canonry traffic logger before syncing again.
167
+
168
+ Do not run generic replace-mode backfill for a WordPress source. The plugin
169
+ prunes retained events and cannot prove that it covers every bucket a replace
170
+ transaction would delete, even when no continuation is pending. Canonry
171
+ rejects that generic path universally. Keep existing rollups intact, use a
172
+ retention-aware repair, and explicitly declare any unrecoverable portion before
173
+ reporting a historical trend.
174
+
175
+ After upgrading from the former unbounded WordPress sync, a source with an old
176
+ continuation cursor is intentionally rejected rather than guessed at. Keep its
177
+ schedule paused, record the ambiguous historical span, then use the explicit
178
+ reset below to start fresh. Reset stops future replay; it does not repair past
179
+ rollups. Generic WordPress replace-mode backfill remains unavailable until a
180
+ retention-aware repair can establish coverage.
181
+
182
+ Reconnecting with a different WordPress `baseUrl` archives the old source and
183
+ creates a fresh one. Its existing rollups and any unrecovered marker remain
184
+ with the old lineage rather than mixing with the new endpoint's traffic.
185
+
154
186
  ## Connecting a Vercel source
155
187
 
156
188
  The Vercel adapter pulls per-request logs from the Vercel API for a
@@ -869,15 +901,14 @@ cnry traffic reset <project> --source <id> --advance-to-now
869
901
 
870
902
  `--advance-to-now` is required — there is no implicit reset.
871
903
 
872
- `reset` accepts any **non-archived** source type. The `lastSyncedAt`
873
- advance is meaningful for time-windowed sources (Vercel, Cloud Run)
874
- where it determines the next sync window. Cursor-based sources
875
- (WordPress) keep their `last_cursor` intact, so the `lastSyncedAt`
876
- advance is informational the next WordPress drain still resumes
877
- from the cursor. The primary use case is the retention-trap recovery
878
- above; clearing `lastError` for a transient WordPress failure also
879
- works. Archived sources are rejected — re-connect them with
880
- `cnry traffic connect ...` instead.
904
+ `reset` accepts any **non-archived** source type. A WordPress reset clears
905
+ its `last_cursor` and pending-window marker as well as advancing
906
+ `lastSyncedAt`, so the next bounded drain begins at the reset watermark
907
+ instead of combining it with an old cursor. The primary use case is the
908
+ retention-trap recovery above; clearing `lastError` for a transient WordPress
909
+ failure also works. Reset deliberately abandons any ambiguous pending history;
910
+ it is not a historical repair. Archived sources are rejected — re-connect them
911
+ with `cnry traffic connect ...` instead.
881
912
 
882
913
  Time-window and cursor adapters use the `last_event_ids` overlap ring. Queue
883
914
  pull uses durable event receipts sized to Queue retention. Both paths make a
@@ -91,7 +91,7 @@ import {
91
91
  trafficConnectWordpressRequestSchema,
92
92
  trafficEventKindSchema,
93
93
  trafficSeriesGranularitySchema
94
- } from "./chunk-UXXXKDW4.js";
94
+ } from "./chunk-PQT3ECM3.js";
95
95
 
96
96
  // src/config.ts
97
97
  import fs from "fs";
@@ -9487,17 +9487,17 @@ var trafficConnectVercelInputSchema = z3.object({
9487
9487
  var trafficSyncInputSchema = z3.object({
9488
9488
  project: projectNameSchema,
9489
9489
  sourceId: z3.string().min(1).describe("Traffic source ID returned by canonry_traffic_connect_cloud_run or canonry_traffic_sources_list."),
9490
- sinceMinutes: z3.number().int().positive().max(7 * 24 * 60).optional().describe("Lookback window in minutes. Defaults to the source's configured window (60 min) when omitted; clamped forward to lastSyncedAt to avoid double-counting.")
9490
+ sinceMinutes: z3.number().int().positive().max(365 * 24 * 60).optional().describe("Optional lookback in minutes. Defaults are adapter-specific and clamp forward to lastSyncedAt; a new or idle WordPress source uses 365d to cover the plugin\u2019s maximum configurable retention.")
9491
9491
  });
9492
9492
  var trafficBackfillInputSchema = z3.object({
9493
9493
  project: projectNameSchema,
9494
9494
  sourceId: z3.string().min(1).describe("Traffic source ID returned by canonry_traffic_sources_list."),
9495
- days: z3.number().int().positive().max(30).optional().describe("Lookback window in days. Default 30, capped server-side at the upstream log retention ceiling (Cloud Logging _Default = 30d).")
9495
+ days: z3.number().int().positive().max(90).optional().describe("Lookback window in days. Default 30, capped by the adapter at 90d. Generic WordPress replace backfill is unavailable because retained coverage is unproven.")
9496
9496
  });
9497
9497
  var trafficResetInputSchema = z3.object({
9498
9498
  project: projectNameSchema,
9499
9499
  sourceId: z3.string().min(1).describe("Traffic source ID returned by canonry_traffic_sources_list."),
9500
- advanceToNow: z3.literal(true).describe("Must be `true`. Explicit gate against accidental resets. Advances lastSyncedAt to NOW and clears the source's error state.")
9500
+ advanceToNow: z3.literal(true).describe("Must be `true`. Explicit gate against accidental resets. Advances lastSyncedAt to NOW and clears the source's error state; WordPress also clears its continuation state and records an unrecovered span that needs retention-aware repair.")
9501
9501
  });
9502
9502
  var trafficEventsInputSchema = z3.object({
9503
9503
  project: projectNameSchema,
@@ -10650,7 +10650,7 @@ var canonryMcpTools = [
10650
10650
  defineTool({
10651
10651
  name: "canonry_traffic_connect_wordpress",
10652
10652
  title: "Connect WordPress traffic-logger source",
10653
- description: "Connect a WordPress site (running the canonry traffic-logger plugin) as a server-side traffic source. Probes the plugin endpoint with the supplied Application Password before persisting \u2014 a bad credential or unreachable host surfaces as a 502 error. Reconnecting updates the existing active WordPress source in place. The Application Password is stored in ~/.canonry/config.yaml (not the DB) and never echoed back.",
10653
+ description: "Connect a WordPress site (running the canonry traffic-logger plugin) as a server-side traffic source. Probes the plugin endpoint with the supplied Application Password before persisting; a bad credential or unreachable host surfaces as a 502 error. Reconnecting the same endpoint updates the active source in place. Changing baseUrl archives the old lineage and creates a fresh source so rollups cannot mix across endpoints. The Application Password is stored in ~/.canonry/config.yaml (not the DB) and never echoed back.",
10654
10654
  access: "write",
10655
10655
  tier: "traffic",
10656
10656
  inputSchema: trafficConnectWordpressInputSchema,
@@ -10671,8 +10671,8 @@ var canonryMcpTools = [
10671
10671
  }),
10672
10672
  defineTool({
10673
10673
  name: "canonry_traffic_sync",
10674
- title: "Sync Cloud Run traffic source",
10675
- description: "Pull the most recent Cloud Logging entries for a Cloud Run traffic source, classify them as crawler / AI-referral / unknown, and upsert hourly rollups + raw samples. Returns totals, bucket counts, and the run id. The window auto-clamps forward to lastSyncedAt to avoid double-counting on back-to-back calls.",
10674
+ title: "Sync traffic source",
10675
+ description: "Pull a Cloud Run, WordPress, Vercel, or Cloudflare Queue traffic source, classify crawler / AI-referral / unknown traffic, and upsert hourly rollups plus raw samples. Time-window pulls clamp forward to lastSyncedAt. WordPress uses a fixed bounded window, defaults a new or idle source to 365d, validates the returned events remain inside that window, and serializes syncs with a source lease.",
10676
10676
  access: "write",
10677
10677
  tier: "traffic",
10678
10678
  inputSchema: trafficSyncInputSchema,
@@ -10682,8 +10682,8 @@ var canonryMcpTools = [
10682
10682
  }),
10683
10683
  defineTool({
10684
10684
  name: "canonry_traffic_backfill",
10685
- title: "Backfill Cloud Run traffic source",
10686
- description: 'Async one-shot reclassification of historical Cloud Run logs. Pulls the last `days` of request logs (capped at the 30d Cloud Logging retention ceiling), classifies them with current rules, and replaces the hourly rollup buckets + sample slice in the window. Returns immediately with `{ runId, status: "running" }`; poll canonry_run_get for completion. lastSyncedAt only advances forward \u2014 a backfill never undoes incremental sync progress that ran ahead of it.',
10685
+ title: "Backfill traffic source",
10686
+ description: 'Async one-shot reclassification for Cloud Run or Vercel traffic. It replaces the selected window\u2019s rollups with a fresh bounded pull and current classifier output. WordPress generic replace backfill is unavailable because the endpoint cannot prove retained coverage; use a retention-aware repair that explicitly declares any unrecoverable span. Returns `{ runId, status: "running" }`; poll canonry_run_get for completion.',
10687
10687
  access: "write",
10688
10688
  tier: "traffic",
10689
10689
  inputSchema: trafficBackfillInputSchema,
@@ -10694,7 +10694,7 @@ var canonryMcpTools = [
10694
10694
  defineTool({
10695
10695
  name: "canonry_traffic_reset",
10696
10696
  title: "Advance traffic source lastSyncedAt to NOW",
10697
- description: "Operator recovery for a stuck traffic source. Advances `lastSyncedAt` to NOW, sets `status` back to `connected`, and clears `last_error`. Accepts any non-archived source: the `lastSyncedAt` advance determines the next sync window for time-windowed sources (Vercel, Cloud Run); cursor-based sources (WordPress) keep their `last_cursor` so the advance is informational. Common trigger: an idle Vercel/Cloud Run source whose `lastSyncedAt` aged past the upstream retention boundary and every sync now throws a retention error. Historical events in the gap are unrecoverable from the sync path; run canonry_traffic_backfill separately if any of them are needed. Archived sources are rejected \u2014 re-connect via the appropriate canonry_traffic_connect_* tool instead.",
10697
+ description: "Operator recovery for a stuck traffic source. Advances `lastSyncedAt` to NOW, sets `status` back to `connected`, and clears `last_error`. A WordPress reset also clears its continuation state and records an unrecovered span so its next bounded drain cannot combine an old cursor with a new window. That stops replay but is not historical repair. Generic WordPress replace-mode backfill is unavailable because retained coverage is unproven; use a retention-aware repair that explicitly declares any unrecoverable span. Archived sources are rejected; reconnect them with the appropriate canonry_traffic_connect_* tool.",
10698
10698
  access: "write",
10699
10699
  tier: "traffic",
10700
10700
  inputSchema: trafficResetInputSchema,