@odla-ai/cli 0.25.10 → 0.25.12

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
@@ -72,10 +72,15 @@ snapshot instead of scraping Studio or composing collector routes themselves:
72
72
  npx odla-ai o11y status --app <appId> --env prod --minutes 60 --json
73
73
  ```
74
74
 
75
- Schema v4 keeps application RED, reconciled live-sync load/freshness and
75
+ Schema v6 keeps application RED, an `applicationVersions` breakdown of
76
+ request/error/latency evidence by exact Cloudflare Worker version, reconciled
77
+ live-sync load/freshness and
76
78
  subscription recompute/fanout/payload/commit-to-send performance, the exact
77
79
  latest protected commit-to-visible canary, collector ingest/scheduler trust,
78
- and Cloudflare-owned Worker runtime evidence separate. It derives one
80
+ the live Cloudflare Worker read, and 30-day-retained provider snapshot history
81
+ with a seven-day maximum per read separate. Scheduled collection failures,
82
+ source age, expected cadence, collection lag, and history truncation remain
83
+ machine-readable. It derives one
79
84
  `healthy`, `degraded`, or `unhealthy` verdict with stable reason codes while
80
85
  retaining every source response for diagnosis. Provider unavailability,
81
86
  adaptive sampling, publication delay, collector loss, quota rejection, stale
package/dist/bin.cjs CHANGED
@@ -2388,7 +2388,7 @@ var CAPABILITIES = {
2388
2388
  "compose declared app-capability schema/rules, create guarded seeds when absent, and configure platform AI, auth, and deployment links",
2389
2389
  "apply Google Calendar booking config, then drive state-bound consent, status, discovery, and disconnect flows (bookings run live through the platform proxy)",
2390
2390
  "validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
2391
- "read one versioned o11y status envelope spanning application RED, current live-sync freshness/load, the protected commit-to-visible canary, collector ingest/scheduler trust, provider-owned runtime metrics, and a bounded machine verdict",
2391
+ "read one versioned o11y status envelope spanning application RED, exact Worker versions observed in traffic, current live-sync freshness/load, the protected commit-to-visible canary, collector ingest/scheduler trust, provider-owned runtime metrics, and a bounded machine verdict",
2392
2392
  "inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
2393
2393
  "run app-attributed hosted security discovery and independent validation without provider keys",
2394
2394
  "connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
@@ -2410,7 +2410,7 @@ var CAPABILITIES = {
2410
2410
  "approve GitHub App repository access separately from redacted-snippet disclosure"
2411
2411
  ],
2412
2412
  studio: [
2413
- "view application telemetry, reconciled live-sync load/freshness, commit-to-visible canary health, provider-owned Worker runtime metrics, and environment state",
2413
+ "view application telemetry grouped by exact Worker version, reconciled live-sync load/freshness, commit-to-visible canary health, provider-owned Worker runtime metrics, and environment state",
2414
2414
  "let signed-in users inventory/revoke their own agent grants and admins audit/global-revoke them",
2415
2415
  "review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens",
2416
2416
  "perform manual credential recovery \u2014 for the primary owner or any co-owner \u2014 when the CLI's local shown-once copy is unavailable",
@@ -8115,6 +8115,7 @@ function statusVerdict(reads) {
8115
8115
  sourceHttp(reasons, "canary", reads.canary);
8116
8116
  sourceHttp(reasons, "collector", reads.collector);
8117
8117
  sourceHttp(reasons, "provider", reads.provider);
8118
+ sourceHttp(reasons, "provider", reads.providerHistory, "provider_history_");
8118
8119
  const liveStatus = String(reads.liveSync.body.status ?? "");
8119
8120
  if (liveStatus === "partial") {
8120
8121
  reasons.push({
@@ -8186,6 +8187,14 @@ function statusVerdict(reads) {
8186
8187
  severity: "degraded"
8187
8188
  });
8188
8189
  }
8190
+ const providerHistoryStatus = String(reads.providerHistory.body.status ?? "");
8191
+ if (providerHistoryStatus && providerHistoryStatus !== "observed" && providerHistoryStatus !== "no_data") {
8192
+ reasons.push({
8193
+ source: "provider",
8194
+ code: `provider_history_${providerHistoryStatus}`,
8195
+ severity: "degraded"
8196
+ });
8197
+ }
8189
8198
  return {
8190
8199
  status: reasons.some((reason) => reason.severity === "unhealthy") ? "unhealthy" : reasons.length > 0 ? "degraded" : "healthy",
8191
8200
  reasons
@@ -8205,15 +8214,80 @@ function collectorReason(read3, fallback) {
8205
8214
  );
8206
8215
  return typeof first?.code === "string" && first.code ? first.code : fallback;
8207
8216
  }
8208
- function sourceHttp(reasons, source, read3) {
8217
+ function sourceHttp(reasons, source, read3, prefix = "") {
8209
8218
  if (read3.httpStatus >= 200 && read3.httpStatus < 300) return;
8210
8219
  reasons.push({
8211
8220
  source,
8212
- code: `http_${read3.httpStatus}`,
8221
+ code: `${prefix}http_${read3.httpStatus}`,
8213
8222
  severity: read3.httpStatus >= 500 && read3.httpStatus !== 501 ? "unhealthy" : "degraded"
8214
8223
  });
8215
8224
  }
8216
8225
 
8226
+ // src/o11y-output.ts
8227
+ function printO11yStatus(status, out) {
8228
+ out.log(
8229
+ `o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
8230
+ );
8231
+ const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record6) : [];
8232
+ const requests = routes.reduce(
8233
+ (total, row) => total + numeric3(row.requests),
8234
+ 0
8235
+ );
8236
+ const errors = routes.reduce(
8237
+ (total, row) => total + numeric3(row.errors),
8238
+ 0
8239
+ );
8240
+ out.log(
8241
+ `application ${status.application.httpStatus} ${requests} requests ${errors} errors`
8242
+ );
8243
+ const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record6) : [];
8244
+ out.log(
8245
+ `application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
8246
+ (row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
8247
+ ).join(", ") : "none observed"}`
8248
+ );
8249
+ out.log(liveSyncLine(status.liveSync));
8250
+ const canaryDurations = record6(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
8251
+ out.log(
8252
+ `canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
8253
+ );
8254
+ const collectorIngest = record6(status.collector.body.ingest) ? status.collector.body.ingest : {};
8255
+ const collectorStorage = record6(collectorIngest.storage) ? collectorIngest.storage : {};
8256
+ out.log(
8257
+ `collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
8258
+ );
8259
+ const providerMetrics = record6(status.provider.body.metrics) ? status.provider.body.metrics : {};
8260
+ out.log(
8261
+ `cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors`
8262
+ );
8263
+ const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
8264
+ const providerFreshness = record6(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
8265
+ out.log(
8266
+ `cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
8267
+ );
8268
+ out.log(
8269
+ `verdict ${status.verdict.status} ${status.verdict.reasons.map((reason) => `${reason.source}:${reason.code}`).join(", ") || "all required signals healthy"}`
8270
+ );
8271
+ }
8272
+ function liveSyncLine(read3) {
8273
+ const performance = record6(read3.body.performance) ? read3.body.performance : {};
8274
+ const commitToSend = record6(performance.commitToSend) ? performance.commitToSend : {};
8275
+ return `live-sync ${read3.httpStatus} ${String(read3.body.status ?? read3.body.error ?? "unavailable")} ${numeric3(read3.body.activeConnections)} active ${optionalNumeric(commitToSend.p95)} commit-to-send p95 ${numeric3(performance.sendFailures)} send failures`;
8276
+ }
8277
+ function record6(value) {
8278
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
8279
+ }
8280
+ function numeric3(value) {
8281
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
8282
+ }
8283
+ function optionalNumeric(value) {
8284
+ return typeof value === "number" && Number.isFinite(value) ? `${value} ms` : "\u2014";
8285
+ }
8286
+ function optionalAge(value) {
8287
+ if (typeof value !== "number" || !Number.isFinite(value)) return "unknown";
8288
+ return `${Math.max(0, Math.round(value / 1e3))}s`;
8289
+ }
8290
+
8217
8291
  // src/o11y-command.ts
8218
8292
  async function o11yCommand(parsed, deps = {}) {
8219
8293
  assertArgs(
@@ -8251,8 +8325,23 @@ async function o11yCommand(parsed, deps = {}) {
8251
8325
  const base = `${cfg.platformUrl}/o11y/${encodeURIComponent(appId)}`;
8252
8326
  const query = new URLSearchParams({ env, minutes: String(minutes) });
8253
8327
  const headers = { authorization: `Bearer ${token}` };
8254
- const [application, liveSync, canary, collector, provider] = await Promise.all([
8328
+ const versionsQuery = new URLSearchParams({
8329
+ env,
8330
+ minutes: String(minutes),
8331
+ dimension: "version",
8332
+ metric: "requests"
8333
+ });
8334
+ const [
8335
+ application,
8336
+ applicationVersions,
8337
+ liveSync,
8338
+ canary,
8339
+ collector,
8340
+ provider,
8341
+ providerHistory
8342
+ ] = await Promise.all([
8255
8343
  read2(`${base}/metrics/red?${query}`, headers, doFetch),
8344
+ read2(`${base}/explore?${versionsQuery}`, headers, doFetch),
8256
8345
  read2(
8257
8346
  `${base}/live-sync/health?${query}`,
8258
8347
  headers,
@@ -8264,31 +8353,35 @@ async function o11yCommand(parsed, deps = {}) {
8264
8353
  doFetch
8265
8354
  ),
8266
8355
  read2(`${base}/self-health?${query}`, headers, doFetch),
8267
- read2(`${base}/provider/cloudflare?${query}`, headers, doFetch)
8356
+ read2(`${base}/provider/cloudflare?${query}`, headers, doFetch),
8357
+ read2(`${base}/provider/cloudflare/history?${query}`, headers, doFetch)
8268
8358
  ]);
8269
8359
  const verdict = statusVerdict({
8270
8360
  application,
8271
8361
  liveSync,
8272
8362
  canary,
8273
8363
  collector,
8274
- provider
8364
+ provider,
8365
+ providerHistory
8275
8366
  });
8276
8367
  const status = {
8277
- schemaVersion: 4,
8368
+ schemaVersion: 6,
8278
8369
  scope: { appId, env, minutes },
8279
8370
  observedAt: (/* @__PURE__ */ new Date()).toISOString(),
8280
8371
  verdict,
8281
8372
  application,
8373
+ applicationVersions,
8282
8374
  liveSync,
8283
8375
  canary,
8284
8376
  collector,
8285
- provider
8377
+ provider,
8378
+ providerHistory
8286
8379
  };
8287
8380
  if (parsed.options.json === true) {
8288
8381
  out.log(JSON.stringify(status, null, 2));
8289
8382
  return;
8290
8383
  }
8291
- printStatus2(status, out);
8384
+ printO11yStatus(status, out);
8292
8385
  }
8293
8386
  function statusMinutes(value) {
8294
8387
  if (!Number.isSafeInteger(value) || value < 1 || value > 7 * 24 * 60) {
@@ -8310,56 +8403,6 @@ async function read2(url, headers, doFetch) {
8310
8403
  }
8311
8404
  return { httpStatus: response2.status, body };
8312
8405
  }
8313
- function printStatus2(status, out) {
8314
- out.log(
8315
- `o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
8316
- );
8317
- const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record6) : [];
8318
- const requests = routes.reduce(
8319
- (total, row) => total + numeric3(row.requests),
8320
- 0
8321
- );
8322
- const errors = routes.reduce(
8323
- (total, row) => total + numeric3(row.errors),
8324
- 0
8325
- );
8326
- out.log(
8327
- `application ${status.application.httpStatus} ${requests} requests ${errors} errors`
8328
- );
8329
- out.log(
8330
- liveSyncLine(status.liveSync)
8331
- );
8332
- const canaryDurations = record6(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
8333
- out.log(
8334
- `canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
8335
- );
8336
- const collectorIngest = record6(status.collector.body.ingest) ? status.collector.body.ingest : {};
8337
- const collectorStorage = record6(collectorIngest.storage) ? collectorIngest.storage : {};
8338
- out.log(
8339
- `collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
8340
- );
8341
- const providerMetrics = record6(status.provider.body.metrics) ? status.provider.body.metrics : {};
8342
- out.log(
8343
- `cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors`
8344
- );
8345
- out.log(
8346
- `verdict ${status.verdict.status} ${status.verdict.reasons.map((reason) => `${reason.source}:${reason.code}`).join(", ") || "all required signals healthy"}`
8347
- );
8348
- }
8349
- function liveSyncLine(read3) {
8350
- const performance = record6(read3.body.performance) ? read3.body.performance : {};
8351
- const commitToSend = record6(performance.commitToSend) ? performance.commitToSend : {};
8352
- return `live-sync ${read3.httpStatus} ${String(read3.body.status ?? read3.body.error ?? "unavailable")} ${numeric3(read3.body.activeConnections)} active ${optionalNumeric(commitToSend.p95)} commit-to-send p95 ${numeric3(performance.sendFailures)} send failures`;
8353
- }
8354
- function record6(value) {
8355
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
8356
- }
8357
- function numeric3(value) {
8358
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
8359
- }
8360
- function optionalNumeric(value) {
8361
- return typeof value === "number" && Number.isFinite(value) ? `${value} ms` : "\u2014";
8362
- }
8363
8406
 
8364
8407
  // src/provision.ts
8365
8408
  var import_apps5 = require("@odla-ai/apps");