@canonry/canonry 4.178.2 → 4.178.4

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.
@@ -5,11 +5,11 @@ import {
5
5
  installSkills,
6
6
  loadConfigRaw,
7
7
  saveConfigPatch
8
- } from "./chunk-P6JVZJO5.js";
8
+ } from "./chunk-HNY2FNJH.js";
9
9
  import {
10
10
  SKILL_MANIFEST_FILENAME,
11
11
  SkillsClients
12
- } from "./chunk-OYDIND7Q.js";
12
+ } from "./chunk-IMEJQQZU.js";
13
13
 
14
14
  // src/skills-autosync.ts
15
15
  import fs from "fs";
@@ -92,7 +92,7 @@ import {
92
92
  trafficConnectWordpressRequestSchema,
93
93
  trafficEventKindSchema,
94
94
  trafficSeriesGranularitySchema
95
- } from "./chunk-OYDIND7Q.js";
95
+ } from "./chunk-IMEJQQZU.js";
96
96
 
97
97
  // src/config.ts
98
98
  import fs from "fs";
@@ -16409,6 +16409,7 @@ export {
16409
16409
  compileQueryClassifier,
16410
16410
  MEASUREMENT_PLAN_V2_SCHEMA_VERSION,
16411
16411
  measurementPlanV2Schema,
16412
+ canonicalMeasurementPlanV2,
16412
16413
  canonicalMeasurementPlanV2Json,
16413
16414
  measurementPlanV2ChecksumJson,
16414
16415
  MEASUREMENT_PAGE_DEFAULT_LIMIT,
@@ -16748,7 +16749,6 @@ export {
16748
16749
  siteCrawlDeadLinksResponseSchema,
16749
16750
  SITE_AUDIT_DEFAULT_PAGE_LIMIT,
16750
16751
  SITE_AUDIT_MAX_PAGE_LIMIT,
16751
- SITE_AUDIT_DEFAULT_EDGE_LIMIT,
16752
16752
  SITE_AUDIT_MAX_EDGE_LIMIT,
16753
16753
  siteAuditRunRequestSchema,
16754
16754
  normalizeSiteAuditRunRequest,
@@ -202,6 +202,7 @@ import {
202
202
  calendarMonthBounds,
203
203
  canonicalMeasurementExecutionIdentityJson,
204
204
  canonicalMeasurementPlanJson,
205
+ canonicalMeasurementPlanV2,
205
206
  canonicalMeasurementPlanV2Json,
206
207
  canonicalizeAdsActivationManifest,
207
208
  canonicalizeGoogleAdsCustomerSelection,
@@ -637,7 +638,7 @@ import {
637
638
  wordpressSchemaDeployResultDtoSchema,
638
639
  wordpressSchemaStatusResultDtoSchema,
639
640
  wordpressStatusDtoSchema
640
- } from "./chunk-OYDIND7Q.js";
641
+ } from "./chunk-IMEJQQZU.js";
641
642
 
642
643
  // src/intelligence-service.ts
643
644
  import { eq as eq62, desc as desc27, asc as asc11, and as and51, ne as ne8, or as or14, inArray as inArray21, gte as gte14, lte as lte12, isNull as isNull9, sql as sql25, exists } from "drizzle-orm";
@@ -826,6 +827,17 @@ var measurementPlanVersions = sqliteTable("measurement_plan_versions", {
826
827
  * never happened.
827
828
  */
828
829
  compiledChecksum: text("compiled_checksum"),
830
+ /**
831
+ * Continuity link written at publish time: the superseded active version this
832
+ * revision is measurement-comparable with. Set ONLY when the publish changed
833
+ * nothing about execution — the frozen execution nodes of both revisions are
834
+ * canonically identical — so a run pinned to the linked version answered
835
+ * exactly the questions this revision would ask. A label-only republish
836
+ * therefore keeps serving the previous run instead of blanking the dashboard
837
+ * until the next sweep. Null for the first revision, for every
838
+ * execution-changing publish, and on every historic row.
839
+ */
840
+ comparableToVersionId: text("comparable_to_version_id"),
829
841
  publishedBy: text("published_by"),
830
842
  sourceDraftId: text("source_draft_id"),
831
843
  createdAt: text("created_at").notNull()
@@ -7039,6 +7051,21 @@ var MIGRATION_VERSIONS = [
7039
7051
  `CREATE INDEX IF NOT EXISTS insight_notify_state_project
7040
7052
  ON insight_notify_state(project_id)`
7041
7053
  ]
7054
+ },
7055
+ {
7056
+ version: 149,
7057
+ name: "measurement-plan-version-continuity",
7058
+ // Every measurement read pins to the active plan version row id, but a
7059
+ // publish mints a new row for ANY compiled-checksum change — labels
7060
+ // included — so renaming a group blanked the dashboard until the next full
7061
+ // sweep. This column records, at publish time, the superseded version a
7062
+ // cosmetic (execution-identical) revision stays comparable with, so reads
7063
+ // can keep serving the previous run. Historic rows stay NULL on purpose:
7064
+ // their execution equality was never verified at publish time, and
7065
+ // backfilling one would claim a comparison nobody made.
7066
+ statements: [
7067
+ `ALTER TABLE measurement_plan_versions ADD COLUMN comparable_to_version_id TEXT`
7068
+ ]
7042
7069
  }
7043
7070
  ];
7044
7071
  function addRunsMeasurementPlanVersionForeignKey(tx) {
@@ -13258,10 +13285,37 @@ function responseFromReport(revision, report, run) {
13258
13285
  diagnostics: report.diagnostics
13259
13286
  };
13260
13287
  }
13261
- function latestMeasurementRun(db, projectId, versionId, statuses, window = {}) {
13288
+ var MEASUREMENT_COMPARABLE_VERSION_WALK_LIMIT = 32;
13289
+ function comparableMeasurementVersionIds(db, projectId, versionId) {
13290
+ const ids = [versionId];
13291
+ const seen = new Set(ids);
13292
+ let cursor = versionId;
13293
+ for (let step = 0; step < MEASUREMENT_COMPARABLE_VERSION_WALK_LIMIT; step++) {
13294
+ const row = db.select({ comparableToVersionId: measurementPlanVersions.comparableToVersionId }).from(measurementPlanVersions).where(and4(
13295
+ eq9(measurementPlanVersions.projectId, projectId),
13296
+ eq9(measurementPlanVersions.id, cursor)
13297
+ )).get();
13298
+ const next = row?.comparableToVersionId ?? null;
13299
+ if (next === null || seen.has(next)) break;
13300
+ ids.push(next);
13301
+ seen.add(next);
13302
+ cursor = next;
13303
+ }
13304
+ return ids;
13305
+ }
13306
+ function runVersionServesActiveVersion(db, projectId, activeVersionId, runVersionId) {
13307
+ if (runVersionId === null) return false;
13308
+ if (runVersionId === activeVersionId) return true;
13309
+ return comparableMeasurementVersionIds(db, projectId, activeVersionId).includes(runVersionId);
13310
+ }
13311
+ function latestMeasurementRun(db, projectId, versionId, statuses, window = {}, opts = {}) {
13262
13312
  const conditions = [
13263
13313
  eq9(runs.projectId, projectId),
13264
- eq9(runs.measurementPlanVersionId, versionId),
13314
+ // The revision-addressed report surface promises the revision AS-WAS and
13315
+ // must never borrow a predecessor's run through the comparable chain; the
13316
+ // active-dashboard surfaces want the chain so a label-only republish does
13317
+ // not blank them. exactVersion selects the contract.
13318
+ opts.exactVersion ? eq9(runs.measurementPlanVersionId, versionId) : inArray3(runs.measurementPlanVersionId, comparableMeasurementVersionIds(db, projectId, versionId)),
13265
13319
  eq9(runs.kind, RunKinds["answer-visibility"]),
13266
13320
  inArray3(runs.status, [...statuses]),
13267
13321
  ne2(runs.trigger, RunTriggers.probe),
@@ -13271,11 +13325,11 @@ function latestMeasurementRun(db, projectId, versionId, statuses, window = {}) {
13271
13325
  if (window.to !== void 0) conditions.push(lte2(runs.createdAt, window.to));
13272
13326
  return db.select().from(runs).where(and4(...conditions)).orderBy(desc(runs.createdAt), desc(runs.id)).get();
13273
13327
  }
13274
- function pinnedMeasurementRun(db, projectId, versionId, runId) {
13328
+ function pinnedMeasurementRun(db, projectId, versionId, runId, opts = {}) {
13275
13329
  return db.select().from(runs).where(and4(
13276
13330
  eq9(runs.id, runId),
13277
13331
  eq9(runs.projectId, projectId),
13278
- eq9(runs.measurementPlanVersionId, versionId),
13332
+ opts.exactVersion ? eq9(runs.measurementPlanVersionId, versionId) : inArray3(runs.measurementPlanVersionId, comparableMeasurementVersionIds(db, projectId, versionId)),
13279
13333
  eq9(runs.kind, RunKinds["answer-visibility"]),
13280
13334
  inArray3(runs.status, [RunStatuses.completed, RunStatuses.partial]),
13281
13335
  ne2(runs.trigger, RunTriggers.probe),
@@ -13287,7 +13341,7 @@ function measurementRunExpectedSlots(run, plan) {
13287
13341
  return parseManifest(run.measurementManifest, frozenExecutionNodes(plan), run.measurementScope === null);
13288
13342
  }
13289
13343
  function storedMeasurementPlanV2Report(db, projectId, version, plan, runId) {
13290
- const run = runId ? pinnedMeasurementRun(db, projectId, version.id, runId) : latestMeasurementRun(db, projectId, version.id, [RunStatuses.completed, RunStatuses.partial]);
13344
+ const run = runId ? pinnedMeasurementRun(db, projectId, version.id, runId, { exactVersion: true }) : latestMeasurementRun(db, projectId, version.id, [RunStatuses.completed, RunStatuses.partial], {}, { exactVersion: true });
13291
13345
  if (!run) {
13292
13346
  const empty = buildMeasurementPlanV2ReportInput(version.revision, plan, { schemaVersion: 1, expectedSlots: [] }, []);
13293
13347
  return {
@@ -13321,7 +13375,7 @@ function buildStoredMeasurementReport(db, projectId, revision, runId) {
13321
13375
  return storedMeasurementPlanV2Report(db, projectId, version, stored, runId);
13322
13376
  }
13323
13377
  const plan = stored;
13324
- const run = runId ? pinnedMeasurementRun(db, projectId, version.id, runId) : db.select().from(runs).where(and4(
13378
+ const run = runId ? pinnedMeasurementRun(db, projectId, version.id, runId, { exactVersion: true }) : db.select().from(runs).where(and4(
13325
13379
  eq9(runs.projectId, projectId),
13326
13380
  eq9(runs.measurementPlanVersionId, version.id),
13327
13381
  eq9(runs.kind, RunKinds["answer-visibility"]),
@@ -15908,6 +15962,26 @@ function diffCompiledPlans(active, candidate, activeRevision) {
15908
15962
  }
15909
15963
  };
15910
15964
  }
15965
+ function plansAreLabelOnlyVariants(active, candidate) {
15966
+ const surface = (plan) => {
15967
+ const doc = canonicalMeasurementPlanV2(plan);
15968
+ const stripped = {
15969
+ ...doc,
15970
+ // compiledChecksum is DERIVED over the full document, labels included,
15971
+ // so keeping it would smuggle the stripped labels back into the
15972
+ // comparison. Everything else in the doc is semantic and stays.
15973
+ compiledChecksum: "",
15974
+ targets: doc.targets.map((target) => ({ ...target, label: "" })),
15975
+ groups: doc.groups.map((group) => ({
15976
+ ...group,
15977
+ label: "",
15978
+ competitors: group.competitors.map((competitor) => ({ ...competitor, label: "" }))
15979
+ }))
15980
+ };
15981
+ return JSON.stringify(canonicalJsonValue(stripped));
15982
+ };
15983
+ return surface(active) === surface(candidate);
15984
+ }
15911
15985
 
15912
15986
  // ../api-routes/src/measurement-draft-actions.ts
15913
15987
  var MEASUREMENT_DRAFT_ACTIONS = [
@@ -17366,7 +17440,10 @@ async function measurementDraftRoutes(app, opts = {}) {
17366
17440
  const activeSchemaVersion = active ? active.schemaVersion === 2 ? 2 : 1 : null;
17367
17441
  const completedRun = active ? app.db.select({ id: runs.id }).from(runs).where(and10(
17368
17442
  eq15(runs.projectId, project.id),
17369
- eq15(runs.measurementPlanVersionId, active.id),
17443
+ // The comparable chain, not the bare active id: a label-only
17444
+ // republish must not flip setup back to awaiting_first_run while
17445
+ // the overview keeps serving the prior run.
17446
+ inArray5(runs.measurementPlanVersionId, comparableMeasurementVersionIds(app.db, project.id, active.id)),
17370
17447
  eq15(runs.status, RunStatuses.completed)
17371
17448
  )).get() : void 0;
17372
17449
  const state = activeSchemaVersion === 1 ? "republish_required" : draft ? "setup_in_progress" : active ? completedRun ? "operational" : "awaiting_first_run" : "simple";
@@ -17774,6 +17851,7 @@ async function measurementDraftRoutes(app, opts = {}) {
17774
17851
  createdAt: now.toISOString()
17775
17852
  }).run();
17776
17853
  }
17854
+ const comparableToVersionId = active !== null && active.schemaVersion === 2 && plansAreLabelOnlyVariants(parseV2Plan(active), compiled.plan) ? active.id : null;
17777
17855
  tx.insert(measurementPlanVersions).values({
17778
17856
  id: versionId,
17779
17857
  projectId: gate.project.id,
@@ -17782,6 +17860,7 @@ async function measurementDraftRoutes(app, opts = {}) {
17782
17860
  checksum: sha256Hex(canonicalJson3),
17783
17861
  schemaVersion: 2,
17784
17862
  compiledChecksum: compiled.plan.compiledChecksum,
17863
+ comparableToVersionId,
17785
17864
  publishedBy: serializeActor(gate.actor),
17786
17865
  sourceDraftId: row.id,
17787
17866
  createdAt: now.toISOString()
@@ -18947,7 +19026,7 @@ function selectDisplayedRun(db, projectId, active, query) {
18947
19026
  }
18948
19027
  const run = db.select().from(runs).where(and12(eq18(runs.projectId, projectId), eq18(runs.id, selectedRunId))).get();
18949
19028
  if (!run) throw notFound("Run", selectedRunId);
18950
- if (run.measurementPlanVersionId === active.version.id) return run;
19029
+ if (runVersionServesActiveVersion(db, projectId, active.version.id, run.measurementPlanVersionId)) return run;
18951
19030
  const pinned = run.measurementPlanVersionId === null ? null : db.select({ revision: measurementPlanVersions.revision }).from(measurementPlanVersions).where(eq18(measurementPlanVersions.id, run.measurementPlanVersionId)).get()?.revision ?? null;
18952
19031
  throw runRevisionMismatch(run.id, pinned, active.version.revision);
18953
19032
  }
@@ -19288,7 +19367,7 @@ function selectDisplayedRun2(db, projectId, active, runId) {
19288
19367
  }
19289
19368
  const run = db.select().from(runs).where(and13(eq19(runs.projectId, projectId), eq19(runs.id, runId))).get();
19290
19369
  if (!run) throw notFound("Run", runId);
19291
- if (run.measurementPlanVersionId === active.version.id) return run;
19370
+ if (runVersionServesActiveVersion(db, projectId, active.version.id, run.measurementPlanVersionId)) return run;
19292
19371
  const pinned = run.measurementPlanVersionId === null ? null : db.select({ revision: measurementPlanVersions.revision }).from(measurementPlanVersions).where(eq19(measurementPlanVersions.id, run.measurementPlanVersionId)).get()?.revision ?? null;
19293
19372
  throw runRevisionMismatch(run.id, pinned, active.version.revision);
19294
19373
  }
@@ -19487,7 +19566,7 @@ function selectMeasurementQuestionRun(db, projectId, active, runId) {
19487
19566
  eq20(runs.projectId, projectId)
19488
19567
  )).get();
19489
19568
  if (!run) throw notFound("Run", runId);
19490
- if (run.measurementPlanVersionId !== active.version.id) {
19569
+ if (!runVersionServesActiveVersion(db, projectId, active.version.id, run.measurementPlanVersionId)) {
19491
19570
  const pinned = run.measurementPlanVersionId === null ? null : db.select({ revision: measurementPlanVersions.revision }).from(measurementPlanVersions).where(eq20(measurementPlanVersions.id, run.measurementPlanVersionId)).get()?.revision ?? null;
19492
19571
  throw runRevisionMismatch(run.id, pinned, active.version.revision);
19493
19572
  }
@@ -20179,7 +20258,11 @@ function previousComparableRun(db, active, current) {
20179
20258
  measurementExecutionIdentity: runs.measurementExecutionIdentity
20180
20259
  }).from(runs).where(and15(
20181
20260
  eq21(runs.projectId, current.projectId),
20182
- eq21(runs.measurementPlanVersionId, active.version.id),
20261
+ // The comparable chain keeps period-over-period alive across a label-only
20262
+ // republish: a run pinned to a comparable prior revision measured exactly
20263
+ // the questions the active revision asks. The execution-identity and scope
20264
+ // checks below still gate what actually compares.
20265
+ inArray7(runs.measurementPlanVersionId, comparableMeasurementVersionIds(db, current.projectId, active.version.id)),
20183
20266
  eq21(runs.kind, RunKinds["answer-visibility"]),
20184
20267
  inArray7(runs.status, [RunStatuses.completed, RunStatuses.partial]),
20185
20268
  or3(
@@ -11,7 +11,7 @@ import {
11
11
  loadConfig,
12
12
  loadConfigRaw,
13
13
  saveConfigPatch
14
- } from "./chunk-P6JVZJO5.js";
14
+ } from "./chunk-HNY2FNJH.js";
15
15
  import {
16
16
  CC_CACHE_DIR,
17
17
  DUCKDB_SPEC,
@@ -157,7 +157,7 @@ import {
157
157
  siteCrawlSnapshots,
158
158
  toAlertView,
159
159
  usageCounters
160
- } from "./chunk-5KI5WVWS.js";
160
+ } from "./chunk-KMK6AOB5.js";
161
161
  import {
162
162
  AGENT_MEMORY_VALUE_MAX_BYTES,
163
163
  AGENT_PROVIDER_IDS,
@@ -191,7 +191,6 @@ import {
191
191
  RunKinds,
192
192
  RunStatuses,
193
193
  RunTriggers,
194
- SITE_AUDIT_DEFAULT_EDGE_LIMIT,
195
194
  SITE_AUDIT_DEFAULT_PAGE_LIMIT,
196
195
  SITE_AUDIT_MAX_EDGE_LIMIT,
197
196
  SITE_AUDIT_MAX_PAGE_LIMIT,
@@ -296,7 +295,7 @@ import {
296
295
  validationError,
297
296
  winnabilityClassLabel,
298
297
  withRetry
299
- } from "./chunk-OYDIND7Q.js";
298
+ } from "./chunk-IMEJQQZU.js";
300
299
 
301
300
  // src/telemetry.ts
302
301
  import crypto from "crypto";
@@ -10742,7 +10741,7 @@ function clampSiteAuditLimit(limit) {
10742
10741
  return Math.max(1, Math.min(SITE_AUDIT_MAX_PAGE_LIMIT, Math.floor(limit)));
10743
10742
  }
10744
10743
  function clampSiteAuditEdgeLimit(limit) {
10745
- if (limit == null || !Number.isFinite(limit)) return SITE_AUDIT_DEFAULT_EDGE_LIMIT;
10744
+ if (limit == null || !Number.isFinite(limit)) return void 0;
10746
10745
  return Math.max(1, Math.min(SITE_AUDIT_MAX_EDGE_LIMIT, Math.floor(limit)));
10747
10746
  }
10748
10747
  function toPageFactor(factor) {
@@ -10873,43 +10872,6 @@ function deadLinkCheckedCount(edges, pages) {
10873
10872
  }
10874
10873
  return new Set([...edges].filter((edge) => edge.type === "anchor" && edge.classification === "internal" && attemptedUrls.has(edge.to)).map((edge) => edge.to)).size;
10875
10874
  }
10876
- var RATE_LIMITED_STATUS = 429;
10877
- function partitionDeadLinks(deadLinks) {
10878
- const reported = deadLinks.findings;
10879
- const engineUnverified = deadLinks.unverified ?? [];
10880
- const answeredWithError = (finding) => typeof finding.statusCode === "number" && finding.statusCode >= 400;
10881
- const dead = reported.filter((finding) => answeredWithError(finding) && finding.statusCode !== RATE_LIMITED_STATUS);
10882
- const unverified = [
10883
- ...reported.filter((finding) => !answeredWithError(finding) || finding.statusCode === RATE_LIMITED_STATUS),
10884
- ...engineUnverified
10885
- ];
10886
- return { dead, unverified };
10887
- }
10888
- function reportedTermination(summary) {
10889
- const limits = summary.limits;
10890
- if (limits) {
10891
- if (limits.maxBytes && (summary.bytesRead ?? 0) >= limits.maxBytes) return "max-bytes";
10892
- if (limits.maxDurationMs && (summary.elapsedMs ?? 0) >= limits.maxDurationMs) return "max-duration";
10893
- if (limits.maxFetches && (summary.fetchesStarted ?? 0) >= limits.maxFetches) return "max-fetches";
10894
- }
10895
- return summary.terminationReason ?? "complete";
10896
- }
10897
- function crawlBudgetsFor(maxPages) {
10898
- return {
10899
- // 2x the ~745 KB/page measured on a media-heavy production site, so the
10900
- // byte cap is headroom rather than the thing that stops the crawl.
10901
- maxBytes: maxPages * 15e5,
10902
- // ~5 pages/s at the engine's default concurrency of 5, doubled for slow
10903
- // origins, and never below the engine's own 2-minute floor.
10904
- maxDurationMs: Math.max(12e4, Math.ceil(maxPages / 5 * 1e3 * 2)),
10905
- // Redirects and dead-link probes cost fetches without producing pages.
10906
- maxFetches: Math.ceil(maxPages * 1.5),
10907
- // Raised off the default of 10 because it is an ADMISSION limit: a sitemap
10908
- // with many query-parameter variants trips it during seeding and latches a
10909
- // soft reason that then masks whatever really stops the crawl.
10910
- maxQueryVariants: Math.max(50, Math.ceil(maxPages / 10))
10911
- };
10912
- }
10913
10875
  async function executeSiteAudit(db, runId, projectId, opts = {}) {
10914
10876
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
10915
10877
  const claim = db.update(runs).set({ status: "running", startedAt }).where(and12(eq15(runs.id, runId), eq15(runs.projectId, projectId), eq15(runs.status, "queued"))).run();
@@ -11210,7 +11172,6 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
11210
11172
  maxEdges,
11211
11173
  maxDepth: opts.maxDepth ?? null
11212
11174
  });
11213
- const budgets = crawlBudgetsFor(maxPages);
11214
11175
  const report = await runSiteCrawl(crawlRootUrl, {
11215
11176
  mode: "summary",
11216
11177
  sitemapUrl: opts.sitemapUrl,
@@ -11218,7 +11179,6 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
11218
11179
  maxEdges,
11219
11180
  maxDepth: opts.maxDepth,
11220
11181
  checkDeadLinks: opts.checkDeadLinks ?? false,
11221
- ...budgets,
11222
11182
  signal: opts.signal,
11223
11183
  onEvent: persistEvent
11224
11184
  });
@@ -11253,7 +11213,8 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
11253
11213
  const errorCount = observedErrorCount(observedPages.values());
11254
11214
  const terminalStatus = crawlSummary.complete ? "completed" : "partial";
11255
11215
  const deadLinksChecked = opts.checkDeadLinks ? deadLinkCheckedCount(observedEdges.values(), observedPages.values()) : 0;
11256
- const { dead: deadLinkFindings, unverified: unverifiedLinks } = partitionDeadLinks(report.deadLinks);
11216
+ const deadLinkFindings = report.deadLinks.findings;
11217
+ const unverifiedLinks = report.deadLinks.unverified;
11257
11218
  const deadLinksFound = deadLinkFindings.length;
11258
11219
  const deadLinksUnverified = new Set(unverifiedLinks.map((finding) => finding.to)).size;
11259
11220
  const legacyIssues = factors.map((factor) => toLegacyIssue(factor, crawlSummary.auditRollup.auditedPages)).filter((issue) => issue !== null);
@@ -11345,7 +11306,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
11345
11306
  maxDepth: opts.maxDepth ?? null,
11346
11307
  checkDeadLinks: opts.checkDeadLinks ?? false,
11347
11308
  complete: crawlSummary.complete,
11348
- termination: reportedTermination(crawlSummary),
11309
+ termination: crawlSummary.terminationReason ?? "complete",
11349
11310
  detailsAvailable: true,
11350
11311
  pagesDiscovered: crawlSummary.pagesDiscovered,
11351
11312
  pagesFetched: crawlSummary.pagesFetched,
@@ -11896,7 +11857,7 @@ function readStoredGroundingSources(rawResponse) {
11896
11857
  return result;
11897
11858
  }
11898
11859
  async function backfillInsightsCommand(project, opts) {
11899
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-KYIQZBEO.js");
11860
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-XPAKDZJI.js");
11900
11861
  const config = loadConfig();
11901
11862
  const db = createClient(config.database);
11902
11863
  migrate(db);
package/dist/cli.js CHANGED
@@ -26,11 +26,11 @@ import {
26
26
  showFirstRunNotice,
27
27
  trackCliCommandFinished,
28
28
  trackEvent
29
- } from "./chunk-LMI72WZP.js";
29
+ } from "./chunk-WYCHM3EP.js";
30
30
  import {
31
31
  autoSyncSkills,
32
32
  formatAutoSyncNotice
33
- } from "./chunk-EAUE2RRY.js";
33
+ } from "./chunk-EBRGLDTB.js";
34
34
  import {
35
35
  CliError,
36
36
  EXIT_SYSTEM_ERROR,
@@ -55,7 +55,7 @@ import {
55
55
  saveConfigPatch,
56
56
  systemError,
57
57
  usageError
58
- } from "./chunk-P6JVZJO5.js";
58
+ } from "./chunk-HNY2FNJH.js";
59
59
  import {
60
60
  CLOUDFLARE_WORKER_BINDINGS,
61
61
  CLOUDFLARE_WORKER_GENERATED_MARKER,
@@ -69,7 +69,7 @@ import {
69
69
  projects,
70
70
  queries,
71
71
  renderReportHtml
72
- } from "./chunk-5KI5WVWS.js";
72
+ } from "./chunk-KMK6AOB5.js";
73
73
  import {
74
74
  AdsDeliverySnapshotStatuses,
75
75
  AdsHistoricalCampaignRollupStatuses,
@@ -138,7 +138,7 @@ import {
138
138
  providerQuotaPolicySchema,
139
139
  resolveProviderInput,
140
140
  winnabilityClassSchema
141
- } from "./chunk-OYDIND7Q.js";
141
+ } from "./chunk-IMEJQQZU.js";
142
142
 
143
143
  // src/cli.ts
144
144
  import { pathToFileURL } from "url";
package/dist/index.js CHANGED
@@ -3,12 +3,12 @@ import {
3
3
  createGoogleMarketingCredentialStore,
4
4
  createGoogleMarketingRuntime,
5
5
  createServer
6
- } from "./chunk-LMI72WZP.js";
6
+ } from "./chunk-WYCHM3EP.js";
7
7
  import {
8
8
  loadConfig
9
- } from "./chunk-P6JVZJO5.js";
10
- import "./chunk-5KI5WVWS.js";
11
- import "./chunk-OYDIND7Q.js";
9
+ } from "./chunk-HNY2FNJH.js";
10
+ import "./chunk-KMK6AOB5.js";
11
+ import "./chunk-IMEJQQZU.js";
12
12
  export {
13
13
  GoogleMarketingRuntimeError,
14
14
  createGoogleMarketingCredentialStore,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-5KI5WVWS.js";
4
- import "./chunk-OYDIND7Q.js";
3
+ } from "./chunk-KMK6AOB5.js";
4
+ import "./chunk-IMEJQQZU.js";
5
5
  export {
6
6
  IntelligenceService
7
7
  };
package/dist/mcp.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  autoSyncSkills
3
- } from "./chunk-EAUE2RRY.js";
3
+ } from "./chunk-EBRGLDTB.js";
4
4
  import {
5
5
  createApiClient,
6
6
  createCanonryMcpServer
7
- } from "./chunk-P6JVZJO5.js";
7
+ } from "./chunk-HNY2FNJH.js";
8
8
  import {
9
9
  isReadOnlyKey
10
- } from "./chunk-OYDIND7Q.js";
10
+ } from "./chunk-IMEJQQZU.js";
11
11
 
12
12
  // src/mcp/cli.ts
13
13
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonry/canonry",
3
- "version": "4.178.2",
3
+ "version": "4.178.4",
4
4
  "type": "module",
5
5
  "description": "Self-hosted AI visibility (AEO) platform: track how ChatGPT, Claude, Gemini, and Perplexity cite your domain, join it with Search Console, GA4, server-side traffic, and paid media, and fix what you find through agent tools (CLI, REST, MCP). Local SQLite.",
6
6
  "license": "FSL-1.1-ALv2",
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@anthropic-ai/sdk": "^0.91.1",
44
- "@canonry/aeo-audit": "4.7.0",
44
+ "@canonry/aeo-audit": "7.1.0",
45
45
  "@fastify/rate-limit": "^11.2.0",
46
46
  "@fastify/static": "^9.1.1",
47
47
  "@google/genai": "^1.46.0",
@@ -71,30 +71,30 @@
71
71
  "tsup": "^8.5.1",
72
72
  "tsx": "^4.19.0",
73
73
  "@ainyc/canonry-api-client": "0.0.0",
74
- "@ainyc/canonry-api-routes": "0.0.0",
75
74
  "@ainyc/canonry-config": "0.0.0",
76
- "@ainyc/canonry-db": "0.0.0",
77
75
  "@ainyc/canonry-contracts": "0.0.0",
78
- "@ainyc/canonry-integration-cloud-run": "0.0.0",
76
+ "@ainyc/canonry-api-routes": "0.0.0",
77
+ "@ainyc/canonry-db": "0.0.0",
79
78
  "@ainyc/canonry-integration-bing": "0.0.0",
79
+ "@ainyc/canonry-integration-cloud-run": "0.0.0",
80
80
  "@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
81
81
  "@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
82
82
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
83
- "@ainyc/canonry-integration-google": "0.0.0",
84
83
  "@ainyc/canonry-integration-google-ads": "0.0.0",
85
- "@ainyc/canonry-integration-google-places": "0.0.0",
86
84
  "@ainyc/canonry-integration-google-business-profile": "0.0.0",
87
- "@ainyc/canonry-integration-openai-ads": "0.0.0",
88
85
  "@ainyc/canonry-integration-google-tag-manager": "0.0.0",
86
+ "@ainyc/canonry-integration-google-places": "0.0.0",
87
+ "@ainyc/canonry-integration-openai-ads": "0.0.0",
88
+ "@ainyc/canonry-integration-google": "0.0.0",
89
89
  "@ainyc/canonry-integration-traffic": "0.0.0",
90
90
  "@ainyc/canonry-integration-wordpress": "0.0.0",
91
+ "@ainyc/canonry-provider-cdp": "0.0.0",
91
92
  "@ainyc/canonry-provider-claude": "0.0.0",
92
- "@ainyc/canonry-provider-gemini": "0.0.0",
93
93
  "@ainyc/canonry-intelligence": "0.0.0",
94
- "@ainyc/canonry-provider-cdp": "0.0.0",
94
+ "@ainyc/canonry-provider-gemini": "0.0.0",
95
+ "@ainyc/canonry-provider-openai": "0.0.0",
95
96
  "@ainyc/canonry-provider-local": "0.0.0",
96
- "@ainyc/canonry-provider-perplexity": "0.0.0",
97
- "@ainyc/canonry-provider-openai": "0.0.0"
97
+ "@ainyc/canonry-provider-perplexity": "0.0.0"
98
98
  },
99
99
  "scripts": {
100
100
  "build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",