@ainyc/canonry 4.180.7 → 4.180.8

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.
Files changed (34) hide show
  1. package/README.md +4 -0
  2. package/assets/agent-workspace/skills/canonry/SKILL.md +13 -10
  3. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +7 -6
  4. package/assets/assets/{AuditHistoryPanel-B1lmI7wm.js → AuditHistoryPanel-qiuqNGvG.js} +1 -1
  5. package/assets/assets/{BacklinksPage-Bw61uQu8.js → BacklinksPage-D-g5f0Xv.js} +1 -1
  6. package/assets/assets/{HistoryPage-D8z9-NkT.js → HistoryPage-q8_pCuOs.js} +1 -1
  7. package/assets/assets/{MeasurementPropertyPage-CXX9GFj8.js → MeasurementPropertyPage-C_wMjUZe.js} +1 -1
  8. package/assets/assets/ProjectPage-33THfDtC.js +9 -0
  9. package/assets/assets/{RunRow-DXHy16T3.js → RunRow-D5hatdlY.js} +1 -1
  10. package/assets/assets/{RunsPage-D2aE620i.js → RunsPage-Bj2snMM3.js} +1 -1
  11. package/assets/assets/SettingsPage-Cf5YgyJv.js +1 -0
  12. package/assets/assets/SiteHealthSection-D8RLvMAE.js +4 -0
  13. package/assets/assets/{TrafficPage-DFHWgvkU.js → TrafficPage-CotJ14le.js} +1 -1
  14. package/assets/assets/{TrafficSourceDetailPage-DxjyRL8Y.js → TrafficSourceDetailPage-BcxYX-uq.js} +1 -1
  15. package/assets/assets/{extract-error-message-BdXUZQW9.js → extract-error-message-Bxt42scd.js} +1 -1
  16. package/assets/assets/index-BlILyZJ5.js +86 -0
  17. package/assets/assets/index-DXCQf5s1.css +1 -0
  18. package/assets/assets/{react-sigma_core.esm.min-DAcOZae7.js → react-sigma_core.esm.min-DxZINEmi.js} +1 -1
  19. package/assets/index.html +2 -2
  20. package/dist/{chunk-XYTSKRO3.js → chunk-G7M2KM7Q.js} +2 -2
  21. package/dist/{chunk-VZO6CLHY.js → chunk-GFVZOVG3.js} +105 -64
  22. package/dist/{chunk-RXXZ5BU5.js → chunk-GN2G7VFK.js} +24 -1
  23. package/dist/{chunk-CWXPBQR5.js → chunk-IM4HFNGF.js} +7 -5
  24. package/dist/{chunk-WJFBNCVD.js → chunk-ZJ2VGTYU.js} +197 -95
  25. package/dist/cli.js +187 -96
  26. package/dist/index.js +4 -4
  27. package/dist/{intelligence-service-MEPYXRBS.js → intelligence-service-IURLYRB3.js} +2 -2
  28. package/dist/mcp.js +3 -3
  29. package/package.json +10 -10
  30. package/assets/assets/ProjectPage-BaN_rvoV.js +0 -9
  31. package/assets/assets/SettingsPage-BiiFqarS.js +0 -1
  32. package/assets/assets/SiteHealthSection-DCH2Ejtc.js +0 -4
  33. package/assets/assets/index-BuEq5Rhg.css +0 -1
  34. package/assets/assets/index-CSictO9U.js +0 -83
@@ -5,11 +5,11 @@ import {
5
5
  installSkills,
6
6
  loadConfigRaw,
7
7
  saveConfigPatch
8
- } from "./chunk-CWXPBQR5.js";
8
+ } from "./chunk-IM4HFNGF.js";
9
9
  import {
10
10
  SKILL_MANIFEST_FILENAME,
11
11
  SkillsClients
12
- } from "./chunk-RXXZ5BU5.js";
12
+ } from "./chunk-GN2G7VFK.js";
13
13
 
14
14
  // src/skills-autosync.ts
15
15
  import fs from "fs";
@@ -11,7 +11,7 @@ import {
11
11
  loadConfig,
12
12
  loadConfigRaw,
13
13
  saveConfigPatch
14
- } from "./chunk-CWXPBQR5.js";
14
+ } from "./chunk-IM4HFNGF.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-WJFBNCVD.js";
160
+ } from "./chunk-ZJ2VGTYU.js";
161
161
  import {
162
162
  AGENT_MEMORY_VALUE_MAX_BYTES,
163
163
  AGENT_PROVIDER_IDS,
@@ -266,6 +266,7 @@ import {
266
266
  isSelfLink,
267
267
  isVertexGroundingRedirect,
268
268
  mapWithConcurrency,
269
+ nextScheduleUpdatedAt,
269
270
  normalizeIdTokens,
270
271
  normalizeMeasurementExecutionQueryText,
271
272
  normalizeServedModel,
@@ -295,7 +296,7 @@ import {
295
296
  validationError,
296
297
  winnabilityClassLabel,
297
298
  withRetry
298
- } from "./chunk-RXXZ5BU5.js";
299
+ } from "./chunk-GN2G7VFK.js";
299
300
 
300
301
  // src/telemetry.ts
301
302
  import crypto from "crypto";
@@ -11866,7 +11867,7 @@ function readStoredGroundingSources(rawResponse) {
11866
11867
  return result;
11867
11868
  }
11868
11869
  async function backfillInsightsCommand(project, opts) {
11869
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-MEPYXRBS.js");
11870
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-IURLYRB3.js");
11870
11871
  const config = loadConfig();
11871
11872
  const db = createClient(config.database);
11872
11873
  migrate(db);
@@ -12447,6 +12448,21 @@ import cron from "node-cron";
12447
12448
  import { and as and14, eq as eq17, inArray as inArray6, notExists, sql as sql8 } from "drizzle-orm";
12448
12449
  var log14 = createLogger("Scheduler");
12449
12450
  var DEFAULT_HEALTH_CRON = "0 */6 * * *";
12451
+ function ensureDefaultHealthSchedule(db, projectId, now = (/* @__PURE__ */ new Date()).toISOString()) {
12452
+ const result = db.insert(schedules).values({
12453
+ id: crypto20.randomUUID(),
12454
+ projectId,
12455
+ kind: SchedulableRunKinds.doctor,
12456
+ cronExpr: DEFAULT_HEALTH_CRON,
12457
+ timezone: "UTC",
12458
+ enabled: true,
12459
+ providers: [],
12460
+ nextRunAt: nextRunFromCron(DEFAULT_HEALTH_CRON, "UTC"),
12461
+ createdAt: now,
12462
+ updatedAt: now
12463
+ }).onConflictDoNothing().run();
12464
+ return result.changes === 1;
12465
+ }
12450
12466
  function taskKey(projectId, kind) {
12451
12467
  return `${projectId}::${kind}`;
12452
12468
  }
@@ -12483,21 +12499,13 @@ var Scheduler = class {
12483
12499
  )).all();
12484
12500
  if (projectsWithoutHealth.length === 0) return;
12485
12501
  const now = (/* @__PURE__ */ new Date()).toISOString();
12502
+ let seeded = 0;
12486
12503
  for (const project of projectsWithoutHealth) {
12487
- this.db.insert(schedules).values({
12488
- id: crypto20.randomUUID(),
12489
- projectId: project.id,
12490
- kind: SchedulableRunKinds.doctor,
12491
- cronExpr: DEFAULT_HEALTH_CRON,
12492
- timezone: "UTC",
12493
- enabled: true,
12494
- providers: [],
12495
- nextRunAt: nextRunFromCron(DEFAULT_HEALTH_CRON, "UTC"),
12496
- createdAt: now,
12497
- updatedAt: now
12498
- }).run();
12504
+ if (ensureDefaultHealthSchedule(this.db, project.id, now)) seeded += 1;
12505
+ }
12506
+ if (seeded > 0) {
12507
+ log14.info("health-schedule.seeded", { projectCount: seeded, cron: DEFAULT_HEALTH_CRON });
12499
12508
  }
12500
- log14.info("health-schedule.seeded", { projectCount: projectsWithoutHealth.length, cron: DEFAULT_HEALTH_CRON });
12501
12509
  }
12502
12510
  /**
12503
12511
  * Record each project's current query set as basket revision 1 if it has
@@ -12584,6 +12592,17 @@ var Scheduler = class {
12584
12592
  void task.destroy();
12585
12593
  log14.info(`task.${verb.toLowerCase()}`, { key });
12586
12594
  }
12595
+ /** Runtime timing writes must not restore a version already issued to a settings caller. */
12596
+ updateScheduleTiming(scheduleId, timing) {
12597
+ this.db.transaction((tx) => {
12598
+ const current = tx.select({ updatedAt: schedules.updatedAt }).from(schedules).where(eq17(schedules.id, scheduleId)).get();
12599
+ if (!current) return;
12600
+ tx.update(schedules).set({
12601
+ ...timing,
12602
+ updatedAt: nextScheduleUpdatedAt(current.updatedAt)
12603
+ }).where(eq17(schedules.id, scheduleId)).run();
12604
+ });
12605
+ }
12587
12606
  registerCronTask(schedule) {
12588
12607
  const { id: scheduleId, projectId, cronExpr, timezone } = schedule;
12589
12608
  const kind = schedule.kind;
@@ -12597,10 +12616,9 @@ var Scheduler = class {
12597
12616
  timezone
12598
12617
  });
12599
12618
  this.tasks.set(taskKey(projectId, kind), task);
12600
- this.db.update(schedules).set({
12601
- nextRunAt: nextRunFromCron(cronExpr, timezone),
12602
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
12603
- }).where(eq17(schedules.id, scheduleId)).run();
12619
+ this.updateScheduleTiming(scheduleId, {
12620
+ nextRunAt: nextRunFromCron(cronExpr, timezone)
12621
+ });
12604
12622
  const label = schedule.preset ?? cronExpr;
12605
12623
  log14.info("cron.registered", { projectId, kind, schedule: label, timezone });
12606
12624
  }
@@ -12630,11 +12648,10 @@ var Scheduler = class {
12630
12648
  log14.warn("traffic-sync.no-callback", { scheduleId, projectId, msg: "host did not register onTrafficSyncRequested" });
12631
12649
  return;
12632
12650
  }
12633
- this.db.update(schedules).set({
12651
+ this.updateScheduleTiming(currentSchedule.id, {
12634
12652
  lastRunAt: now,
12635
- nextRunAt,
12636
- updatedAt: now
12637
- }).where(eq17(schedules.id, currentSchedule.id)).run();
12653
+ nextRunAt
12654
+ });
12638
12655
  log14.info("traffic-sync.triggered", { projectName: project.name, sourceId });
12639
12656
  this.callbacks.onTrafficSyncRequested(project.name, sourceId);
12640
12657
  return;
@@ -12653,11 +12670,10 @@ var Scheduler = class {
12653
12670
  trigger: RunTriggers.scheduled,
12654
12671
  createdAt: now
12655
12672
  }).run();
12656
- this.db.update(schedules).set({
12673
+ this.updateScheduleTiming(currentSchedule.id, {
12657
12674
  lastRunAt: now,
12658
- nextRunAt,
12659
- updatedAt: now
12660
- }).where(eq17(schedules.id, currentSchedule.id)).run();
12675
+ nextRunAt
12676
+ });
12661
12677
  log14.info("gbp-sync.triggered", { runId: runId2, projectName: project.name });
12662
12678
  this.callbacks.onGbpSyncRequested(runId2, projectId);
12663
12679
  return;
@@ -12674,7 +12690,7 @@ var Scheduler = class {
12674
12690
  )).get();
12675
12691
  if (activeAdsRun) {
12676
12692
  log14.info("ads-sync.skipped-active", { projectName: project.name, activeRunId: activeAdsRun.id });
12677
- this.db.update(schedules).set({ nextRunAt, updatedAt: now }).where(eq17(schedules.id, currentSchedule.id)).run();
12693
+ this.updateScheduleTiming(currentSchedule.id, { nextRunAt });
12678
12694
  return;
12679
12695
  }
12680
12696
  const runId2 = crypto20.randomUUID();
@@ -12686,11 +12702,10 @@ var Scheduler = class {
12686
12702
  trigger: RunTriggers.scheduled,
12687
12703
  createdAt: now
12688
12704
  }).run();
12689
- this.db.update(schedules).set({
12705
+ this.updateScheduleTiming(currentSchedule.id, {
12690
12706
  lastRunAt: now,
12691
- nextRunAt,
12692
- updatedAt: now
12693
- }).where(eq17(schedules.id, currentSchedule.id)).run();
12707
+ nextRunAt
12708
+ });
12694
12709
  log14.info("ads-sync.triggered", { runId: runId2, projectName: project.name });
12695
12710
  this.callbacks.onAdsSyncRequested(runId2, projectId);
12696
12711
  return;
@@ -12700,11 +12715,10 @@ var Scheduler = class {
12700
12715
  log14.warn("data-refresh.no-callback", { scheduleId, projectId, msg: "host did not register onDataRefreshRequested" });
12701
12716
  return;
12702
12717
  }
12703
- this.db.update(schedules).set({
12718
+ this.updateScheduleTiming(currentSchedule.id, {
12704
12719
  lastRunAt: now,
12705
- nextRunAt,
12706
- updatedAt: now
12707
- }).where(eq17(schedules.id, currentSchedule.id)).run();
12720
+ nextRunAt
12721
+ });
12708
12722
  log14.info("data-refresh.triggered", { projectName: project.name });
12709
12723
  this.callbacks.onDataRefreshRequested(project.name);
12710
12724
  return;
@@ -12714,11 +12728,10 @@ var Scheduler = class {
12714
12728
  log14.warn("doctor.no-callback", { scheduleId, projectId, msg: "host did not register onDoctorRequested" });
12715
12729
  return;
12716
12730
  }
12717
- this.db.update(schedules).set({
12731
+ this.updateScheduleTiming(currentSchedule.id, {
12718
12732
  lastRunAt: now,
12719
- nextRunAt,
12720
- updatedAt: now
12721
- }).where(eq17(schedules.id, currentSchedule.id)).run();
12733
+ nextRunAt
12734
+ });
12722
12735
  log14.info("doctor.triggered", { projectName: project.name });
12723
12736
  this.callbacks.onDoctorRequested(project.name);
12724
12737
  return;
@@ -12728,11 +12741,10 @@ var Scheduler = class {
12728
12741
  log14.warn("backlinks-sync.no-callback", { scheduleId, projectId, msg: "host did not register onBacklinksSyncRequested" });
12729
12742
  return;
12730
12743
  }
12731
- this.db.update(schedules).set({
12744
+ this.updateScheduleTiming(currentSchedule.id, {
12732
12745
  lastRunAt: now,
12733
- nextRunAt,
12734
- updatedAt: now
12735
- }).where(eq17(schedules.id, currentSchedule.id)).run();
12746
+ nextRunAt
12747
+ });
12736
12748
  log14.info("backlinks-sync.triggered", { projectName: project.name });
12737
12749
  this.callbacks.onBacklinksSyncRequested(project.name);
12738
12750
  return;
@@ -12749,7 +12761,7 @@ var Scheduler = class {
12749
12761
  )).get();
12750
12762
  if (active) {
12751
12763
  log14.info("site-audit.skipped-active", { projectName: project.name, activeRunId: active.id });
12752
- this.db.update(schedules).set({ nextRunAt, updatedAt: now }).where(eq17(schedules.id, currentSchedule.id)).run();
12764
+ this.updateScheduleTiming(currentSchedule.id, { nextRunAt });
12753
12765
  return;
12754
12766
  }
12755
12767
  const runId2 = crypto20.randomUUID();
@@ -12771,11 +12783,10 @@ var Scheduler = class {
12771
12783
  createdAt: now
12772
12784
  }).run();
12773
12785
  });
12774
- this.db.update(schedules).set({
12786
+ this.updateScheduleTiming(currentSchedule.id, {
12775
12787
  lastRunAt: now,
12776
- nextRunAt,
12777
- updatedAt: now
12778
- }).where(eq17(schedules.id, currentSchedule.id)).run();
12788
+ nextRunAt
12789
+ });
12779
12790
  log14.info("site-audit.triggered", { runId: runId2, projectName: project.name });
12780
12791
  this.callbacks.onSiteAuditRequested(runId2, projectId);
12781
12792
  return;
@@ -12805,18 +12816,16 @@ var Scheduler = class {
12805
12816
  });
12806
12817
  if (queueResult.conflict) {
12807
12818
  log14.info("run.skipped-active", { projectName: project.name, activeRunId: queueResult.activeRunId });
12808
- this.db.update(schedules).set({
12809
- nextRunAt,
12810
- updatedAt: now
12811
- }).where(eq17(schedules.id, currentSchedule.id)).run();
12819
+ this.updateScheduleTiming(currentSchedule.id, {
12820
+ nextRunAt
12821
+ });
12812
12822
  return;
12813
12823
  }
12814
12824
  const runId = queueResult.runId;
12815
- this.db.update(schedules).set({
12825
+ this.updateScheduleTiming(currentSchedule.id, {
12816
12826
  lastRunAt: now,
12817
- nextRunAt,
12818
- updatedAt: now
12819
- }).where(eq17(schedules.id, currentSchedule.id)).run();
12827
+ nextRunAt
12828
+ });
12820
12829
  log14.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
12821
12830
  this.callbacks.onRunCreated(runId, projectId, providers, resolvedLocation);
12822
12831
  } catch (err) {
@@ -16775,6 +16784,11 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
16775
16784
  var _require3 = createRequire4(import.meta.url);
16776
16785
  var { version: PKG_VERSION2 } = _require3("../package.json");
16777
16786
  var log21 = createLogger("Server");
16787
+ var runtimeStartupByServer = /* @__PURE__ */ new WeakMap();
16788
+ function waitForServerRuntimeStartup(app) {
16789
+ const startup = runtimeStartupByServer.get(app);
16790
+ return startup ?? Promise.reject(new Error("Server runtime startup was not registered"));
16791
+ }
16778
16792
  var DEFAULT_QUOTA = {
16779
16793
  maxConcurrency: 2,
16780
16794
  maxRequestsPerMinute: 10,
@@ -18114,7 +18128,7 @@ async function createServer(opts) {
18114
18128
  return reply.status(401).send({
18115
18129
  error: {
18116
18130
  code: "AUTH_INVALID",
18117
- message: "Server API key not found \u2014 re-run canonry init"
18131
+ message: "Server API key not found \u2014 run canonry bootstrap"
18118
18132
  }
18119
18133
  });
18120
18134
  }
@@ -18679,6 +18693,11 @@ async function createServer(opts) {
18679
18693
  if (action === "upsert") scheduler.upsert(projectId, kind);
18680
18694
  if (action === "delete") scheduler.remove(projectId, kind);
18681
18695
  },
18696
+ onProjectCreated: (projectId) => {
18697
+ if (ensureDefaultHealthSchedule(opts.db, projectId)) {
18698
+ scheduler.upsert(projectId, SchedulableRunKinds.doctor);
18699
+ }
18700
+ },
18682
18701
  onProjectDeleting: prepareGoogleMarketingCredentialDelete,
18683
18702
  onProjectDeleted: (projectId) => {
18684
18703
  scheduler.removeAllForProject(projectId);
@@ -18948,10 +18967,30 @@ async function createServer(opts) {
18948
18967
  app.get(`${basePath}health`, healthHandler);
18949
18968
  }
18950
18969
  checkLatestVersionForServer();
18951
- scheduler.start();
18952
- for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq26(researchRuns.status, ResearchRunStatuses.queued)).all()) {
18953
- dispatchResearchRun(run.id, run.projectId);
18954
- }
18970
+ let resolveRuntimeStartup;
18971
+ let rejectRuntimeStartup;
18972
+ const runtimeStartup = new Promise((resolve, reject) => {
18973
+ resolveRuntimeStartup = resolve;
18974
+ rejectRuntimeStartup = reject;
18975
+ });
18976
+ void runtimeStartup.catch(() => {
18977
+ });
18978
+ runtimeStartupByServer.set(app, runtimeStartup);
18979
+ let runtimeStartupSettled = false;
18980
+ app.addHook("onListen", () => {
18981
+ if (runtimeStartupSettled) return;
18982
+ try {
18983
+ scheduler.start();
18984
+ for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq26(researchRuns.status, ResearchRunStatuses.queued)).all()) {
18985
+ dispatchResearchRun(run.id, run.projectId);
18986
+ }
18987
+ runtimeStartupSettled = true;
18988
+ resolveRuntimeStartup();
18989
+ } catch (error) {
18990
+ runtimeStartupSettled = true;
18991
+ rejectRuntimeStartup(error);
18992
+ }
18993
+ });
18955
18994
  app.addHook("onClose", async () => {
18956
18995
  scheduler.stop();
18957
18996
  });
@@ -19043,5 +19082,7 @@ export {
19043
19082
  GoogleMarketingRuntimeError,
19044
19083
  createGoogleMarketingCredentialStore,
19045
19084
  createGoogleMarketingRuntime,
19085
+ waitForServerRuntimeStartup,
19086
+ isLoopbackBindHost,
19046
19087
  createServer
19047
19088
  };
@@ -78,6 +78,14 @@ function runInProgress(projectName) {
78
78
  function operationInProgress(message, details) {
79
79
  return new AppError("OPERATION_IN_PROGRESS", message, 409, details);
80
80
  }
81
+ function scheduleVersionConflict(expectedUpdatedAt, actualUpdatedAt) {
82
+ return new AppError(
83
+ "SCHEDULE_VERSION_CONFLICT",
84
+ "The schedule changed since it was loaded. Reload it before saving.",
85
+ 409,
86
+ { expectedUpdatedAt, actualUpdatedAt }
87
+ );
88
+ }
81
89
  function measurementPlanRevisionConflict(expectedActiveRevision, actualActiveRevision) {
82
90
  return new AppError(
83
91
  "MEASUREMENT_PLAN_REVISION_CONFLICT",
@@ -6818,6 +6826,8 @@ var measurementSetupResponseSchema = z15.object({
6818
6826
  state: measurementSetupStateSchema,
6819
6827
  nextAction: measurementSetupNextActionSchema,
6820
6828
  mode: measurementSetupModeSchema,
6829
+ /** Whether this project's selected provider roster contains an engine the host can execute now. */
6830
+ answerVisibilityProviderReady: z15.boolean(),
6821
6831
  activeRevision: measurementDraftRevisionSchema.nullable(),
6822
6832
  activeSchemaVersion: z15.union([z15.literal(1), z15.literal(2)]).nullable(),
6823
6833
  draft: z15.object({
@@ -10748,6 +10758,11 @@ var scheduleDtoSchema = z31.object({
10748
10758
  createdAt: z31.string(),
10749
10759
  updatedAt: z31.string()
10750
10760
  });
10761
+ var scheduleExpectedUpdatedAtSchema = z31.string().datetime();
10762
+ function nextScheduleUpdatedAt(previous, nowMs = Date.now()) {
10763
+ const previousMs = previous === void 0 ? Number.NaN : Date.parse(previous);
10764
+ return new Date(Number.isFinite(previousMs) ? Math.max(nowMs, previousMs + 1) : nowMs).toISOString();
10765
+ }
10751
10766
  var scheduleUpsertRequestSchema = z31.object({
10752
10767
  /** Run kind. Defaults to 'answer-visibility' so existing callers don't have to change. */
10753
10768
  kind: schedulableRunKindSchema.optional(),
@@ -10757,7 +10772,12 @@ var scheduleUpsertRequestSchema = z31.object({
10757
10772
  enabled: z31.boolean().optional().default(true),
10758
10773
  providers: z31.array(providerNameSchema).optional().default([]),
10759
10774
  /** Required when kind === 'traffic-sync'. Forbidden for other kinds. Validated server-side. */
10760
- sourceId: z31.string().optional()
10775
+ sourceId: z31.string().optional(),
10776
+ /**
10777
+ * Optimistic concurrency guard. A timestamp updates only that exact version;
10778
+ * null creates only while the schedule is absent. Omit for legacy behavior.
10779
+ */
10780
+ expectedUpdatedAt: scheduleExpectedUpdatedAtSchema.nullable().optional()
10761
10781
  }).refine(
10762
10782
  (data) => data.preset && !data.cron || !data.preset && data.cron,
10763
10783
  { message: 'Exactly one of "preset" or "cron" must be provided' }
@@ -16436,6 +16456,7 @@ export {
16436
16456
  noQueries,
16437
16457
  runInProgress,
16438
16458
  operationInProgress,
16459
+ scheduleVersionConflict,
16439
16460
  measurementPlanRevisionConflict,
16440
16461
  measurementCompiledChecksumConflict,
16441
16462
  measurementRunRevisionMismatch,
@@ -16854,6 +16875,8 @@ export {
16854
16875
  schedulableRunKindSchema,
16855
16876
  SchedulableRunKinds,
16856
16877
  scheduleDtoSchema,
16878
+ scheduleExpectedUpdatedAtSchema,
16879
+ nextScheduleUpdatedAt,
16857
16880
  scheduleUpsertRequestSchema,
16858
16881
  categorizeSource,
16859
16882
  categorizeSourceWithCompetitors,
@@ -92,7 +92,7 @@ import {
92
92
  trafficConnectWordpressRequestSchema,
93
93
  trafficEventKindSchema,
94
94
  trafficSeriesGranularitySchema
95
- } from "./chunk-RXXZ5BU5.js";
95
+ } from "./chunk-GN2G7VFK.js";
96
96
 
97
97
  // src/config.ts
98
98
  import fs from "fs";
@@ -160,8 +160,8 @@ function loadConfig() {
160
160
  if (!fs.existsSync(configPath)) {
161
161
  throw new Error(
162
162
  `Config not found at ${configPath}.
163
- Run "canonry init" to set up interactively, or "canonry init --gemini-key <key>" for non-interactive setup.
164
- For CI/Docker, use "canonry bootstrap" with env vars (GEMINI_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, PERPLEXITY_API_KEY, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET).`
163
+ Run "canonry bootstrap" to create the local Page Health runtime; provider credentials are optional.
164
+ Use "canonry init" instead only when you want interactive provider/OAuth provisioning.`
165
165
  );
166
166
  }
167
167
  const raw = fs.readFileSync(configPath, "utf-8");
@@ -174,8 +174,10 @@ For CI/Docker, use "canonry bootstrap" with env vars (GEMINI_API_KEY, OPENAI_API
174
174
  ].filter(Boolean).join(", ");
175
175
  throw new Error(
176
176
  `Invalid config at ${configPath} \u2014 missing: ${missing}.
177
- These fields are auto-generated. Run "canonry init" (or "canonry init --gemini-key <key>" for non-interactive setup) to create a valid config.
178
- Do not write config.yaml by hand; use "canonry init", "canonry settings", or "canonry bootstrap" instead.`
177
+ Back up this config privately before you repair it.
178
+ Restore the missing values from a known-good backup. If none exists, recover the original values before you retry.
179
+ Keep the original API key and database path. Do not share secrets.
180
+ Do not use "canonry init --force" for recovery. It replaces credentials without a backup.`
179
181
  );
180
182
  }
181
183
  if (parsed.geminiApiKey && !parsed.providers?.gemini) {