@odla-ai/cli 0.33.0 → 0.34.0

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.
@@ -257,10 +257,10 @@ function isManagedDevVar(line) {
257
257
  const match = line.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/);
258
258
  return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
259
259
  }
260
- function writePrivateText(path, text2) {
260
+ function writePrivateText(path, text3) {
261
261
  mkdirSync(dirname2(path), { recursive: true });
262
262
  const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
263
- writeFileSync(temporary, text2, { mode: 384 });
263
+ writeFileSync(temporary, text3, { mode: 384 });
264
264
  chmodSync(temporary, 384);
265
265
  renameSync(temporary, path);
266
266
  }
@@ -375,7 +375,7 @@ async function freshHandshake(ctx, waitMs) {
375
375
  }
376
376
  function cachedGrantCovers(cached, required) {
377
377
  if (required.optionalProjectCapabilities.length === 0) return true;
378
- return required.projectIds.every((id) => cached.projectIds?.includes(id)) && required.optionalProjectCapabilities.every(
378
+ return required.projectIds.every((id2) => cached.projectIds?.includes(id2)) && required.optionalProjectCapabilities.every(
379
379
  (capability) => cached.optionalProjectCapabilities?.includes(capability)
380
380
  );
381
381
  }
@@ -534,8 +534,8 @@ async function scopedToken(platform, scope, options, doFetch, out) {
534
534
  // src/principal-presentation.ts
535
535
  function unresolvedPrincipalLabel(credentialKind2, principalId) {
536
536
  const kind = typeof credentialKind2 === "string" ? credentialKind2.trim() : "";
537
- const id = typeof principalId === "string" ? principalId.trim() : "";
538
- const audit = kind && id ? `${kind}:${id}` : kind || id;
537
+ const id2 = typeof principalId === "string" ? principalId.trim() : "";
538
+ const audit = kind && id2 ? `${kind}:${id2}` : kind || id2;
539
539
  return `Unknown principal${audit ? ` [${audit}]` : ""}`;
540
540
  }
541
541
 
@@ -547,38 +547,38 @@ function adminAiAuditQuery(filters) {
547
547
  }
548
548
  return `?limit=${filters.limit}`;
549
549
  }
550
- async function readAdminAiAudit(request2) {
551
- const response2 = await request2.fetch(`${request2.platform}/registry/platform/ai-audit${request2.query}`, {
552
- headers: request2.headers
550
+ async function readAdminAiAudit(request3) {
551
+ const response2 = await request3.fetch(`${request3.platform}/registry/platform/ai-audit${request3.query}`, {
552
+ headers: request3.headers
553
553
  });
554
554
  const body = await responseBody(response2);
555
555
  if (!response2.ok) throw new Error(apiError(response2.status, body));
556
- if (request2.json) {
557
- request2.stdout.log(JSON.stringify(body, null, 2));
556
+ if (request3.json) {
557
+ request3.stdout.log(JSON.stringify(body, null, 2));
558
558
  return;
559
559
  }
560
560
  const events = isRecord(body) && Array.isArray(body.events) ? body.events.filter(isRecord) : [];
561
- request2.stdout.log("when change target before -> after actor");
561
+ request3.stdout.log("when change target before -> after actor");
562
562
  for (const event of events) {
563
563
  const before = isRecord(event.oldPolicy) ? event.oldPolicy : void 0;
564
564
  const after = isRecord(event.newPolicy) ? event.newPolicy : void 0;
565
- const route2 = before && after ? `${String(before.provider)}/${String(before.model)}@v${String(before.version)} -> ${String(after.provider)}/${String(after.model)}@v${String(after.version)}` : "value not retained";
566
- request2.stdout.log([
565
+ const route3 = before && after ? `${String(before.provider)}/${String(before.model)}@v${String(before.version)} -> ${String(after.provider)}/${String(after.model)}@v${String(after.version)}` : "value not retained";
566
+ request3.stdout.log([
567
567
  timestamp(event.createdAt),
568
568
  String(event.changeKind ?? ""),
569
569
  String(event.purpose ?? event.provider ?? ""),
570
- route2,
570
+ route3,
571
571
  unresolvedPrincipalLabel(event.actorType, event.actorId)
572
572
  ].join(" "));
573
573
  }
574
574
  }
575
575
  async function responseBody(response2) {
576
- const text2 = await response2.text();
577
- if (!text2) return {};
576
+ const text3 = await response2.text();
577
+ if (!text3) return {};
578
578
  try {
579
- return JSON.parse(text2);
579
+ return JSON.parse(text3);
580
580
  } catch {
581
- return { message: text2.slice(0, 300) };
581
+ return { message: text3.slice(0, 300) };
582
582
  }
583
583
  }
584
584
  function apiError(status, body) {
@@ -619,14 +619,14 @@ function adminAiUsageQuery(filters) {
619
619
  const query = params.toString();
620
620
  return query ? `?${query}` : "";
621
621
  }
622
- async function readAdminAiUsage(request2) {
623
- const res = await request2.fetch(`${request2.platform}/registry/platform/ai-usage${request2.query}`, {
624
- headers: request2.headers
622
+ async function readAdminAiUsage(request3) {
623
+ const res = await request3.fetch(`${request3.platform}/registry/platform/ai-usage${request3.query}`, {
624
+ headers: request3.headers
625
625
  });
626
626
  const body = await responseBody2(res);
627
627
  if (!res.ok) throw new Error(apiError2("read platform AI usage", res.status, body));
628
- if (request2.json) request2.stdout.log(JSON.stringify(body, null, 2));
629
- else printUsage(body, request2.stdout);
628
+ if (request3.json) request3.stdout.log(JSON.stringify(body, null, 2));
629
+ else printUsage(body, request3.stdout);
630
630
  }
631
631
  function usageLimit(value2) {
632
632
  if (!Number.isSafeInteger(value2) || value2 < 1 || value2 > 500) {
@@ -680,12 +680,12 @@ function timestamp2(value2) {
680
680
  return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
681
681
  }
682
682
  async function responseBody2(res) {
683
- const text2 = await res.text();
684
- if (!text2) return {};
683
+ const text3 = await res.text();
684
+ if (!text3) return {};
685
685
  try {
686
- return JSON.parse(text2);
686
+ return JSON.parse(text3);
687
687
  } catch {
688
- return { message: text2.slice(0, 300) };
688
+ return { message: text3.slice(0, 300) };
689
689
  }
690
690
  }
691
691
  function apiError2(action2, status, body) {
@@ -861,12 +861,12 @@ function catalogModels(body) {
861
861
  return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
862
862
  }
863
863
  async function responseBody3(res) {
864
- const text2 = await res.text();
865
- if (!text2) return {};
864
+ const text3 = await res.text();
865
+ if (!text3) return {};
866
866
  try {
867
- return JSON.parse(text2);
867
+ return JSON.parse(text3);
868
868
  } catch {
869
- return { message: text2.slice(0, 300) };
869
+ return { message: text3.slice(0, 300) };
870
870
  }
871
871
  }
872
872
  function apiError3(action2, status, body) {
@@ -998,7 +998,7 @@ function calendarServiceConfig(cfg, env) {
998
998
  if (!google) throw new Error("calendar.google is required when the calendar service is enabled");
999
999
  const configured = google.availabilityCalendars?.[env] ?? google.calendars?.[env];
1000
1000
  if (!configured?.length) throw new Error(`calendar.google.availabilityCalendars.${env} is required`);
1001
- const availability = unique(configured.map((id) => id.trim()));
1001
+ const availability = unique(configured.map((id2) => id2.trim()));
1002
1002
  return {
1003
1003
  provider: "google",
1004
1004
  access: "book",
@@ -1047,7 +1047,7 @@ function validateCalendarConfig(cfg, envs, services, path) {
1047
1047
  if (ids.length > 10) {
1048
1048
  throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must contain at most 10 calendar ids`);
1049
1049
  }
1050
- if (ids.some((id) => !safeText2(id, 1024))) {
1050
+ if (ids.some((id2) => !safeText2(id2, 1024))) {
1051
1051
  throw new Error(`${path}: calendar.google.${availabilityKey}.${env} contains an invalid calendar id`);
1052
1052
  }
1053
1053
  }
@@ -1189,6 +1189,175 @@ function unique2(values) {
1189
1189
  return [...new Set(values.filter(Boolean))];
1190
1190
  }
1191
1191
 
1192
+ // src/monitoring-validation.ts
1193
+ var CADENCES = /* @__PURE__ */ new Set(["1m", "2m", "5m", "10m", "15m", "30m", "1h"]);
1194
+ var WINDOWS = /* @__PURE__ */ new Set(["7d", "28d", "30d"]);
1195
+ var SHORT = /* @__PURE__ */ new Set(["30m", "1h", "6h", "12h", "1d"]);
1196
+ var LONG = /* @__PURE__ */ new Set(["1d", "3d", "7d"]);
1197
+ var DAYS = /* @__PURE__ */ new Set(["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]);
1198
+ var O11Y_METRICS = /* @__PURE__ */ new Set(["error_rate", "latency_p95", "synthetic_success", "synthetic_publish_to_visible"]);
1199
+ var COMPARATORS = /* @__PURE__ */ new Set(["gt", "gte", "lt", "lte"]);
1200
+ function validateMonitoringConfig(cfg, envs, services, path) {
1201
+ if (!cfg.o11y) return;
1202
+ if (!record(cfg.o11y)) fail(path, "o11y must be an object");
1203
+ only(cfg.o11y, ["service", "endpoint", "version", "monitoring"], `${path}: o11y`);
1204
+ const monitoring = cfg.o11y.monitoring;
1205
+ if (!monitoring) return;
1206
+ if (!services.includes("o11y")) fail(path, 'o11y.monitoring requires "o11y" in services');
1207
+ if (!record(monitoring)) fail(path, "o11y.monitoring must be an object");
1208
+ only(monitoring, ["probes", "slos", "notifications"], `${path}: o11y.monitoring`);
1209
+ if (monitoring.probes !== void 0 && (!Array.isArray(monitoring.probes) || monitoring.probes.length > 50)) {
1210
+ fail(path, "o11y.monitoring.probes must contain at most 50 probes");
1211
+ }
1212
+ const probeIds = /* @__PURE__ */ new Set();
1213
+ (monitoring.probes ?? []).forEach((probe, index) => validateProbe(probe, index, envs, path, probeIds));
1214
+ if (!Array.isArray(monitoring.slos) || monitoring.slos.length < 1 || monitoring.slos.length > 50) {
1215
+ fail(path, "o11y.monitoring.slos must contain 1 through 50 SLOs");
1216
+ }
1217
+ const sloIds = /* @__PURE__ */ new Set();
1218
+ monitoring.slos.forEach((slo, index) => validateSlo(slo, index, path, probeIds, sloIds));
1219
+ if (monitoring.notifications !== void 0) validateNotifications(monitoring.notifications, envs, path);
1220
+ }
1221
+ function validateProbe(value2, index, envs, path, ids) {
1222
+ const label = `${path}: o11y.monitoring.probes[${index}]`;
1223
+ if (!record(value2)) fail(label, "must be an object");
1224
+ only(value2, ["id", "route", "envs", "every", "timeout", "ready", "expect", "enabled"], label);
1225
+ if (!id(value2.id)) fail(label, "id must be lowercase letters, numbers, and hyphens");
1226
+ if (ids.has(value2.id)) fail(label, `id duplicates ${value2.id}`);
1227
+ ids.add(value2.id);
1228
+ if (!route(value2.route)) fail(label, "route must be a relative absolute path without credentials or a fragment");
1229
+ if (!CADENCES.has(String(value2.every))) fail(label, `every must be one of ${[...CADENCES].join(", ")}`);
1230
+ if (value2.timeout !== void 0 && (!Number.isSafeInteger(value2.timeout) || Number(value2.timeout) < 1e3 || Number(value2.timeout) > 6e4)) fail(label, "timeout must be an integer from 1000 through 60000");
1231
+ if (value2.envs !== void 0 && (!Array.isArray(value2.envs) || value2.envs.length < 1 || value2.envs.some((env) => typeof env !== "string" || !envs.includes(env) && env !== "prod"))) fail(label, "envs must contain configured environment names");
1232
+ if (value2.ready !== void 0) {
1233
+ if (!record(value2.ready) || !text(value2.ready.selector, 300)) fail(label, "ready.selector is required");
1234
+ only(value2.ready, ["selector"], `${label}.ready`);
1235
+ }
1236
+ if (!record(value2.expect)) fail(label, "expect must be an object");
1237
+ only(value2.expect, ["status", "titleIncludes", "textIncludes", "accessibility"], `${label}.expect`);
1238
+ if (!Number.isSafeInteger(value2.expect.status) || Number(value2.expect.status) < 100 || Number(value2.expect.status) > 599) fail(label, "expect.status must be an HTTP status from 100 through 599");
1239
+ if (value2.expect.titleIncludes !== void 0 && !text(value2.expect.titleIncludes, 300)) fail(label, "expect.titleIncludes is invalid");
1240
+ if (value2.expect.textIncludes !== void 0 && (!Array.isArray(value2.expect.textIncludes) || value2.expect.textIncludes.length > 10 || value2.expect.textIncludes.some((item) => !text(item, 500)))) fail(label, "expect.textIncludes must contain at most 10 bounded strings");
1241
+ if (value2.expect.accessibility !== void 0) validateAccessibility(value2.expect.accessibility, label);
1242
+ }
1243
+ function validateAccessibility(value2, label) {
1244
+ if (!Array.isArray(value2) || value2.length > 10) fail(label, "expect.accessibility must contain at most 10 assertions");
1245
+ for (const item of value2) {
1246
+ if (!record(item) || !text(item.role, 80) || !text(item.name, 300)) fail(label, "expect.accessibility entries need role and name");
1247
+ only(item, ["role", "name"], `${label}.expect.accessibility`);
1248
+ }
1249
+ }
1250
+ function validateSlo(value2, index, path, probes, ids) {
1251
+ const label = `${path}: o11y.monitoring.slos[${index}]`;
1252
+ if (!record(value2)) fail(label, "must be an object");
1253
+ only(value2, ["id", "name", "indicator", "target", "window", "alerts", "enabled"], label);
1254
+ if (!id(value2.id) || ids.has(value2.id)) fail(label, "id must be unique lowercase letters, numbers, and hyphens");
1255
+ ids.add(value2.id);
1256
+ if (value2.name !== void 0 && !text(value2.name, 160)) fail(label, "name is invalid");
1257
+ validateIndicator(value2.indicator, label, probes);
1258
+ if (typeof value2.target !== "number" || !Number.isFinite(value2.target) || value2.target <= 0 || value2.target >= 1) fail(label, "target must be a fraction greater than 0 and less than 1");
1259
+ if (!WINDOWS.has(String(value2.window))) fail(label, `window must be one of ${[...WINDOWS].join(", ")}`);
1260
+ if (value2.alerts !== void 0) validateAlerts(value2.alerts, label);
1261
+ }
1262
+ function validateIndicator(value2, label, probes) {
1263
+ if (!record(value2)) fail(label, "indicator must be an object");
1264
+ if (value2.type === "probe-success") {
1265
+ only(value2, ["type", "probes"], `${label}.indicator`);
1266
+ if (!Array.isArray(value2.probes) || value2.probes.length < 1) fail(label, "probe-success must select at least one probe");
1267
+ if (value2.probes.some((probe) => typeof probe !== "string" || !probes.has(probe))) fail(label, "indicator references an unknown probe");
1268
+ return;
1269
+ }
1270
+ if (value2.type !== "o11y-metric") fail(label, "indicator.type must be probe-success or o11y-metric");
1271
+ only(value2, ["type", "metric", "comparator", "threshold", "every", "observationWindow", "route"], `${label}.indicator`);
1272
+ if (!O11Y_METRICS.has(String(value2.metric))) fail(label, "indicator.metric is unsupported");
1273
+ if (!COMPARATORS.has(String(value2.comparator))) fail(label, "indicator.comparator is unsupported");
1274
+ if (typeof value2.threshold !== "number" || !Number.isFinite(value2.threshold)) fail(label, "indicator.threshold must be finite");
1275
+ if (!CADENCES.has(String(value2.every)) || !CADENCES.has(String(value2.observationWindow))) fail(label, "indicator cadence and observationWindow must be supported durations");
1276
+ if (value2.route !== void 0 && !routePattern(value2.route)) fail(label, "indicator.route must be an exact route template or trailing-* prefix");
1277
+ if ((value2.metric === "synthetic_success" || value2.metric === "synthetic_publish_to_visible") && value2.route !== void 0) fail(label, "synthetic indicators cannot select a route");
1278
+ }
1279
+ function validateAlerts(value2, label) {
1280
+ if (!record(value2)) fail(label, "alerts must be an object");
1281
+ only(value2, ["spike", "trend"], `${label}.alerts`);
1282
+ if (value2.spike !== void 0) {
1283
+ if (!record(value2.spike)) fail(label, "alerts.spike must be an object");
1284
+ only(value2.spike, ["badChecks", "withinChecks", "recoverAfter"], `${label}.alerts.spike`);
1285
+ const bad = positive(value2.spike.badChecks, 2), within = positive(value2.spike.withinChecks, 3), recover = positive(value2.spike.recoverAfter, 2);
1286
+ if (bad > within || within > 20 || recover > 20) fail(label, "alerts.spike requires badChecks <= withinChecks <= 20 and recoverAfter <= 20");
1287
+ }
1288
+ if (value2.trend !== void 0) {
1289
+ if (!record(value2.trend)) fail(label, "alerts.trend must be an object");
1290
+ only(value2.trend, ["burnRate", "shortWindow", "longWindow", "minBadChecks"], `${label}.alerts.trend`);
1291
+ const burn = value2.trend.burnRate ?? 1;
1292
+ if (typeof burn !== "number" || !Number.isFinite(burn) || burn <= 0 || burn > 1e3) fail(label, "alerts.trend.burnRate must be greater than 0");
1293
+ if (value2.trend.shortWindow !== void 0 && !SHORT.has(String(value2.trend.shortWindow))) fail(label, "alerts.trend.shortWindow is unsupported");
1294
+ if (value2.trend.longWindow !== void 0 && !LONG.has(String(value2.trend.longWindow))) fail(label, "alerts.trend.longWindow is unsupported");
1295
+ if (positive(value2.trend.minBadChecks, 2) > 100) fail(label, "alerts.trend.minBadChecks must be at most 100");
1296
+ }
1297
+ }
1298
+ function validateNotifications(value2, envs, path) {
1299
+ if (!record(value2)) fail(path, "o11y.monitoring.notifications must map environments to policies");
1300
+ for (const [env, policy] of Object.entries(value2)) {
1301
+ const label = `${path}: o11y.monitoring.notifications.${env}`;
1302
+ if (!envs.includes(env) && env !== "prod") fail(label, "is not a configured environment");
1303
+ if (!record(policy)) fail(label, "must be an object");
1304
+ only(policy, ["email", "timezone", "daily", "weekly"], label);
1305
+ if (!Array.isArray(policy.email) || policy.email.length < 1 || policy.email.length > 10 || policy.email.some((email) => !emailAddress(email))) fail(label, "email must contain 1 through 10 email addresses");
1306
+ if (!timezone(policy.timezone)) fail(label, "timezone must be an IANA timezone");
1307
+ if (policy.daily !== void 0 && policy.daily !== false && !clock(policy.daily)) fail(label, "daily must be HH:MM or false");
1308
+ if (policy.weekly !== void 0 && policy.weekly !== false) {
1309
+ if (!record(policy.weekly) || !DAYS.has(String(policy.weekly.day)) || !clock(policy.weekly.at)) fail(label, "weekly needs a weekday and HH:MM time");
1310
+ only(policy.weekly, ["day", "at"], `${label}.weekly`);
1311
+ }
1312
+ }
1313
+ }
1314
+ function fail(label, message2) {
1315
+ throw new Error(`${label}: ${message2}`);
1316
+ }
1317
+ function record(value2) {
1318
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
1319
+ }
1320
+ function only(value2, keys, label) {
1321
+ const extra = Object.keys(value2).find((key) => !keys.includes(key));
1322
+ if (extra) fail(label, `${extra} is not supported`);
1323
+ }
1324
+ function id(value2) {
1325
+ return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
1326
+ }
1327
+ function text(value2, max) {
1328
+ return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
1329
+ }
1330
+ function positive(value2, fallback) {
1331
+ return value2 === void 0 ? fallback : Number.isSafeInteger(value2) && Number(value2) > 0 ? Number(value2) : Infinity;
1332
+ }
1333
+ function route(value2) {
1334
+ if (typeof value2 !== "string" || value2.length > 2048 || !value2.startsWith("/") || value2.startsWith("//")) return false;
1335
+ try {
1336
+ const url = new URL(value2, "https://probe.invalid");
1337
+ return url.origin === "https://probe.invalid" && !url.hash;
1338
+ } catch {
1339
+ return false;
1340
+ }
1341
+ }
1342
+ function routePattern(value2) {
1343
+ return typeof value2 === "string" && value2.length <= 160 && /^\/[A-Za-z0-9_./:-]+\*?$/.test(value2) && !value2.slice(0, -1).includes("*");
1344
+ }
1345
+ function emailAddress(value2) {
1346
+ return typeof value2 === "string" && value2.length <= 254 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value2);
1347
+ }
1348
+ function clock(value2) {
1349
+ return typeof value2 === "string" && /^(?:[01]\d|2[0-3]):[0-5]\d$/.test(value2);
1350
+ }
1351
+ function timezone(value2) {
1352
+ if (typeof value2 !== "string" || value2.length > 100) return false;
1353
+ try {
1354
+ new Intl.DateTimeFormat("en", { timeZone: value2 }).format(0);
1355
+ return true;
1356
+ } catch {
1357
+ return false;
1358
+ }
1359
+ }
1360
+
1192
1361
  // src/config.ts
1193
1362
  var DEFAULT_PLATFORM = "https://odla.ai";
1194
1363
  var DEFAULT_ENVS = ["dev"];
@@ -1209,6 +1378,7 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
1209
1378
  const services = unique3(raw.services?.length ? raw.services : DEFAULT_SERVICES);
1210
1379
  validateServices(services, resolved);
1211
1380
  validateCalendarConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
1381
+ validateMonitoringConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
1212
1382
  const local = {
1213
1383
  tokenFile: resolve2(rootDir, raw.local?.tokenFile ?? ".odla/dev-token.json"),
1214
1384
  credentialsFile: resolve2(rootDir, raw.local?.credentialsFile ?? ".odla/credentials.local.json"),
@@ -1610,7 +1780,7 @@ async function adminCommand(parsed, deps = {}) {
1610
1780
  import process11 from "process";
1611
1781
 
1612
1782
  // src/whoami-command.ts
1613
- var text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
1783
+ var text2 = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
1614
1784
  function principalKind(value2, machine) {
1615
1785
  return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
1616
1786
  }
@@ -1623,12 +1793,12 @@ function credentialKind(value2, machine, scopes) {
1623
1793
  function managerOf(value2) {
1624
1794
  if (!value2 || typeof value2 !== "object") return null;
1625
1795
  const row = value2;
1626
- const principalId = text(row.principalId);
1796
+ const principalId = text2(row.principalId);
1627
1797
  if (!principalId) return null;
1628
1798
  return {
1629
1799
  principalId,
1630
- displayName: text(row.displayName) ?? "Unnamed member",
1631
- handle: text(row.handle) ?? ""
1800
+ displayName: text2(row.displayName) ?? "Unnamed member",
1801
+ handle: text2(row.handle) ?? ""
1632
1802
  };
1633
1803
  }
1634
1804
  function unnamedPrincipal(kind) {
@@ -1642,14 +1812,14 @@ async function fetchIdentity(platformUrl, token, doFetch) {
1642
1812
  });
1643
1813
  if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
1644
1814
  const body = await res.json();
1645
- const developerId = text(body.developerId) ?? "";
1815
+ const developerId = text2(body.developerId) ?? "";
1646
1816
  const machine = body.machine === true;
1647
1817
  const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
1648
- const principalId = text(body.principalId) ?? developerId;
1649
- const email = text(body.email);
1818
+ const principalId = text2(body.principalId) ?? developerId;
1819
+ const email = text2(body.email);
1650
1820
  const kind = principalKind(body.principalKind, machine);
1651
- const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
1652
- const handle = text(body.handle) ?? "";
1821
+ const displayName = text2(body.displayName) ?? email ?? unnamedPrincipal(kind);
1822
+ const handle = text2(body.handle) ?? "";
1653
1823
  const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
1654
1824
  return {
1655
1825
  developerId,
@@ -1659,7 +1829,7 @@ async function fetchIdentity(platformUrl, token, doFetch) {
1659
1829
  handle,
1660
1830
  manager: managerOf(body.manager),
1661
1831
  credential: {
1662
- id: text(credential2.id),
1832
+ id: text2(credential2.id),
1663
1833
  kind: credentialKind(credential2.kind, machine, scopes)
1664
1834
  },
1665
1835
  email,
@@ -1854,13 +2024,13 @@ async function agentCommand(parsed, deps = {}) {
1854
2024
  const base = `${cfg.dbEndpoint}/app/${encodeURIComponent(tenant)}/admin/agent-jobs`;
1855
2025
  const headers = { authorization: `Bearer ${credential2}` };
1856
2026
  if (action2 === "retry") {
1857
- const id = parsed.positionals[2];
1858
- const res2 = await doFetch(`${base}/${encodeURIComponent(id)}/retry`, { method: "POST", headers });
2027
+ const id2 = parsed.positionals[2];
2028
+ const res2 = await doFetch(`${base}/${encodeURIComponent(id2)}/retry`, { method: "POST", headers });
1859
2029
  const body2 = await readJson(res2);
1860
2030
  if (!res2.ok) throw new Error(`agent retry failed (${res2.status}): ${errorMessage(body2)}`);
1861
2031
  const result2 = { v: 1, appId: cfg.app.id, env, tenant, ...body2 };
1862
2032
  if (parsed.options.json === true) out.log(JSON.stringify(result2, null, 2));
1863
- else out.log(`${tenant}: requeued ${id}`);
2033
+ else out.log(`${tenant}: requeued ${id2}`);
1864
2034
  return;
1865
2035
  }
1866
2036
  const state2 = stringOpt(parsed.options.state);
@@ -1944,8 +2114,8 @@ async function appImport(options) {
1944
2114
  const out = options.stdout ?? console;
1945
2115
  const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
1946
2116
  const { tenant } = resolveTenant(cfg, options.env);
1947
- const text2 = options.file === "-" ? (options.readStdin ?? (() => readFileSync4(0, "utf8")))() : readFileSync4(options.file, "utf8");
1948
- const { format, sources } = parseImport(text2, options.ns);
2117
+ const text3 = options.file === "-" ? (options.readStdin ?? (() => readFileSync4(0, "utf8")))() : readFileSync4(options.file, "utf8");
2118
+ const { format, sources } = parseImport(text3, options.ns);
1949
2119
  if (format === "namespace-map" && options.ns) {
1950
2120
  throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
1951
2121
  }
@@ -2161,7 +2331,7 @@ var EXTENSIONS = {
2161
2331
  "text/html": "html",
2162
2332
  "application/json": "json"
2163
2333
  };
2164
- var encode = (text2) => new TextEncoder().encode(text2);
2334
+ var encode = (text3) => new TextEncoder().encode(text3);
2165
2335
  function assetFileName(uuid, mime) {
2166
2336
  const ext = EXTENSIONS[mime.split(";")[0].trim().toLowerCase()] ?? "bin";
2167
2337
  return `${uuid.replace(/[^a-zA-Z0-9._-]/g, "_")}.${ext}`;
@@ -2323,18 +2493,18 @@ async function readCalendarStatus(ctx) {
2323
2493
  }
2324
2494
  async function discoverGoogleCalendars(ctx) {
2325
2495
  const raw = await calendarJson(ctx, "/calendars", {});
2326
- const value2 = record(raw);
2496
+ const value2 = record2(raw);
2327
2497
  if (!value2 || !Array.isArray(value2.calendars)) throw new Error("calendar discovery returned an invalid response");
2328
2498
  return value2.calendars.map((item, index) => {
2329
- const calendar = record(item);
2330
- const id = textField(calendar?.id, 1024);
2331
- if (!calendar || !id) throw new Error(`calendar discovery returned an invalid calendar at index ${index}`);
2499
+ const calendar = record2(item);
2500
+ const id2 = textField(calendar?.id, 1024);
2501
+ if (!calendar || !id2) throw new Error(`calendar discovery returned an invalid calendar at index ${index}`);
2332
2502
  const role = calendar.accessRole;
2333
2503
  if (role !== void 0 && role !== "freeBusyReader" && role !== "reader" && role !== "writer" && role !== "owner") {
2334
2504
  throw new Error(`calendar discovery returned an invalid access role at index ${index}`);
2335
2505
  }
2336
2506
  return {
2337
- id,
2507
+ id: id2,
2338
2508
  ...optionalText("summary", calendar.summary, 500),
2339
2509
  ...typeof calendar.primary === "boolean" ? { primary: calendar.primary } : {},
2340
2510
  ...typeof calendar.selected === "boolean" ? { selected: calendar.selected } : {},
@@ -2362,10 +2532,10 @@ async function pollCalendarConnection(ctx, attemptId) {
2362
2532
  }
2363
2533
  function parseCalendarStatus(raw, env) {
2364
2534
  const outer = wrapped(raw, "calendar");
2365
- const value2 = record(outer.attempt) ?? record(outer.status) ?? outer;
2366
- const connection = record(value2.connection) ?? {};
2367
- const config = record(value2.config) ?? record(outer.config) ?? {};
2368
- const googleConfig = record(config.google) ?? config;
2535
+ const value2 = record2(outer.attempt) ?? record2(outer.status) ?? outer;
2536
+ const connection = record2(value2.connection) ?? {};
2537
+ const config = record2(value2.config) ?? record2(outer.config) ?? {};
2538
+ const googleConfig = record2(config.google) ?? config;
2369
2539
  const stateValue = calendarState(value2.status ?? value2.state ?? connection.status ?? connection.state);
2370
2540
  if (!stateValue) {
2371
2541
  throw new Error("calendar status returned an invalid connection state");
@@ -2378,7 +2548,7 @@ function parseCalendarStatus(raw, env) {
2378
2548
  if (accessValue !== void 0 && accessValue !== "book" && accessValue !== "read") {
2379
2549
  throw new Error("calendar status returned unsupported access");
2380
2550
  }
2381
- const errorValue = record(value2.error) ?? record(connection.error);
2551
+ const errorValue = record2(value2.error) ?? record2(connection.error);
2382
2552
  const errorCode2 = textField(value2.lastErrorCode, 128);
2383
2553
  const bookingPageValue = Object.hasOwn(value2, "bookingPageUrl") ? value2.bookingPageUrl : Object.hasOwn(config, "bookingPageUrl") ? config.bookingPageUrl : googleConfig.bookingPageUrl;
2384
2554
  const connected = typeof (value2.connected ?? connection.connected) === "boolean" ? Boolean(value2.connected ?? connection.connected) : ["healthy", "degraded"].includes(stateValue);
@@ -2448,11 +2618,11 @@ async function calendarJson(ctx, suffix, init) {
2448
2618
  return body;
2449
2619
  }
2450
2620
  function wrapped(raw, key) {
2451
- const outer = record(raw);
2621
+ const outer = record2(raw);
2452
2622
  if (!outer) throw new Error("calendar returned an invalid response");
2453
- return record(outer[key]) ?? outer;
2623
+ return record2(outer[key]) ?? outer;
2454
2624
  }
2455
- function record(value2) {
2625
+ function record2(value2) {
2456
2626
  return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
2457
2627
  }
2458
2628
  function textField(value2, max) {
@@ -2465,9 +2635,9 @@ function calendarIds(value2) {
2465
2635
  if (!Array.isArray(value2)) return [];
2466
2636
  return [...new Set(value2.flatMap((item) => {
2467
2637
  if (typeof item === "string") return textField(item, 4096) ? [item] : [];
2468
- const calendar = record(item);
2469
- const id = textField(calendar?.id, 4096);
2470
- return id && calendar?.selected !== false ? [id] : [];
2638
+ const calendar = record2(item);
2639
+ const id2 = textField(calendar?.id, 4096);
2640
+ return id2 && calendar?.selected !== false ? [id2] : [];
2471
2641
  }))];
2472
2642
  }
2473
2643
  function timestamp3(value2) {
@@ -2702,6 +2872,7 @@ var CAPABILITIES = {
2702
2872
  "validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
2703
2873
  "save and explicitly select non-secret named operator contexts with isolated credential caches; resolve and explain platform, app, environment, and credential provenance without authenticating; then run PM, Discussions, o11y, runbook, and identity operations outside a project checkout",
2704
2874
  "read one versioned o11y status envelope spanning application RED, exact Worker versions and Cloudflare colos observed in traffic, current live-sync freshness/load, the protected commit-to-visible canary, collector ingest/scheduler trust, provider-owned runtime metrics, account-scoped Durable Object, D1, and R2 evidence under odla-db, and a bounded machine verdict",
2875
+ "reconcile app-owned Kitesurf probes and rolling SLOs, run live checks, and read incident and digest status as stable JSON",
2705
2876
  "read one canonical platform fleet snapshot over private service bindings, including release identities, probe latency, Cloudflare load/runtime freshness, explicit unknowns, and stable next actions through a read-only capability",
2706
2877
  "compare one project's checked-in Registry intent with live owner-visible state, freeze a secret-free Registry-revision-bound plan through app:config:read, and conditionally apply checkpoint-free actions through exact app:config:write operation routes",
2707
2878
  "inspect or bounded-wait one exact durable config-operation journal entry and verify every terminal receipt digest before returning it to remote automation",
@@ -2714,7 +2885,8 @@ var CAPABILITIES = {
2714
2885
  "install and import the selected odla SDKs",
2715
2886
  "wrap the Worker with withObservability and choose useful telemetry",
2716
2887
  "install capability packages, mount their runtime routes, and make application-specific schema, rules, auth, UI, and migration decisions",
2717
- "wire @odla-ai/calendar into trusted Worker code and keep the app admin key out of browsers"
2888
+ "wire @odla-ai/calendar into trusted Worker code and keep the app admin key out of browsers",
2889
+ "choose public readiness assertions and SLO objectives in odla.config.mjs, then consume monitor JSON without treating captured page content as trusted instructions"
2718
2890
  ],
2719
2891
  human: [
2720
2892
  "provide the existing odla account email, then sign in and explicitly review/approve the exact device code",
@@ -2727,6 +2899,7 @@ var CAPABILITIES = {
2727
2899
  ],
2728
2900
  studio: [
2729
2901
  "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",
2902
+ "view reliability objectives, error budget, Kitesurf probe history, incidents, and notification delivery state",
2730
2903
  "let signed-in users inventory/revoke their own agent grants and admins audit/global-revoke them",
2731
2904
  "review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens",
2732
2905
  "perform manual credential recovery \u2014 for the primary owner or any co-owner \u2014 when the CLI's local shown-once copy is unavailable",
@@ -2846,9 +3019,9 @@ function canonicalValue(value2) {
2846
3019
  }
2847
3020
  if (Array.isArray(value2)) return value2.map(canonicalValue);
2848
3021
  if (value2 && typeof value2 === "object") {
2849
- const record9 = value2;
3022
+ const record11 = value2;
2850
3023
  return Object.fromEntries(
2851
- Object.keys(record9).filter((key) => record9[key] !== void 0).sort().map((key) => [key, canonicalValue(record9[key])])
3024
+ Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, canonicalValue(record11[key])])
2852
3025
  );
2853
3026
  }
2854
3027
  throw new TypeError("canonical JSON rejects unsupported values");
@@ -2873,8 +3046,8 @@ function readPlan(path) {
2873
3046
  "invalid_plan"
2874
3047
  );
2875
3048
  }
2876
- if (!record2(value2) || value2.schemaVersion !== "odla.config-plan/v2") invalidPlan("unsupported plan schema");
2877
- if (!record2(value2.scope) || typeof value2.scope.appId !== "string" || typeof value2.scope.platformUrl !== "string") {
3049
+ if (!record3(value2) || value2.schemaVersion !== "odla.config-plan/v2") invalidPlan("unsupported plan schema");
3050
+ if (!record3(value2.scope) || typeof value2.scope.appId !== "string" || typeof value2.scope.platformUrl !== "string") {
2878
3051
  invalidPlan("plan scope is invalid");
2879
3052
  }
2880
3053
  if (!DIGEST.test(String(value2.desiredRevision)) || !DIGEST.test(String(value2.observedRevision))) {
@@ -2924,10 +3097,10 @@ function assertOperationId(value2) {
2924
3097
  function assertActions(actions) {
2925
3098
  const ids = /* @__PURE__ */ new Set();
2926
3099
  for (const action2 of actions) {
2927
- if (!record2(action2)) invalidPlan("every plan action must be an object");
2928
- const id = String(action2.id ?? "");
2929
- if (!ACTION_ID.test(id) || ids.has(id)) invalidPlan("plan action ids must be unique frozen ids");
2930
- ids.add(id);
3100
+ if (!record3(action2)) invalidPlan("every plan action must be an object");
3101
+ const id2 = String(action2.id ?? "");
3102
+ if (!ACTION_ID.test(id2) || ids.has(id2)) invalidPlan("plan action ids must be unique frozen ids");
3103
+ ids.add(id2);
2931
3104
  if (typeof action2.path !== "string" || typeof action2.reason !== "string" || !action2.reason || action2.reason.length > 500 || typeof action2.requiresApproval !== "boolean" || !["low", "medium", "high"].includes(String(action2.risk)) || !["provision", "command", "studio"].includes(String(action2.applySupport))) {
2932
3105
  invalidPlan("plan action metadata is invalid");
2933
3106
  }
@@ -2953,14 +3126,14 @@ function assertConditionalAction(action2) {
2953
3126
  if (action2.path !== (action2.kind === "configure_service" ? `${base}.config` : base)) {
2954
3127
  invalidPlan("service action path is invalid");
2955
3128
  }
2956
- if (action2.applySupport !== "provision" || !record2(action2.after)) {
3129
+ if (action2.applySupport !== "provision" || !record3(action2.after)) {
2957
3130
  invalidPlan("service action payload is invalid");
2958
3131
  }
2959
3132
  if (action2.kind === "enable_service") {
2960
- if (action2.after.enabled !== true || action2.before !== null && !record2(action2.before)) {
3133
+ if (action2.after.enabled !== true || action2.before !== null && !record3(action2.before)) {
2961
3134
  invalidPlan("service enable action is invalid");
2962
3135
  }
2963
- } else if (!record2(action2.before)) {
3136
+ } else if (!record3(action2.before)) {
2964
3137
  invalidPlan("service configure action is invalid");
2965
3138
  }
2966
3139
  }
@@ -2977,7 +3150,7 @@ function linkState(value2) {
2977
3150
  function invalidPlan(message2) {
2978
3151
  throw new ConfigOperationCommandError(message2, "invalid_plan");
2979
3152
  }
2980
- function record2(value2) {
3153
+ function record3(value2) {
2981
3154
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
2982
3155
  }
2983
3156
 
@@ -3016,9 +3189,9 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
3016
3189
  }
3017
3190
  throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
3018
3191
  }
3019
- function errorCode(text2) {
3192
+ function errorCode(text3) {
3020
3193
  try {
3021
- const body = JSON.parse(text2);
3194
+ const body = JSON.parse(text3);
3022
3195
  return typeof body.error?.code === "string" ? body.error.code : null;
3023
3196
  } catch {
3024
3197
  return null;
@@ -3161,7 +3334,7 @@ async function configApply(options) {
3161
3334
  throw new ConfigOperationCommandError("--idempotency-key is not a safe 1-120 character key", "invalid_plan");
3162
3335
  }
3163
3336
  const client = await operationClient(cfg, options, "apply");
3164
- const request2 = {
3337
+ const request3 = {
3165
3338
  schemaVersion: "odla.config-operation-request/v1",
3166
3339
  expectedRevision: plan.registryRevision,
3167
3340
  desiredRevision: plan.desiredRevision,
@@ -3173,7 +3346,7 @@ async function configApply(options) {
3173
3346
  };
3174
3347
  let receipt;
3175
3348
  try {
3176
- receipt = await client.applyConfigOperation(cfg.app.id, request2);
3349
+ receipt = await client.applyConfigOperation(cfg.app.id, request3);
3177
3350
  } catch (error) {
3178
3351
  const retained = retainedReceipt(error);
3179
3352
  if (retained) {
@@ -3272,8 +3445,8 @@ function failureForReceipt(receipt) {
3272
3445
  return new ConfigOperationCommandError(receipt.error?.message ?? `config operation ${receipt.state}`, "config_operation_failed");
3273
3446
  }
3274
3447
  function retainedReceipt(error) {
3275
- if (!(error instanceof AppsError) || !record3(error.details)) return null;
3276
- return record3(error.details.operation) ? error.details.operation : null;
3448
+ if (!(error instanceof AppsError) || !record4(error.details)) return null;
3449
+ return record4(error.details.operation) ? error.details.operation : null;
3277
3450
  }
3278
3451
  function normalizeRequestError(error) {
3279
3452
  if (!(error instanceof AppsError)) return error instanceof Error ? error : new Error(String(error));
@@ -3286,7 +3459,7 @@ function normalizeRequestError(error) {
3286
3459
  }
3287
3460
  return new ConfigOperationCommandError(error.message, error.code || "config_operation_failed");
3288
3461
  }
3289
- function record3(value2) {
3462
+ function record4(value2) {
3290
3463
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
3291
3464
  }
3292
3465
 
@@ -3753,15 +3926,15 @@ function readWranglerConfig(path) {
3753
3926
  return null;
3754
3927
  }
3755
3928
  }
3756
- function stripJsonComments(text2) {
3929
+ function stripJsonComments(text3) {
3757
3930
  let result = "";
3758
3931
  let inString = false;
3759
- for (let i = 0; i < text2.length; i++) {
3760
- const ch = text2[i];
3932
+ for (let i = 0; i < text3.length; i++) {
3933
+ const ch = text3[i];
3761
3934
  if (inString) {
3762
3935
  result += ch;
3763
3936
  if (ch === "\\") {
3764
- result += text2[i + 1] ?? "";
3937
+ result += text3[i + 1] ?? "";
3765
3938
  i++;
3766
3939
  } else if (ch === '"') {
3767
3940
  inString = false;
@@ -3773,14 +3946,14 @@ function stripJsonComments(text2) {
3773
3946
  result += ch;
3774
3947
  continue;
3775
3948
  }
3776
- if (ch === "/" && text2[i + 1] === "/") {
3777
- while (i < text2.length && text2[i] !== "\n") i++;
3949
+ if (ch === "/" && text3[i + 1] === "/") {
3950
+ while (i < text3.length && text3[i] !== "\n") i++;
3778
3951
  result += "\n";
3779
3952
  continue;
3780
3953
  }
3781
- if (ch === "/" && text2[i + 1] === "*") {
3954
+ if (ch === "/" && text3[i + 1] === "*") {
3782
3955
  i += 2;
3783
- while (i < text2.length && !(text2[i] === "*" && text2[i + 1] === "/")) i++;
3956
+ while (i < text3.length && !(text3[i] === "*" && text3[i + 1] === "/")) i++;
3784
3957
  i++;
3785
3958
  continue;
3786
3959
  }
@@ -3819,7 +3992,7 @@ async function wranglerRuntimeTarget(run, opts) {
3819
3992
  throw new Error(`wrangler is not logged in \u2014 run "wrangler login" (a browser step for the human)`);
3820
3993
  }
3821
3994
  const discovered = [...new Set(`${whoami.stdout}
3822
- ${whoami.stderr}`.match(/\b[a-f0-9]{32}\b/gi)?.map((id) => id.toLowerCase()) ?? [])];
3995
+ ${whoami.stderr}`.match(/\b[a-f0-9]{32}\b/gi)?.map((id2) => id2.toLowerCase()) ?? [])];
3823
3996
  const accountId = configuredAccount.toLowerCase() || (discovered.length === 1 ? discovered[0] : "");
3824
3997
  if (!/^[a-f0-9]{32}$/.test(accountId) || configuredAccount && discovered.length > 0 && !discovered.includes(accountId)) {
3825
3998
  throw new Error("Wrangler account is ambiguous or does not match account_id in the config");
@@ -4294,9 +4467,9 @@ function initProject(options) {
4294
4467
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
4295
4468
  out.log("updated .gitignore for local odla credentials");
4296
4469
  }
4297
- function writeIfMissing(path, text2) {
4470
+ function writeIfMissing(path, text3) {
4298
4471
  if (existsSync8(path)) return;
4299
- writeFileSync2(path, text2);
4472
+ writeFileSync2(path, text3);
4300
4473
  }
4301
4474
  function configTemplate(input) {
4302
4475
  const calendar = input.services.includes("calendar") ? ` calendar: {
@@ -4506,13 +4679,13 @@ async function secretsSetClerkKey(options) {
4506
4679
  body: JSON.stringify({ value: value2 })
4507
4680
  });
4508
4681
  if (!res.ok) {
4509
- const text2 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
4510
- throw new Error(`store Clerk secret key failed (${res.status}): ${text2 || "request failed"}`);
4682
+ const text3 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
4683
+ throw new Error(`store Clerk secret key failed (${res.status}): ${text3 || "request failed"}`);
4511
4684
  }
4512
4685
  out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
4513
4686
  }
4514
- function scrubValue(text2, value2) {
4515
- return redactSecrets(text2).split(value2).join("[value redacted]");
4687
+ function scrubValue(text3, value2) {
4688
+ return redactSecrets(text3).split(value2).join("[value redacted]");
4516
4689
  }
4517
4690
  async function resolveVaultWrite(options) {
4518
4691
  const out = options.stdout ?? console;
@@ -4646,8 +4819,8 @@ alwaysApply: false
4646
4819
 
4647
4820
  ${PROJECT_INSTRUCTIONS}
4648
4821
  `;
4649
- function claudeAdapter(skill, canonical) {
4650
- const match = canonical.match(/^---\r?\n([\s\S]*?)\r?\n---/);
4822
+ function claudeAdapter(skill, canonical2) {
4823
+ const match = canonical2.match(/^---\r?\n([\s\S]*?)\r?\n---/);
4651
4824
  if (!match) throw new Error(`bundled skill ${skill} has no YAML frontmatter`);
4652
4825
  const lines = match[1].split(/\r?\n/);
4653
4826
  const frontmatter = [];
@@ -4717,8 +4890,8 @@ function installSkill(options = {}) {
4717
4890
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
4718
4891
  if (harnesses.includes("claude")) {
4719
4892
  for (const skill of skillNames(files)) {
4720
- const canonical = readFileSync8(join9(sourceDir, skill, "SKILL.md"), "utf8");
4721
- plan(join9(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
4893
+ const canonical2 = readFileSync8(join9(sourceDir, skill, "SKILL.md"), "utf8");
4894
+ plan(join9(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical2));
4722
4895
  }
4723
4896
  rememberTarget("claude", claudeRoot);
4724
4897
  }
@@ -4994,7 +5167,7 @@ function assertCalendarHealthy(status, expected) {
4994
5167
  if (!status.writable) throw new Error('calendar grant does not cover booking writes; run "odla-ai calendar connect" to re-consent');
4995
5168
  const hasEventsScope = status.grantedScopes.some((scope) => scope === GOOGLE_CALENDAR_EVENTS_SCOPE);
4996
5169
  if (!hasEventsScope) throw new Error("calendar connection is missing calendar.events consent");
4997
- const missing = expected.availabilityCalendars.filter((id) => !status.calendars.includes(id));
5170
+ const missing = expected.availabilityCalendars.filter((id2) => !status.calendars.includes(id2));
4998
5171
  if (missing.length) throw new Error(`calendar connection is missing configured calendars: ${missing.join(", ")}`);
4999
5172
  }
5000
5173
  async function getJson(doFetch, url, bearer) {
@@ -5394,8 +5567,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
5394
5567
  const maxFiles = options.maxFiles ?? 2e4;
5395
5568
  const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
5396
5569
  const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
5397
- const entries = inventory.flatMap((record9) => {
5398
- const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record9);
5570
+ const entries = inventory.flatMap((record11) => {
5571
+ const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record11);
5399
5572
  return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
5400
5573
  });
5401
5574
  if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
@@ -5643,8 +5816,8 @@ function normalize(value2) {
5643
5816
  if (Array.isArray(value2)) return value2.map(normalize);
5644
5817
  if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
5645
5818
  if (typeof value2 === "object") {
5646
- const record9 = value2;
5647
- return Object.fromEntries(Object.keys(record9).filter((key) => record9[key] !== void 0).sort().map((key) => [key, normalize(record9[key])]));
5819
+ const record11 = value2;
5820
+ return Object.fromEntries(Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, normalize(record11[key])]));
5648
5821
  }
5649
5822
  throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
5650
5823
  }
@@ -5718,7 +5891,7 @@ function copyRef(ref) {
5718
5891
  function normalizeReaders(readers) {
5719
5892
  if (readers.kind === "public") return Object.freeze({ kind: "public" });
5720
5893
  const principalIds = [...new Set(readers.principalIds)].sort();
5721
- if (principalIds.some((id) => !id)) throw new CamelError("reader_mismatch", "Reader principal IDs must be non-empty.");
5894
+ if (principalIds.some((id2) => !id2)) throw new CamelError("reader_mismatch", "Reader principal IDs must be non-empty.");
5722
5895
  return Object.freeze({ kind: "principals", principalIds: Object.freeze(principalIds) });
5723
5896
  }
5724
5897
 
@@ -5728,7 +5901,7 @@ var SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
5728
5901
  var ID = /^[A-Za-z0-9._:-]{1,160}$/;
5729
5902
  async function digestCodeVerificationReceipt(fields) {
5730
5903
  validate(fields);
5731
- const canonical = {
5904
+ const canonical2 = {
5732
5905
  schemaVersion: fields.schemaVersion,
5733
5906
  verificationId: fields.verificationId,
5734
5907
  trustedBaseCommitSha: fields.trustedBaseCommitSha,
@@ -5755,7 +5928,7 @@ async function digestCodeVerificationReceipt(fields) {
5755
5928
  changedTestsRequireReview: fields.changedTestsRequireReview,
5756
5929
  outcome: fields.outcome
5757
5930
  };
5758
- return `sha256:${await sha256Hex(canonicalJson2(canonical))}`;
5931
+ return `sha256:${await sha256Hex(canonicalJson2(canonical2))}`;
5759
5932
  }
5760
5933
  function validate(fields) {
5761
5934
  if (fields.schemaVersion !== 1 || typeof fields.verificationId !== "string" || !ID.test(fields.verificationId) || typeof fields.trustedBaseCommitSha !== "string" || !SHA.test(fields.trustedBaseCommitSha) || !DIGEST2.test(fields.trustedBaseDigest) || !DIGEST2.test(fields.patchDigest) || !DIGEST2.test(fields.candidateDigest) || !DIGEST2.test(fields.sourceDigest) || !DIGEST2.test(fields.policyDigest) || !DIGEST2.test(fields.changedTestSetDigest) || !Number.isSafeInteger(fields.changedTestCount) || fields.changedTestCount < 0 || fields.changedTestCount > 1e4 || fields.changedTestsRequireReview !== fields.changedTestCount > 0 || fields.recipes.length < 1 || fields.recipes.length > 64) {
@@ -5973,7 +6146,7 @@ async function createConversionRegistry(config) {
5973
6146
  if (await conversionPolicyDigest(definition) !== policy.digest) throw new CamelError("state_conflict", "Conversion policy digest mismatch.");
5974
6147
  if (policy.output.kind === "registered_id") {
5975
6148
  const registry = config.registeredIds?.[policy.output.registryId];
5976
- const validValues = registry && Object.entries(registry.values).every(([candidate, id]) => candidate.length > 0 && typeof id === "string" && id.length > 0);
6149
+ const validValues = registry && Object.entries(registry.values).every(([candidate, id2]) => candidate.length > 0 && typeof id2 === "string" && id2.length > 0);
5977
6150
  if (!registry || !validValues || registry.digest !== policy.output.registryDigest || await registeredIdRegistryDigest(registry.values) !== registry.digest) {
5978
6151
  throw new CamelError("state_conflict", "Registered-ID registry digest mismatch.");
5979
6152
  }
@@ -5981,63 +6154,63 @@ async function createConversionRegistry(config) {
5981
6154
  policies.set(policy.conversionId, Object.freeze(policy));
5982
6155
  }
5983
6156
  const outputCounts = /* @__PURE__ */ new Map();
5984
- const get = (id, kind) => {
5985
- const policy = policies.get(id);
6157
+ const get = (id2, kind) => {
6158
+ const policy = policies.get(id2);
5986
6159
  if (!policy || policy.output.kind !== kind) throw new CamelError("conversion_rejected", "Conversion policy is missing or has the wrong output kind.");
5987
6160
  return policy;
5988
6161
  };
5989
- const checked = (source, id, kind) => {
5990
- const policy = get(id, kind);
6162
+ const checked = (source, id2, kind) => {
6163
+ const policy = get(id2, kind);
5991
6164
  if (utf8Length(source.value) > policy.maximumSourceBytes) throw new CamelError("limit_exceeded", "Unsafe conversion input exceeds its byte bound.");
5992
6165
  return policy;
5993
6166
  };
5994
- const emit3 = (source, policy, value2) => convert(source, policy, value2, outputCounts);
6167
+ const emit4 = (source, policy, value2) => convert(source, policy, value2, outputCounts);
5995
6168
  const operations = Object.freeze({
5996
- boolean: async (value2, id) => {
5997
- const policy = checked(value2, id, "boolean");
5998
- return emit3(value2, policy, requireBoolean(value2.value));
6169
+ boolean: async (value2, id2) => {
6170
+ const policy = checked(value2, id2, "boolean");
6171
+ return emit4(value2, policy, requireBoolean(value2.value));
5999
6172
  },
6000
- integer: async (value2, id) => {
6001
- const policy = checked(value2, id, "integer");
6002
- return emit3(value2, policy, boundedInteger(value2.value, policy.output));
6173
+ integer: async (value2, id2) => {
6174
+ const policy = checked(value2, id2, "integer");
6175
+ return emit4(value2, policy, boundedInteger(value2.value, policy.output));
6003
6176
  },
6004
- finiteNumber: async (value2, id) => {
6005
- const policy = checked(value2, id, "finite_number");
6006
- return emit3(value2, policy, boundedNumber(value2.value, policy.output));
6177
+ finiteNumber: async (value2, id2) => {
6178
+ const policy = checked(value2, id2, "finite_number");
6179
+ return emit4(value2, policy, boundedNumber(value2.value, policy.output));
6007
6180
  },
6008
- enum: async (value2, id) => {
6009
- const policy = checked(value2, id, "enum");
6010
- return emit3(value2, policy, enumMember(value2.value, policy.output));
6181
+ enum: async (value2, id2) => {
6182
+ const policy = checked(value2, id2, "enum");
6183
+ return emit4(value2, policy, enumMember(value2.value, policy.output));
6011
6184
  },
6012
- date: async (value2, id) => {
6013
- const policy = checked(value2, id, "date");
6014
- return emit3(value2, policy, canonicalDate(value2.value, policy.output));
6185
+ date: async (value2, id2) => {
6186
+ const policy = checked(value2, id2, "date");
6187
+ return emit4(value2, policy, canonicalDate(value2.value, policy.output));
6015
6188
  },
6016
- registeredId: async (value2, id) => {
6017
- const policy = checked(value2, id, "registered_id");
6189
+ registeredId: async (value2, id2) => {
6190
+ const policy = checked(value2, id2, "registered_id");
6018
6191
  const registry = config.registeredIds?.[policy.output.registryId];
6019
6192
  const output = typeof value2.value === "string" ? registry?.values[value2.value] : void 0;
6020
6193
  if (!output) throw new CamelError("conversion_rejected", "Registered-ID conversion rejected the candidate.");
6021
- return emit3(value2, policy, output);
6194
+ return emit4(value2, policy, output);
6022
6195
  },
6023
- digest: async (value2, id) => {
6024
- const policy = checked(value2, id, "digest");
6196
+ digest: async (value2, id2) => {
6197
+ const policy = checked(value2, id2, "digest");
6025
6198
  if (!(value2.value instanceof Uint8Array)) throw new CamelError("conversion_rejected", "Digest conversion requires bytes.");
6026
- return emit3(value2, policy, await sha256Hex(value2.value));
6199
+ return emit4(value2, policy, await sha256Hex(value2.value));
6027
6200
  },
6028
- measure: async (value2, metric, id) => {
6029
- const policy = checked(value2, id, "integer");
6201
+ measure: async (value2, metric, id2) => {
6202
+ const policy = checked(value2, id2, "integer");
6030
6203
  const measured = measure(value2.value, metric);
6031
- return emit3(value2, policy, boundedInteger(measured, policy.output));
6204
+ return emit4(value2, policy, boundedInteger(measured, policy.output));
6032
6205
  },
6033
- test: async (value2, predicateId, id) => {
6034
- const policy = checked(value2, id, "boolean");
6206
+ test: async (value2, predicateId, id2) => {
6207
+ const policy = checked(value2, id2, "boolean");
6035
6208
  const predicate = config.predicates?.[predicateId];
6036
6209
  if (!predicate) throw new CamelError("conversion_rejected", "Predicate is not registered.");
6037
- return emit3(value2, policy, evaluatePredicate(value2.value, predicate, config.registeredIds));
6210
+ return emit4(value2, policy, evaluatePredicate(value2.value, predicate, config.registeredIds));
6038
6211
  }
6039
6212
  });
6040
- return Object.freeze({ operations, policy: (id) => policies.get(id) ?? missingPolicy() });
6213
+ return Object.freeze({ operations, policy: (id2) => policies.get(id2) ?? missingPolicy() });
6041
6214
  }
6042
6215
  async function convert(source, policy, value2, counts) {
6043
6216
  const sourceKey = sourceIdentity(source);
@@ -6080,8 +6253,8 @@ function boundedInteger(value2, spec) {
6080
6253
  }
6081
6254
  function boundedNumber(value2, spec) {
6082
6255
  if (spec.kind !== "finite_number" || typeof value2 !== "number" || !Number.isFinite(value2) || value2 < spec.minimum || value2 > spec.maximum) throw new CamelError("conversion_rejected", "Finite-number conversion rejected the structured value.");
6083
- const text2 = String(value2);
6084
- if (/e/i.test(text2) || (text2.split(".")[1]?.length ?? 0) > spec.maximumDecimalPlaces) throw new CamelError("conversion_rejected", "Finite-number conversion rejected a non-canonical decimal.");
6256
+ const text3 = String(value2);
6257
+ if (/e/i.test(text3) || (text3.split(".")[1]?.length ?? 0) > spec.maximumDecimalPlaces) throw new CamelError("conversion_rejected", "Finite-number conversion rejected a non-canonical decimal.");
6085
6258
  return value2;
6086
6259
  }
6087
6260
  function enumMember(value2, spec) {
@@ -6132,10 +6305,10 @@ function createCamelIngress(constants2 = []) {
6132
6305
  const ingress = {
6133
6306
  userInstruction: (value2, input) => createSafeInternal(value2, "user_instruction", metadata("user_instruction", input.id, input.readers)),
6134
6307
  systemPolicy: (value2, input) => createSafeInternal(value2, "system_policy", metadata("system_policy", input.id, input.readers)),
6135
- control: (id) => {
6136
- const item = byId.get(id);
6308
+ control: (id2) => {
6309
+ const item = byId.get(id2);
6137
6310
  if (!item) throw new CamelError("permission_denied", "Unknown control constant.");
6138
- return createSafeInternal(item.value, "harness_constant", metadata("harness", id, item.readers));
6311
+ return createSafeInternal(item.value, "harness_constant", metadata("harness", id2, item.readers));
6139
6312
  },
6140
6313
  external: (value2, label) => createUnsafeInternal(value2, label),
6141
6314
  quarantinedOutput: (value2, input) => {
@@ -6159,9 +6332,9 @@ function assertNoUnsafeConstant(value2, seen = /* @__PURE__ */ new WeakSet()) {
6159
6332
  }
6160
6333
  for (const child of Object.values(value2)) assertNoUnsafeConstant(child, seen);
6161
6334
  }
6162
- function metadata(kind, id, readers) {
6163
- if (!id) throw new CamelError("state_conflict", "Provenance IDs must be non-empty.");
6164
- return { readers, provenance: [{ kind, id }] };
6335
+ function metadata(kind, id2, readers) {
6336
+ if (!id2) throw new CamelError("state_conflict", "Provenance IDs must be non-empty.");
6337
+ return { readers, provenance: [{ kind, id: id2 }] };
6165
6338
  }
6166
6339
 
6167
6340
  // ../camel/dist/policy.js
@@ -6225,7 +6398,7 @@ function isControlOwned(value2) {
6225
6398
  return value2.label.safeBasis === "system_policy" || value2.label.safeBasis === "harness_constant";
6226
6399
  }
6227
6400
  function copyRegistries(registries) {
6228
- return Object.freeze(Object.fromEntries(Object.entries(registries).map(([id, registry]) => [id, Object.freeze({ digest: registry.digest, values: Object.freeze([...registry.values]) })])));
6401
+ return Object.freeze(Object.fromEntries(Object.entries(registries).map(([id2, registry]) => [id2, Object.freeze({ digest: registry.digest, values: Object.freeze([...registry.values]) })])));
6229
6402
  }
6230
6403
  function validateUnsafeSelector(path, value2, tool) {
6231
6404
  const policy = tool.unsafeSelectorPolicy;
@@ -6237,8 +6410,8 @@ function validateUnsafeSelector(path, value2, tool) {
6237
6410
  return void 0;
6238
6411
  }
6239
6412
  function looksLikeDestination(value2) {
6240
- const text2 = value2.trim();
6241
- return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text2) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text2);
6413
+ const text3 = value2.trim();
6414
+ return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text3) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text3);
6242
6415
  }
6243
6416
 
6244
6417
  // ../harness/dist/chunk-ANNX7VGK.js
@@ -6248,9 +6421,9 @@ import { join as join33 } from "path";
6248
6421
 
6249
6422
  // ../graph/dist/chunk-PS2SO4UP.js
6250
6423
  var nodeId = (kind, name) => `${kind}:${name}`;
6251
- function parseNodeId(id) {
6252
- const at = id.indexOf(":");
6253
- return at < 0 ? { kind: "", name: id } : { kind: id.slice(0, at), name: id.slice(at + 1) };
6424
+ function parseNodeId(id2) {
6425
+ const at = id2.indexOf(":");
6426
+ return at < 0 ? { kind: "", name: id2 } : { kind: id2.slice(0, at), name: id2.slice(at + 1) };
6254
6427
  }
6255
6428
  var GraphBuilder = class {
6256
6429
  byId = /* @__PURE__ */ new Map();
@@ -6258,14 +6431,14 @@ var GraphBuilder = class {
6258
6431
  seen = /* @__PURE__ */ new Set();
6259
6432
  /** Add or enrich a node. Later attributes win; the kind never changes. */
6260
6433
  node(kind, name, attrs) {
6261
- const id = nodeId(kind, name);
6262
- const existing = this.byId.get(id);
6434
+ const id2 = nodeId(kind, name);
6435
+ const existing = this.byId.get(id2);
6263
6436
  if (existing) {
6264
- if (attrs) this.byId.set(id, { ...existing, attrs: { ...existing.attrs, ...attrs } });
6265
- return id;
6437
+ if (attrs) this.byId.set(id2, { ...existing, attrs: { ...existing.attrs, ...attrs } });
6438
+ return id2;
6266
6439
  }
6267
- this.byId.set(id, { id, kind, name, ...attrs ? { attrs } : {} });
6268
- return id;
6440
+ this.byId.set(id2, { id: id2, kind, name, ...attrs ? { attrs } : {} });
6441
+ return id2;
6269
6442
  }
6270
6443
  /**
6271
6444
  * Add a directed edge, minting either endpoint if it is not known yet.
@@ -6275,10 +6448,10 @@ var GraphBuilder = class {
6275
6448
  * by how often someone repeated an import.
6276
6449
  */
6277
6450
  edge(from, kind, to, attrs) {
6278
- for (const id of [from, to]) {
6279
- if (!this.byId.has(id)) {
6280
- const parsed = parseNodeId(id);
6281
- this.byId.set(id, { id, kind: parsed.kind, name: parsed.name });
6451
+ for (const id2 of [from, to]) {
6452
+ if (!this.byId.has(id2)) {
6453
+ const parsed = parseNodeId(id2);
6454
+ this.byId.set(id2, { id: id2, kind: parsed.kind, name: parsed.name });
6282
6455
  }
6283
6456
  }
6284
6457
  const key = `${from} ${kind} ${to}`;
@@ -6311,18 +6484,18 @@ function nodesOfKind(graph, kind) {
6311
6484
 
6312
6485
  // ../graph/dist/index.js
6313
6486
  var follows = (kinds, edge) => !kinds || kinds.includes(edge.kind);
6314
- function incident(graph, id, traversal = {}) {
6487
+ function incident(graph, id2, traversal = {}) {
6315
6488
  const direction = traversal.direction ?? "out";
6316
- const forward = direction === "out" || direction === "both" ? graph.out.get(id) ?? [] : [];
6317
- const backward = direction === "in" || direction === "both" ? graph.in.get(id) ?? [] : [];
6489
+ const forward = direction === "out" || direction === "both" ? graph.out.get(id2) ?? [] : [];
6490
+ const backward = direction === "in" || direction === "both" ? graph.in.get(id2) ?? [] : [];
6318
6491
  return [...forward, ...backward].filter((edge) => follows(traversal.kinds, edge));
6319
6492
  }
6320
6493
  var otherEnd = (edge, from) => edge.from === from ? edge.to : edge.from;
6321
- function neighbors(graph, id, traversal = {}) {
6494
+ function neighbors(graph, id2, traversal = {}) {
6322
6495
  const seen = /* @__PURE__ */ new Set();
6323
- for (const edge of incident(graph, id, traversal)) {
6324
- const other = otherEnd(edge, id);
6325
- if (other !== id) seen.add(other);
6496
+ for (const edge of incident(graph, id2, traversal)) {
6497
+ const other = otherEnd(edge, id2);
6498
+ if (other !== id2) seen.add(other);
6326
6499
  }
6327
6500
  return [...seen];
6328
6501
  }
@@ -6406,9 +6579,9 @@ async function extractImports(builder, input) {
6406
6579
  const sources = input.paths.filter(isSourcePath);
6407
6580
  const known = new Set(sources);
6408
6581
  for (const path of sources) {
6409
- let text2;
6582
+ let text3;
6410
6583
  try {
6411
- text2 = await input.read(path);
6584
+ text3 = await input.read(path);
6412
6585
  } catch {
6413
6586
  continue;
6414
6587
  }
@@ -6416,13 +6589,13 @@ async function extractImports(builder, input) {
6416
6589
  const file = builder.node(FILE, path, pkg ? { pkg } : void 0);
6417
6590
  if (pkg) builder.edge(builder.node(PACKAGE, pkg), CONTAINS, file);
6418
6591
  const specifiers = /* @__PURE__ */ new Set();
6419
- for (const match of text2.matchAll(IMPORT_FROM)) specifiers.add(match[1]);
6420
- for (const match of text2.matchAll(BARE_IMPORT)) specifiers.add(match[1]);
6592
+ for (const match of text3.matchAll(IMPORT_FROM)) specifiers.add(match[1]);
6593
+ for (const match of text3.matchAll(BARE_IMPORT)) specifiers.add(match[1]);
6421
6594
  for (const specifier of specifiers) {
6422
6595
  const resolved = resolveImport(path, specifier, known);
6423
6596
  if (resolved) builder.edge(file, IMPORTS, nodeId(FILE, resolved));
6424
6597
  }
6425
- for (const name of exportedNames(text2)) {
6598
+ for (const name of exportedNames(text3)) {
6426
6599
  builder.edge(file, EXPORTS, builder.node(SYMBOL, name));
6427
6600
  }
6428
6601
  }
@@ -6463,16 +6636,16 @@ async function extractData(builder, input) {
6463
6636
  };
6464
6637
  for (const path of input.paths) {
6465
6638
  if (!SOURCE_FILE.test(path) || input.ignore?.(path)) continue;
6466
- let text2;
6639
+ let text3;
6467
6640
  try {
6468
- text2 = await input.read(path);
6641
+ text3 = await input.read(path);
6469
6642
  } catch {
6470
6643
  continue;
6471
6644
  }
6472
- for (const statement of text2.matchAll(STATEMENT)) {
6645
+ for (const statement of text3.matchAll(STATEMENT)) {
6473
6646
  const verb = statement[1].toUpperCase().replace(/\s+/g, " ");
6474
6647
  const start = statement.index ?? 0;
6475
- const rest = text2.slice(start + statement[0].length, start + STATEMENT_WINDOW);
6648
+ const rest = text3.slice(start + statement[0].length, start + STATEMENT_WINDOW);
6476
6649
  if (verb === "SELECT") {
6477
6650
  for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
6478
6651
  continue;
@@ -6488,16 +6661,16 @@ async function extractData(builder, input) {
6488
6661
  for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
6489
6662
  }
6490
6663
  }
6491
- for (const match of text2.matchAll(NS_CONST)) {
6492
- touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(text2, match.index ?? 0));
6664
+ for (const match of text3.matchAll(NS_CONST)) {
6665
+ touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(text3, match.index ?? 0));
6493
6666
  }
6494
- for (const match of text2.matchAll(NS_LITERAL)) {
6495
- touch(path, match[1], NAMESPACE, accessFor(text2, match.index ?? 0));
6667
+ for (const match of text3.matchAll(NS_LITERAL)) {
6668
+ touch(path, match[1], NAMESPACE, accessFor(text3, match.index ?? 0));
6496
6669
  }
6497
6670
  }
6498
6671
  }
6499
- function accessFor(text2, index) {
6500
- const window = text2.slice(Math.max(0, index - 160), index + 40);
6672
+ function accessFor(text3, index) {
6673
+ const window = text3.slice(Math.max(0, index - 160), index + 40);
6501
6674
  return /\b(?:transact|update|delete|create|insert|Ops)\b/.test(window) ? WRITES : READS;
6502
6675
  }
6503
6676
  async function buildCodeGraph(input) {
@@ -6628,13 +6801,13 @@ function createCodeRuntimeControlClient(options) {
6628
6801
  if (!Number.isSafeInteger(modelRequestTimeoutMs) || modelRequestTimeoutMs < 3e4 || modelRequestTimeoutMs > 30 * 6e4) {
6629
6802
  throw new TypeError("modelRequestTimeoutMs must be an integer from 30000 to 1800000");
6630
6803
  }
6631
- const request2 = options.fetch ?? fetch;
6804
+ const request3 = options.fetch ?? fetch;
6632
6805
  const call2 = async (path, body, timeoutMs = requestTimeoutMs) => {
6633
6806
  const timeout = AbortSignal.timeout(timeoutMs);
6634
6807
  const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
6635
6808
  let response2;
6636
6809
  try {
6637
- response2 = await request2(`${endpoint}${path}`, {
6810
+ response2 = await request3(`${endpoint}${path}`, {
6638
6811
  method: "POST",
6639
6812
  headers: { authorization: `Bearer ${options.token}`, "content-type": "application/json" },
6640
6813
  body: JSON.stringify(body),
@@ -6647,7 +6820,7 @@ function createCodeRuntimeControlClient(options) {
6647
6820
  }
6648
6821
  const value2 = await response2.json().catch(() => null);
6649
6822
  if (!response2.ok) {
6650
- const problem = record4(record4(value2)?.error);
6823
+ const problem = record5(record5(value2)?.error);
6651
6824
  throw new CodeRuntimeControlError(
6652
6825
  typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
6653
6826
  response2.status,
@@ -6669,12 +6842,12 @@ function createCodeRuntimeControlClient(options) {
6669
6842
  await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
6670
6843
  ),
6671
6844
  infer: async (sessionId, inference) => {
6672
- const value2 = record4(await call2(
6845
+ const value2 = record5(await call2(
6673
6846
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
6674
6847
  inference,
6675
6848
  modelRequestTimeoutMs
6676
6849
  ));
6677
- if (!value2 || value2.requestId !== inference.requestId || !record4(value2.response) || !record4(value2.receipt)) {
6850
+ if (!value2 || value2.requestId !== inference.requestId || !record5(value2.response) || !record5(value2.receipt)) {
6678
6851
  throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
6679
6852
  }
6680
6853
  return value2;
@@ -6742,12 +6915,12 @@ function validateHeartbeat(version, capabilities) {
6742
6915
  }
6743
6916
  }
6744
6917
  function parseSnapshot(value2) {
6745
- const root = record4(value2);
6746
- const host = record4(root?.host);
6918
+ const root = record5(value2);
6919
+ const host = record5(root?.host);
6747
6920
  if (!host || typeof host.hostId !== "string" || typeof host.runtimeVersion !== "string" || !Number.isSafeInteger(host.lastSeenAt) || host.revokedAt !== null || !Array.isArray(root?.bindings) || root.bindings.length > 1024 || !Array.isArray(root?.commands) || root.commands.length > 64) throw invalid("heartbeat");
6748
6921
  const bindingIds = /* @__PURE__ */ new Set();
6749
6922
  const bindings = root.bindings.map((item) => {
6750
- const binding = record4(item);
6923
+ const binding = record5(item);
6751
6924
  if (!binding || typeof binding.bindingId !== "string" || typeof binding.appId !== "string" || binding.env !== "dev" && binding.env !== "prod" || typeof binding.offerId !== "string" || binding.hostId !== host.hostId || !Number.isSafeInteger(binding.generation) || Number(binding.generation) < 1 || binding.revokedAt !== null || bindingIds.has(binding.bindingId)) {
6752
6925
  throw invalid("binding");
6753
6926
  }
@@ -6757,10 +6930,10 @@ function parseSnapshot(value2) {
6757
6930
  const commandIds = /* @__PURE__ */ new Set();
6758
6931
  const commandSequences = /* @__PURE__ */ new Set();
6759
6932
  const commands = root.commands.map((item) => {
6760
- const command = record4(item);
6933
+ const command = record5(item);
6761
6934
  const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
6762
6935
  const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
6763
- if (!command || typeof command.commandId !== "string" || !/^ccmd_[0-9a-f]{32}$/.test(command.commandId) || typeof command.instanceId !== "string" || typeof command.sessionId !== "string" || !/^csess_[0-9a-f]{32}$/.test(command.sessionId) || typeof command.appId !== "string" || command.env !== "dev" && command.env !== "prod" || command.hostId !== host.hostId || !binding || binding.appId !== command.appId || binding.generation !== command.bindingGeneration || !Number.isSafeInteger(command.sequence) || Number(command.sequence) < 1 || commandIds.has(command.commandId) || commandSequences.has(sequenceKey) || !["start", "prompt", "checkpoint_stop", "resume"].includes(String(command.kind)) || !record4(command.payload) || !Number.isSafeInteger(command.createdAt)) throw invalid("command");
6936
+ if (!command || typeof command.commandId !== "string" || !/^ccmd_[0-9a-f]{32}$/.test(command.commandId) || typeof command.instanceId !== "string" || typeof command.sessionId !== "string" || !/^csess_[0-9a-f]{32}$/.test(command.sessionId) || typeof command.appId !== "string" || command.env !== "dev" && command.env !== "prod" || command.hostId !== host.hostId || !binding || binding.appId !== command.appId || binding.generation !== command.bindingGeneration || !Number.isSafeInteger(command.sequence) || Number(command.sequence) < 1 || commandIds.has(command.commandId) || commandSequences.has(sequenceKey) || !["start", "prompt", "checkpoint_stop", "resume"].includes(String(command.kind)) || !record5(command.payload) || !Number.isSafeInteger(command.createdAt)) throw invalid("command");
6764
6937
  commandIds.add(command.commandId);
6765
6938
  commandSequences.add(sequenceKey);
6766
6939
  return command;
@@ -6768,10 +6941,10 @@ function parseSnapshot(value2) {
6768
6941
  return { host, bindings, commands };
6769
6942
  }
6770
6943
  async function parseSource(value2) {
6771
- const snapshot = record4(record4(value2)?.snapshot);
6944
+ const snapshot = record5(record5(value2)?.snapshot);
6772
6945
  if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
6773
6946
  const files = snapshot.files.map((value22) => {
6774
- const file = record4(value22);
6947
+ const file = record5(value22);
6775
6948
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
6776
6949
  return { path: file.path, content: file.content };
6777
6950
  });
@@ -6780,11 +6953,11 @@ async function parseSource(value2) {
6780
6953
  const aliases = /* @__PURE__ */ new Set();
6781
6954
  const references = [];
6782
6955
  for (const item of referencesValue) {
6783
- const reference = record4(item);
6956
+ const reference = record5(item);
6784
6957
  if (!reference || typeof reference.alias !== "string" || !/^[a-z][a-z0-9-]{0,39}$/.test(reference.alias) || aliases.has(reference.alias) || reference.alias === "primary" || typeof reference.repository !== "string" || typeof reference.commitSha !== "string" || typeof reference.treeDigest !== "string" || !Array.isArray(reference.files)) throw invalid("reference source");
6785
6958
  aliases.add(reference.alias);
6786
6959
  const referenceFiles = reference.files.map((entry) => {
6787
- const file = record4(entry);
6960
+ const file = record5(entry);
6788
6961
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
6789
6962
  return { path: file.path, content: file.content };
6790
6963
  });
@@ -6799,18 +6972,18 @@ async function parseSource(value2) {
6799
6972
  return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
6800
6973
  }
6801
6974
  function parseReview(value2) {
6802
- const review = record4(record4(value2)?.review);
6975
+ const review = record5(record5(value2)?.review);
6803
6976
  if (!review || !["approved", "rejected"].includes(String(review.verdict)) || typeof review.reviewDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(review.reviewDigest) || typeof review.provider !== "string" || !review.provider || typeof review.model !== "string" || !review.model || !Number.isSafeInteger(review.policyVersion) || Number(review.policyVersion) < 1) throw invalid("review");
6804
6977
  return review;
6805
6978
  }
6806
6979
  function parseCandidate(value2) {
6807
- const candidate = record4(record4(value2)?.candidate);
6980
+ const candidate = record5(record5(value2)?.candidate);
6808
6981
  if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
6809
6982
  throw invalid("candidate");
6810
6983
  }
6811
6984
  return { candidateId: candidate.candidateId, status: candidate.status };
6812
6985
  }
6813
- var record4 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
6986
+ var record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
6814
6987
  var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
6815
6988
  var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
6816
6989
  var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
@@ -6906,8 +7079,8 @@ function gitApply(cwd, patch2, check) {
6906
7079
  });
6907
7080
  let stderr = "";
6908
7081
  child.stderr.setEncoding("utf8");
6909
- child.stderr.on("data", (text2) => {
6910
- if (stderr.length < 4e3) stderr += text2.slice(0, 4e3);
7082
+ child.stderr.on("data", (text3) => {
7083
+ if (stderr.length < 4e3) stderr += text3.slice(0, 4e3);
6911
7084
  });
6912
7085
  child.once("error", reject);
6913
7086
  child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr.trim().slice(0, 500)))));
@@ -7775,7 +7948,7 @@ async function runCodeAgentAttempt(options) {
7775
7948
  }
7776
7949
  }
7777
7950
  async function handleCodeRuntimeInference(input) {
7778
- const { command, metadata: metadata2, request: request2, state: state2 } = input;
7951
+ const { command, metadata: metadata2, request: request3, state: state2 } = input;
7779
7952
  if (state2.tokens >= metadata2.maxTokensPerInteraction) {
7780
7953
  if (!state2.noticeEmitted) {
7781
7954
  state2.noticeEmitted = true;
@@ -7788,7 +7961,7 @@ async function handleCodeRuntimeInference(input) {
7788
7961
  return {
7789
7962
  protocolVersion: HARNESS_PROTOCOL_VERSION,
7790
7963
  type: "inference.response",
7791
- requestId: request2.requestId,
7964
+ requestId: request3.requestId,
7792
7965
  response: {
7793
7966
  id: `budget:${command.commandId}`,
7794
7967
  provider: "openai",
@@ -7802,9 +7975,9 @@ async function handleCodeRuntimeInference(input) {
7802
7975
  }
7803
7976
  const startedAt = Date.now();
7804
7977
  const response2 = await input.control.infer(command.sessionId, {
7805
- requestId: request2.requestId,
7978
+ requestId: request3.requestId,
7806
7979
  interactionId: command.commandId,
7807
- call: request2.call
7980
+ call: request3.call
7808
7981
  });
7809
7982
  state2.tokens += response2.receipt.inputTokens + response2.receipt.outputTokens;
7810
7983
  await input.event({
@@ -7821,14 +7994,14 @@ async function handleCodeRuntimeInference(input) {
7821
7994
  return {
7822
7995
  protocolVersion: HARNESS_PROTOCOL_VERSION,
7823
7996
  type: "inference.response",
7824
- requestId: request2.requestId,
7997
+ requestId: request3.requestId,
7825
7998
  response: response2.response
7826
7999
  };
7827
8000
  }
7828
8001
  function createCodeRuntimeInference(options) {
7829
8002
  let seq = 0;
7830
8003
  return {
7831
- chat: async (request2) => {
8004
+ chat: async (request3) => {
7832
8005
  const requestId = `${options.command.commandId}:${++seq}`;
7833
8006
  const answer = await handleCodeRuntimeInference({
7834
8007
  command: options.command,
@@ -7840,7 +8013,7 @@ function createCodeRuntimeInference(options) {
7840
8013
  protocolVersion: HARNESS_PROTOCOL_VERSION,
7841
8014
  type: "inference.request",
7842
8015
  requestId,
7843
- call: request2
8016
+ call: request3
7844
8017
  }
7845
8018
  });
7846
8019
  if (answer.type !== "inference.response") throw new TypeError("brokered inference returned the wrong frame");
@@ -8046,9 +8219,9 @@ async function safePrefix(base, paths, prefix) {
8046
8219
  function descriptor(name, effect, argumentRoles) {
8047
8220
  return { name, version: 1, effect, inputSchema: { type: "object" }, argumentRoles, policyId: `odla.code.${name}.v1` };
8048
8221
  }
8049
- async function conversionPolicy(id, output) {
8222
+ async function conversionPolicy(id2, output) {
8050
8223
  const definition = {
8051
- conversionId: id,
8224
+ conversionId: id2,
8052
8225
  version: 1,
8053
8226
  output,
8054
8227
  maximumSourceBytes: 1e6,
@@ -8057,18 +8230,18 @@ async function conversionPolicy(id, output) {
8057
8230
  };
8058
8231
  return { ...definition, digest: await conversionPolicyDigest(definition) };
8059
8232
  }
8060
- async function registeredPolicy(id, registryId, values) {
8233
+ async function registeredPolicy(id2, registryId, values) {
8061
8234
  const mapping = Object.fromEntries(values.map((value2) => [value2, value2]));
8062
- return conversionPolicy(id, {
8235
+ return conversionPolicy(id2, {
8063
8236
  kind: "registered_id",
8064
8237
  registryId,
8065
8238
  registryDigest: await registeredIdRegistryDigest(mapping)
8066
8239
  });
8067
8240
  }
8068
8241
  async function conversionRegistry(policies, values) {
8069
- const registeredIds = Object.fromEntries(await Promise.all(Object.entries(values).map(async ([id, entries]) => {
8242
+ const registeredIds = Object.fromEntries(await Promise.all(Object.entries(values).map(async ([id2, entries]) => {
8070
8243
  const mapping = Object.fromEntries(entries.map((value2) => [value2, value2]));
8071
- return [id, { values: mapping, digest: await registeredIdRegistryDigest(mapping) }];
8244
+ return [id2, { values: mapping, digest: await registeredIdRegistryDigest(mapping) }];
8072
8245
  })));
8073
8246
  return createConversionRegistry({ policies, registeredIds });
8074
8247
  }
@@ -8122,10 +8295,10 @@ function decision(input, policy, approvalConsumed, tool, actionDigest) {
8122
8295
  actionDigest: actionDigest ?? (policy.outcome === "require_approval" ? policy.actionDigest : "")
8123
8296
  };
8124
8297
  }
8125
- function policyContext(context, request2, options, extra) {
8298
+ function policyContext(context, request3, options, extra) {
8126
8299
  return {
8127
8300
  lease: context.lease,
8128
- request: request2,
8301
+ request: request3,
8129
8302
  workspaceId: `workspace:${context.lease.task.attemptId}`,
8130
8303
  readers: { kind: "principals", principalIds: [options.readerId] },
8131
8304
  ...extra
@@ -8144,8 +8317,8 @@ function optionalInteger(value2) {
8144
8317
  if (!Number.isSafeInteger(value2) || value2 < 1) throw new TypeError("line bounds must be positive integers");
8145
8318
  return value2;
8146
8319
  }
8147
- function response(request2, ok, content2, details) {
8148
- return { requestId: request2.requestId, ok, content: content2, ...details ? { details } : {} };
8320
+ function response(request3, ok, content2, details) {
8321
+ return { requestId: request3.requestId, ok, content: content2, ...details ? { details } : {} };
8149
8322
  }
8150
8323
  var cache = /* @__PURE__ */ new Map();
8151
8324
  function workspaceGraphs(workspaceDir, paths) {
@@ -8161,7 +8334,7 @@ function workspaceGraphs(workspaceDir, paths) {
8161
8334
  cache.set(workspaceDir, built);
8162
8335
  return built;
8163
8336
  }
8164
- var shortId = (id) => id.slice(id.indexOf(":") + 1);
8337
+ var shortId = (id2) => id2.slice(id2.indexOf(":") + 1);
8165
8338
  function renderOverview(graphs, prefix) {
8166
8339
  const rows = rollup(graphs.graph, FILE, prefix === void 0 ? {} : { prefix });
8167
8340
  if (rows.length === 0) return prefix ? `No source under "${prefix}".` : "No source files.";
@@ -8170,19 +8343,19 @@ function renderOverview(graphs, prefix) {
8170
8343
  return [`${total} source files. Directories, largest first \u2014 read one with sandbox.list --prefix.`, ...lines].join("\n");
8171
8344
  }
8172
8345
  function renderWhereIs(graphs, symbol) {
8173
- const sites = neighbors(graphs.graph, nodeId(SYMBOL, symbol), { direction: "in", kinds: ["exports"] }).map((id) => ({
8174
- path: shortId(id),
8175
- pkg: neighbors(graphs.graph, id, { direction: "in", kinds: ["contains"] })[0],
8176
- dependents: incident(graphs.graph, id, { direction: "in", kinds: [IMPORTS] }).length
8346
+ const sites = neighbors(graphs.graph, nodeId(SYMBOL, symbol), { direction: "in", kinds: ["exports"] }).map((id2) => ({
8347
+ path: shortId(id2),
8348
+ pkg: neighbors(graphs.graph, id2, { direction: "in", kinds: ["contains"] })[0],
8349
+ dependents: incident(graphs.graph, id2, { direction: "in", kinds: [IMPORTS] }).length
8177
8350
  })).sort((left, right) => right.dependents - left.dependents || left.path.localeCompare(right.path));
8178
8351
  if (sites.length === 0) return `No exported symbol named "${symbol}". Try sandbox.search for a textual match.`;
8179
8352
  return sites.slice(0, 20).map((site) => `${site.path}${site.pkg ? ` [${shortId(site.pkg)}]` : ""} ${site.dependents} dependents`).join("\n");
8180
8353
  }
8181
8354
  function renderWhoImports(graphs, path) {
8182
- const id = nodeId(FILE, path);
8183
- const importers = neighbors(graphs.graph, id, { direction: "in", kinds: [IMPORTS] });
8355
+ const id2 = nodeId(FILE, path);
8356
+ const importers = neighbors(graphs.graph, id2, { direction: "in", kinds: [IMPORTS] });
8184
8357
  if (importers.length === 0) {
8185
- return graphs.graph.nodes.has(id) ? `Nothing imports ${path}. It is a leaf.` : `${path} is not a source file in this workspace.`;
8358
+ return graphs.graph.nodes.has(id2) ? `Nothing imports ${path}. It is a leaf.` : `${path} is not a source file in this workspace.`;
8186
8359
  }
8187
8360
  return importers.slice(0, 40).map(shortId).sort().join("\n");
8188
8361
  }
@@ -8205,11 +8378,11 @@ var GRAPH_TOOLS = /* @__PURE__ */ new Set([
8205
8378
  "sandbox.who_imports",
8206
8379
  "sandbox.who_touches"
8207
8380
  ]);
8208
- async function read(context, request2, options, policy) {
8209
- exactKeys(request2.input, ["path", "startLine", "endLine"]);
8210
- const path = stringField(request2.input, "path");
8211
- const startLine = optionalInteger(request2.input.startLine) ?? 1;
8212
- const endLine = optionalInteger(request2.input.endLine) ?? startLine + (options.maxReadLines ?? 2e3) - 1;
8381
+ async function read(context, request3, options, policy) {
8382
+ exactKeys(request3.input, ["path", "startLine", "endLine"]);
8383
+ const path = stringField(request3.input, "path");
8384
+ const startLine = optionalInteger(request3.input.startLine) ?? 1;
8385
+ const endLine = optionalInteger(request3.input.endLine) ?? startLine + (options.maxReadLines ?? 2e3) - 1;
8213
8386
  if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
8214
8387
  throw new TypeError("requested line range exceeds its bound");
8215
8388
  }
@@ -8217,8 +8390,8 @@ async function read(context, request2, options, policy) {
8217
8390
  if (!paths.includes(path)) {
8218
8391
  throw new TypeError(`no such file in the staged workspace: "${path}". Use sandbox.overview, sandbox.where_is or sandbox.search to find the correct path.`);
8219
8392
  }
8220
- const allowed = await policy.read(policyContext(context, request2, options, { paths, path, startLine, endLine }));
8221
- if (!allowed) return response(request2, false, "tool denied by CaMeL policy");
8393
+ const allowed = await policy.read(policyContext(context, request3, options, { paths, path, startLine, endLine }));
8394
+ if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8222
8395
  const target = resolveCodePath(context.workspaceDir, path);
8223
8396
  const info = await stat2(target);
8224
8397
  if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
@@ -8231,74 +8404,74 @@ async function read(context, request2, options, policy) {
8231
8404
  if (Buffer.byteLength(content2) > (options.maxReadBytes ?? 128 * 1024)) {
8232
8405
  throw new TypeError("read result exceeds its byte bound");
8233
8406
  }
8234
- return response(request2, true, content2, { path, startLine, endLine: Math.min(endLine, lines.length) });
8407
+ return response(request3, true, content2, { path, startLine, endLine: Math.min(endLine, lines.length) });
8235
8408
  }
8236
- async function list(context, request2, options, policy) {
8237
- exactKeys(request2.input, ["prefix", "maxEntries"]);
8238
- const raw = request2.input.prefix;
8409
+ async function list(context, request3, options, policy) {
8410
+ exactKeys(request3.input, ["prefix", "maxEntries"]);
8411
+ const raw = request3.input.prefix;
8239
8412
  const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
8240
- const maxEntries = optionalInteger(request2.input.maxEntries) ?? 1e3;
8413
+ const maxEntries = optionalInteger(request3.input.maxEntries) ?? 1e3;
8241
8414
  if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
8242
8415
  const paths = await registeredFiles(context.workspaceDir, 2e4);
8243
- const allowed = await policy.list(policyContext(context, request2, options, { paths, ...prefix ? { prefix } : {} }));
8244
- if (!allowed) return response(request2, false, "tool denied by CaMeL policy");
8416
+ const allowed = await policy.list(policyContext(context, request3, options, { paths, ...prefix ? { prefix } : {} }));
8417
+ if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8245
8418
  const entries = listWorkspace(paths, { ...prefix ? { prefix } : {}, maxEntries });
8246
8419
  if (!entries.length) {
8247
- return response(request2, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
8420
+ return response(request3, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
8248
8421
  }
8249
8422
  const truncated = entries.length < paths.length && entries.length === maxEntries;
8250
8423
  const hint = !prefix && paths.length > 500 ? `
8251
8424
  \u2026 ${paths.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
8252
8425
  return response(
8253
- request2,
8426
+ request3,
8254
8427
  true,
8255
8428
  `${entries.join("\n")}${truncated ? `
8256
8429
  \u2026 truncated at ${maxEntries} entries` : ""}${hint}`,
8257
8430
  { count: entries.length, truncated }
8258
8431
  );
8259
8432
  }
8260
- async function search(context, request2, options, policy) {
8261
- exactKeys(request2.input, ["query", "prefix", "maxResults", "caseSensitive"]);
8262
- const query = stringField(request2.input, "query");
8433
+ async function search(context, request3, options, policy) {
8434
+ exactKeys(request3.input, ["query", "prefix", "maxResults", "caseSensitive"]);
8435
+ const query = stringField(request3.input, "query");
8263
8436
  if (query.length > 512) throw new TypeError("search query exceeds its bound");
8264
- const raw = request2.input.prefix;
8437
+ const raw = request3.input.prefix;
8265
8438
  const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
8266
- const maxResults = optionalInteger(request2.input.maxResults) ?? 100;
8439
+ const maxResults = optionalInteger(request3.input.maxResults) ?? 100;
8267
8440
  if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
8268
- const caseSensitive = request2.input.caseSensitive === void 0 ? true : request2.input.caseSensitive === true;
8441
+ const caseSensitive = request3.input.caseSensitive === void 0 ? true : request3.input.caseSensitive === true;
8269
8442
  const paths = await registeredFiles(context.workspaceDir, 2e4);
8270
- const allowed = await policy.search(policyContext(context, request2, options, { paths, query, ...prefix ? { prefix } : {} }));
8271
- if (!allowed) return response(request2, false, "tool denied by CaMeL policy");
8443
+ const allowed = await policy.search(policyContext(context, request3, options, { paths, query, ...prefix ? { prefix } : {} }));
8444
+ if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8272
8445
  const matches = await searchWorkspace(context.workspaceDir, paths, {
8273
8446
  query,
8274
8447
  maxResults,
8275
8448
  caseSensitive,
8276
8449
  ...prefix ? { prefix } : {}
8277
8450
  });
8278
- if (!matches.length) return response(request2, true, `No match for "${query}".`, { count: 0 });
8279
- return response(request2, true, matches.map((match) => `${match.path}:${match.line}: ${match.text}`).join("\n"), {
8451
+ if (!matches.length) return response(request3, true, `No match for "${query}".`, { count: 0 });
8452
+ return response(request3, true, matches.map((match) => `${match.path}:${match.line}: ${match.text}`).join("\n"), {
8280
8453
  count: matches.length
8281
8454
  });
8282
8455
  }
8283
- async function graphQuery(context, request2, options, policy) {
8284
- exactKeys(request2.input, ["query"]);
8285
- const raw = request2.input.query;
8456
+ async function graphQuery(context, request3, options, policy) {
8457
+ exactKeys(request3.input, ["query"]);
8458
+ const raw = request3.input.query;
8286
8459
  const query = typeof raw === "string" ? raw : "";
8287
8460
  if (query.length > 512) throw new TypeError("query exceeds its bound");
8288
- const allowed = await policy.graph(policyContext(context, request2, options, {
8289
- tool: request2.tool,
8461
+ const allowed = await policy.graph(policyContext(context, request3, options, {
8462
+ tool: request3.tool,
8290
8463
  selector: query
8291
8464
  }));
8292
- if (!allowed) return response(request2, false, "tool denied by CaMeL policy");
8465
+ if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8293
8466
  const paths = await registeredFiles(context.workspaceDir, 2e4);
8294
8467
  const graphs = await workspaceGraphs(context.workspaceDir, paths);
8295
- if (request2.tool === "sandbox.overview") {
8296
- return response(request2, true, renderOverview(graphs, query || void 0));
8468
+ if (request3.tool === "sandbox.overview") {
8469
+ return response(request3, true, renderOverview(graphs, query || void 0));
8297
8470
  }
8298
- if (!query) throw new TypeError(`${request2.tool} requires a query`);
8299
- if (request2.tool === "sandbox.where_is") return response(request2, true, renderWhereIs(graphs, query));
8300
- if (request2.tool === "sandbox.who_imports") return response(request2, true, renderWhoImports(graphs, query));
8301
- return response(request2, true, renderWhoTouches(graphs, query));
8471
+ if (!query) throw new TypeError(`${request3.tool} requires a query`);
8472
+ if (request3.tool === "sandbox.where_is") return response(request3, true, renderWhereIs(graphs, query));
8473
+ if (request3.tool === "sandbox.who_imports") return response(request3, true, renderWhoImports(graphs, query));
8474
+ return response(request3, true, renderWhoTouches(graphs, query));
8302
8475
  }
8303
8476
  function createCodeToolBroker(options) {
8304
8477
  validateOptions(options);
@@ -8306,24 +8479,24 @@ function createCodeToolBroker(options) {
8306
8479
  const policy = createCodePolicyGate(options);
8307
8480
  let tail = Promise.resolve();
8308
8481
  return {
8309
- execute(context, request2) {
8310
- const result = tail.then(() => route(context, request2, options, recipes, policy));
8482
+ execute(context, request3) {
8483
+ const result = tail.then(() => route2(context, request3, options, recipes, policy));
8311
8484
  tail = result.then(() => void 0, () => void 0);
8312
8485
  return result;
8313
8486
  }
8314
8487
  };
8315
8488
  }
8316
- async function route(context, request2, options, recipes, policy) {
8489
+ async function route2(context, request3, options, recipes, policy) {
8317
8490
  try {
8318
8491
  if (context.signal?.aborted) throw new TypeError("tool request was cancelled");
8319
- if (request2.tool === "sandbox.read") return await read(context, request2, options, policy);
8320
- if (request2.tool === "sandbox.list") return await list(context, request2, options, policy);
8321
- if (request2.tool === "sandbox.search") return await search(context, request2, options, policy);
8322
- if (GRAPH_TOOLS.has(request2.tool)) return await graphQuery(context, request2, options, policy);
8323
- if (request2.tool === "sandbox.apply_patch") return await patch(context, request2, options, policy);
8324
- return await recipe(context, request2, options, recipes, policy);
8492
+ if (request3.tool === "sandbox.read") return await read(context, request3, options, policy);
8493
+ if (request3.tool === "sandbox.list") return await list(context, request3, options, policy);
8494
+ if (request3.tool === "sandbox.search") return await search(context, request3, options, policy);
8495
+ if (GRAPH_TOOLS.has(request3.tool)) return await graphQuery(context, request3, options, policy);
8496
+ if (request3.tool === "sandbox.apply_patch") return await patch(context, request3, options, policy);
8497
+ return await recipe(context, request3, options, recipes, policy);
8325
8498
  } catch (reason) {
8326
- return response(request2, false, toolFailureMessage(reason));
8499
+ return response(request3, false, toolFailureMessage(reason));
8327
8500
  }
8328
8501
  }
8329
8502
  function toolFailureMessage(reason) {
@@ -8335,34 +8508,34 @@ function toolFailureMessage(reason) {
8335
8508
  if (code === "EACCES" || code === "EPERM") return "that path is not readable through this tool";
8336
8509
  return "tool failed closed";
8337
8510
  }
8338
- async function patch(context, request2, options, policy) {
8339
- exactKeys(request2.input, ["patch"]);
8340
- const value2 = stringField(request2.input, "patch");
8511
+ async function patch(context, request3, options, policy) {
8512
+ exactKeys(request3.input, ["patch"]);
8513
+ const value2 = stringField(request3.input, "patch");
8341
8514
  const paths = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
8342
8515
  if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
8343
8516
  throw new TypeError("patch targets a read-only reference source");
8344
8517
  }
8345
- const allowed = await policy.patch(policyContext(context, request2, options, { patch: value2 }));
8346
- if (!allowed) return response(request2, false, "tool denied by CaMeL policy");
8518
+ const allowed = await policy.patch(policyContext(context, request3, options, { patch: value2 }));
8519
+ if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8347
8520
  await applyCodePatch(context.workspaceDir, value2, paths);
8348
- return response(request2, true, `Applied patch to ${paths.length} file(s).`, { paths });
8521
+ return response(request3, true, `Applied patch to ${paths.length} file(s).`, { paths });
8349
8522
  }
8350
- async function recipe(context, request2, options, recipes, policy) {
8351
- exactKeys(request2.input, ["recipeId"]);
8352
- const recipeId = stringField(request2.input, "recipeId");
8523
+ async function recipe(context, request3, options, recipes, policy) {
8524
+ exactKeys(request3.input, ["recipeId"]);
8525
+ const recipeId = stringField(request3.input, "recipeId");
8353
8526
  const digestLimits = {
8354
8527
  maxFiles: options.maxRecipeWorkspaceFiles ?? 2e4,
8355
8528
  maxBytes: options.maxRecipeWorkspaceBytes ?? 512 * 1024 * 1024
8356
8529
  };
8357
8530
  const sourceDigest = await digestStagedWorkspace(context.workspaceDir, digestLimits);
8358
- const allowed = await policy.recipe(policyContext(context, request2, options, {
8531
+ const allowed = await policy.recipe(policyContext(context, request3, options, {
8359
8532
  recipeIds: [...recipes.keys()].sort(),
8360
8533
  recipeId,
8361
8534
  sourceDigest
8362
8535
  }));
8363
- if (!allowed) return response(request2, false, "tool denied by CaMeL policy");
8536
+ if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8364
8537
  const selected = recipes.get(recipeId);
8365
- if (!selected) return response(request2, false, "build recipe is not registered");
8538
+ if (!selected) return response(request3, false, "build recipe is not registered");
8366
8539
  const staged = await stageWorkspace(context.workspaceDir, {
8367
8540
  maxFiles: digestLimits.maxFiles,
8368
8541
  maxBytes: digestLimits.maxBytes
@@ -8379,7 +8552,7 @@ async function recipe(context, request2, options, recipes, policy) {
8379
8552
  const output = [result.stdout, result.stderr].filter(Boolean).join("\n");
8380
8553
  const ok = result.exitCode === 0 && !result.outputLimitExceeded && !result.timedOut;
8381
8554
  const status = result.timedOut ? "timed out" : result.outputLimitExceeded ? "exceeded output limit" : ok ? "passed" : `failed with exit ${result.exitCode}`;
8382
- return response(request2, ok, `Recipe ${recipeId} ${status}.${output ? `
8555
+ return response(request3, ok, `Recipe ${recipeId} ${status}.${output ? `
8383
8556
  ${output}` : ""}`, {
8384
8557
  recipeId,
8385
8558
  exitCode: result.exitCode,
@@ -8429,7 +8602,7 @@ async function runGoal(spec, attempt) {
8429
8602
  const startedAt = now();
8430
8603
  const attempts = [];
8431
8604
  const boardErrors = [];
8432
- const emit3 = async (event) => {
8605
+ const emit4 = async (event) => {
8433
8606
  if (!spec.onEvent) return;
8434
8607
  try {
8435
8608
  await spec.onEvent(event);
@@ -8442,7 +8615,7 @@ async function runGoal(spec, attempt) {
8442
8615
  let costKnown = false;
8443
8616
  const finish2 = async (stoppedReason) => {
8444
8617
  const met = stoppedReason === "proof_passed";
8445
- await emit3(met ? { type: "goal_met", attempts: attempts.length, tokens, ...costKnown ? { costUsd } : {} } : {
8618
+ await emit4(met ? { type: "goal_met", attempts: attempts.length, tokens, ...costKnown ? { costUsd } : {} } : {
8446
8619
  type: "goal_abandoned",
8447
8620
  reason: stoppedReason,
8448
8621
  attempts: attempts.length,
@@ -8463,7 +8636,7 @@ async function runGoal(spec, attempt) {
8463
8636
  if (spec.signal?.aborted) return finish2("cancelled");
8464
8637
  if (spec.budget.deadline !== void 0 && now() >= spec.budget.deadline) return finish2("deadline");
8465
8638
  const prompt = index === 1 ? openingPrompt(spec) : retryPrompt(spec, attempts.at(-1));
8466
- await emit3({ type: "attempt_started", attempt: index, prompt });
8639
+ await emit4({ type: "attempt_started", attempt: index, prompt });
8467
8640
  const outcome = await attempt({
8468
8641
  attempt: index,
8469
8642
  prompt,
@@ -8483,7 +8656,7 @@ async function runGoal(spec, attempt) {
8483
8656
  ...outcome.error === void 0 ? {} : { error: outcome.error }
8484
8657
  });
8485
8658
  if (outcome.gatePassed) return finish2("proof_passed");
8486
- await emit3({
8659
+ await emit4({
8487
8660
  type: "attempt_failed",
8488
8661
  attempt: index,
8489
8662
  feedback: outcome.feedback,
@@ -8532,7 +8705,7 @@ function createCodeRuntimeToolBroker(input, lease, role) {
8532
8705
  readerId: `code-session:${lease.task.taskId}`,
8533
8706
  readOnlyPrefixes: [".odla-references"]
8534
8707
  });
8535
- return role === "coding" ? broker : { execute: (context, request2) => request2.tool === "sandbox.read" ? broker.execute(context, request2) : Promise.resolve({ requestId: request2.requestId, ok: false, content: "review sessions are read-only" }) };
8708
+ return role === "coding" ? broker : { execute: (context, request3) => request3.tool === "sandbox.read" ? broker.execute(context, request3) : Promise.resolve({ requestId: request3.requestId, ok: false, content: "review sessions are read-only" }) };
8536
8709
  }
8537
8710
  var POSITIVE = (value2) => Number.isFinite(value2) && Number(value2) > 0 ? Number(value2) : void 0;
8538
8711
  function codeGoalSpec(payload) {
@@ -8920,18 +9093,18 @@ var CodePiRuntimeEngine = class {
8920
9093
  /** Report every brokered effect as it starts and finishes. */
8921
9094
  #observed(command, active, broker) {
8922
9095
  return {
8923
- execute: async (context, request2) => {
9096
+ execute: async (context, request3) => {
8924
9097
  const startedAt = Date.now();
8925
9098
  await this.#event(
8926
9099
  command,
8927
- { type: "tool", phase: "started", tool: request2.tool },
9100
+ { type: "tool", phase: "started", tool: request3.tool },
8928
9101
  active.conversationRefs
8929
9102
  ).catch(() => void 0);
8930
- const response2 = await broker.execute(context, request2);
9103
+ const response2 = await broker.execute(context, request3);
8931
9104
  await this.#event(command, {
8932
9105
  type: "tool",
8933
9106
  phase: "completed",
8934
- tool: request2.tool,
9107
+ tool: request3.tool,
8935
9108
  ok: response2.ok,
8936
9109
  durationMs: Date.now() - startedAt
8937
9110
  }, active.conversationRefs).catch(() => void 0);
@@ -9071,7 +9244,7 @@ async function waitForHostedPoll(milliseconds, signal) {
9071
9244
  }
9072
9245
  function isValidHostedSecurityPlan(value2, env) {
9073
9246
  if (!value2 || value2.env !== env || !/^sha256:[a-f0-9]{64}$/.test(value2.planDigest) || value2.consentContract !== "odla.hosted-security-consent.v1" || value2.reportProjection !== "odla.hosted-security-report.v1" || value2.redactionContract !== "odla.best-effort-credential-pattern-redaction.v1" || typeof value2.promptBundle !== "string" || value2.promptBundle.length < 1 || value2.promptBundle.length > 100 || typeof value2.ready !== "boolean" || typeof value2.independent !== "boolean" || value2.sourceDisclosure !== "redacted" || value2.targetExecution !== false || !Number.isSafeInteger(value2.reportRetentionDays) || value2.reportRetentionDays < 1) return false;
9074
- const validRoute = (route2, purpose) => !!route2 && route2.purpose === purpose && typeof route2.enabled === "boolean" && typeof route2.credentialReady === "boolean" && typeof route2.provider === "string" && route2.provider.length > 0 && route2.provider.length <= 100 && typeof route2.model === "string" && route2.model.length > 0 && route2.model.length <= 200 && Number.isSafeInteger(route2.policyVersion) && route2.policyVersion >= 1 && Number.isSafeInteger(route2.maxCallsPerRun) && route2.maxCallsPerRun >= 1 && Number.isSafeInteger(route2.maxInputBytes) && route2.maxInputBytes >= 1 && Number.isSafeInteger(route2.maxOutputTokens) && route2.maxOutputTokens >= 1;
9247
+ const validRoute = (route3, purpose) => !!route3 && route3.purpose === purpose && typeof route3.enabled === "boolean" && typeof route3.credentialReady === "boolean" && typeof route3.provider === "string" && route3.provider.length > 0 && route3.provider.length <= 100 && typeof route3.model === "string" && route3.model.length > 0 && route3.model.length <= 200 && Number.isSafeInteger(route3.policyVersion) && route3.policyVersion >= 1 && Number.isSafeInteger(route3.maxCallsPerRun) && route3.maxCallsPerRun >= 1 && Number.isSafeInteger(route3.maxInputBytes) && route3.maxInputBytes >= 1 && Number.isSafeInteger(route3.maxOutputTokens) && route3.maxOutputTokens >= 1;
9075
9248
  return validRoute(value2.routes?.discovery, "security.discovery") && validRoute(value2.routes?.validation, "security.validation");
9076
9249
  }
9077
9250
  function hostedSecurityCredential(value2) {
@@ -9420,20 +9593,20 @@ async function runCodeRuntime(input) {
9420
9593
  }
9421
9594
  }
9422
9595
  function parseConnection(value2, appId, appEnv) {
9423
- const root = record5(value2);
9424
- const host = record5(root?.host);
9425
- const offer = record5(root?.offer);
9426
- const binding = record5(root?.binding);
9596
+ const root = record6(value2);
9597
+ const host = record6(root?.host);
9598
+ const offer = record6(root?.offer);
9599
+ const binding = record6(root?.binding);
9427
9600
  if (!root || typeof root.token !== "string" || !/^odla_code_host_[0-9a-f]{64}$/.test(root.token) || typeof root.resumed !== "boolean" || !host || !/^chost_[0-9a-f]{32}$/.test(String(host.hostId)) || typeof host.name !== "string" || !offer || !Number.isSafeInteger(offer.slots) || !binding || typeof binding.appId !== "string" || !binding.appId || appId && binding.appId !== appId || binding.env !== appEnv || !Number.isSafeInteger(binding.generation)) {
9428
9601
  throw new Error("connect Code host returned an invalid response");
9429
9602
  }
9430
9603
  return root;
9431
9604
  }
9432
9605
  function apiFailure(action2, status, value2) {
9433
- const message2 = record5(record5(value2)?.error)?.message;
9606
+ const message2 = record6(record6(value2)?.error)?.message;
9434
9607
  return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
9435
9608
  }
9436
- function record5(value2) {
9609
+ function record6(value2) {
9437
9610
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
9438
9611
  }
9439
9612
 
@@ -9652,9 +9825,9 @@ async function credentialCommand(parsed, deps = {}) {
9652
9825
  }, doFetch, out, { optionalProjectCapabilities: ["app.manage"] });
9653
9826
  const base = `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials`;
9654
9827
  if (action2 === "revoke") {
9655
- const id = parsed.positionals[2];
9656
- if (!id) throw new Error("credentials revoke requires the exact receipt id from credentials list");
9657
- const response3 = await doFetch(`${base}/${encodeURIComponent(id)}`, {
9828
+ const id2 = parsed.positionals[2];
9829
+ if (!id2) throw new Error("credentials revoke requires the exact receipt id from credentials list");
9830
+ const response3 = await doFetch(`${base}/${encodeURIComponent(id2)}`, {
9658
9831
  method: "DELETE",
9659
9832
  headers: { authorization: `Bearer ${token}` }
9660
9833
  });
@@ -9762,6 +9935,12 @@ Usage:
9762
9935
  odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
9763
9936
  odla-ai context remove <name> --yes [--json]
9764
9937
  odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
9938
+ odla-ai monitor plan [--config odla.config.mjs] [--env prod] [--json]
9939
+ odla-ai monitor apply [--config odla.config.mjs] [--env prod] [--json] [--yes]
9940
+ odla-ai monitor run <probe-id> [--app <id>] [--env prod] [--json]
9941
+ odla-ai monitor status [--app <id>] [--context <name>] [--env prod] [--json]
9942
+ odla-ai monitor incidents [--app <id>] [--env prod] [--limit 100] [--runs] [--json]
9943
+ odla-ai monitor report [--app <id>] [--env prod] [--period daily|weekly] [--json]
9765
9944
  odla-ai platform status [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
9766
9945
  odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
9767
9946
  odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
@@ -9899,6 +10078,9 @@ Commands:
9899
10078
  canary, collector ingest/scheduler trust, Cloudflare-owned
9900
10079
  runtime metrics, and a machine verdict.
9901
10080
  --json keeps auth progress on stderr for unattended agents.
10081
+ monitor Reconcile checked-in Kitesurf routes, rolling SLOs, spike/trend
10082
+ policies, and email digests; run probes manually and expose
10083
+ stable status, incident, and report JSON to agents and CI.
9902
10084
  platform Read canonical fleet health, releases, provider load/freshness,
9903
10085
  explicit unknowns, and next actions through a read-only grant.
9904
10086
  provision Register services, compose integrations, persist credentials, optionally push secrets.
@@ -10102,7 +10284,7 @@ async function discussList(ctx, parsed) {
10102
10284
  }
10103
10285
  });
10104
10286
  }
10105
- async function discussRead(ctx, id, parsed) {
10287
+ async function discussRead(ctx, id2, parsed) {
10106
10288
  const requestedLimit = stringOpt(parsed.options.limit);
10107
10289
  const requestedOffset = stringOpt(parsed.options.offset);
10108
10290
  if (requestedLimit !== void 0 || requestedOffset !== void 0) {
@@ -10110,7 +10292,7 @@ async function discussRead(ctx, id, parsed) {
10110
10292
  limit: requestedLimit ?? "200",
10111
10293
  offset: requestedOffset ?? "0"
10112
10294
  });
10113
- const page2 = await request(ctx, "GET", `/topics/${encodeURIComponent(id)}?${query}`);
10295
+ const page2 = await request(ctx, "GET", `/topics/${encodeURIComponent(id2)}?${query}`);
10114
10296
  emit(
10115
10297
  ctx,
10116
10298
  page2,
@@ -10130,7 +10312,7 @@ async function discussRead(ctx, id, parsed) {
10130
10312
  const page2 = await request(
10131
10313
  ctx,
10132
10314
  "GET",
10133
- `/topics/${encodeURIComponent(id)}?limit=200&offset=${offset}`
10315
+ `/topics/${encodeURIComponent(id2)}?limit=200&offset=${offset}`
10134
10316
  );
10135
10317
  topic = page2.topic;
10136
10318
  for (const post of page2.posts) posts.set(post.id, post);
@@ -10172,20 +10354,20 @@ async function discussPost(ctx, parsed) {
10172
10354
  });
10173
10355
  emit(ctx, created, () => ctx.out.log(`opened topic ${created.id}`));
10174
10356
  }
10175
- async function discussReply(ctx, id, parsed) {
10357
+ async function discussReply(ctx, id2, parsed) {
10176
10358
  const created = await request(
10177
10359
  ctx,
10178
10360
  "POST",
10179
- `/topics/${encodeURIComponent(id)}/replies`,
10361
+ `/topics/${encodeURIComponent(id2)}/replies`,
10180
10362
  { ...content(parsed), mutationId: writeMutationId(parsed) }
10181
10363
  );
10182
10364
  emit(ctx, created, () => ctx.out.log(`replied ${created.id}`));
10183
10365
  }
10184
- async function discussResolve(ctx, id, resolved, parsed) {
10366
+ async function discussResolve(ctx, id2, resolved, parsed) {
10185
10367
  const result = await request(
10186
10368
  ctx,
10187
10369
  "PATCH",
10188
- `/topics/${encodeURIComponent(id)}`,
10370
+ `/topics/${encodeURIComponent(id2)}`,
10189
10371
  { resolved, mutationId: writeMutationId(parsed) }
10190
10372
  );
10191
10373
  emit(ctx, result, () => ctx.out.log(`${resolved ? "resolved" : "reopened"} ${result.id}`));
@@ -10459,9 +10641,9 @@ var ALLOWED = [
10459
10641
  "context",
10460
10642
  "open"
10461
10643
  ];
10462
- function requireId(id, action2) {
10463
- if (!id) throw new Error(`"discuss ${action2}" needs a topic id`);
10464
- return id;
10644
+ function requireId(id2, action2) {
10645
+ if (!id2) throw new Error(`"discuss ${action2}" needs a topic id`);
10646
+ return id2;
10465
10647
  }
10466
10648
  async function buildContext(parsed, deps) {
10467
10649
  const context = await resolveOperatorContext(parsed, {
@@ -10496,7 +10678,7 @@ async function buildContext(parsed, deps) {
10496
10678
  async function discussCommand(parsed, deps = {}) {
10497
10679
  assertArgs(parsed, ALLOWED, 3);
10498
10680
  const action2 = parsed.positionals[1];
10499
- const id = parsed.positionals[2];
10681
+ const id2 = parsed.positionals[2];
10500
10682
  if (!action2) throw new Error('"discuss" needs an action. Run "odla-ai help".');
10501
10683
  const ctx = await buildContext(parsed, deps);
10502
10684
  switch (action2) {
@@ -10506,17 +10688,17 @@ async function discussCommand(parsed, deps = {}) {
10506
10688
  case "topics":
10507
10689
  return discussList(ctx, parsed);
10508
10690
  case "read":
10509
- return discussRead(ctx, requireId(id, "read"), parsed);
10691
+ return discussRead(ctx, requireId(id2, "read"), parsed);
10510
10692
  case "post":
10511
10693
  return discussPost(ctx, parsed);
10512
10694
  case "reply":
10513
- return discussReply(ctx, requireId(id, "reply"), parsed);
10695
+ return discussReply(ctx, requireId(id2, "reply"), parsed);
10514
10696
  case "resolve":
10515
- return discussResolve(ctx, requireId(id, "resolve"), parsed.options.reopen !== true, parsed);
10697
+ return discussResolve(ctx, requireId(id2, "resolve"), parsed.options.reopen !== true, parsed);
10516
10698
  case "who":
10517
10699
  return discussWho(ctx, parsed);
10518
10700
  case "watch": {
10519
- const result = await discussWatch(ctx, id, parsed);
10701
+ const result = await discussWatch(ctx, id2, parsed);
10520
10702
  if (!result.found) throw new WatchTimeoutError(result.cursor);
10521
10703
  return;
10522
10704
  }
@@ -10587,8 +10769,8 @@ function collectFields(parsed, allowClear) {
10587
10769
  if (allowClear) out[spec.key] = null;
10588
10770
  continue;
10589
10771
  }
10590
- const text2 = stringOpt(value2);
10591
- out[spec.key] = spec.num ? Number(text2) : text2;
10772
+ const text3 = stringOpt(value2);
10773
+ out[spec.key] = spec.num ? Number(text3) : text3;
10592
10774
  }
10593
10775
  return out;
10594
10776
  }
@@ -10601,17 +10783,17 @@ function collectEntityFields(entity, parsed, allowClear) {
10601
10783
  if (entity === "task" && fields.column === "ready") fields.column = "todo";
10602
10784
  return fields;
10603
10785
  }
10604
- function statusCol(entity, record9) {
10605
- if (entity === "bug") return `${record9.status ?? ""}/${record9.severity ?? ""}`;
10786
+ function statusCol(entity, record11) {
10787
+ if (entity === "bug") return `${record11.status ?? ""}/${record11.severity ?? ""}`;
10606
10788
  if (entity === "task") {
10607
- const state2 = record9.column === "todo" ? "ready" : String(record9.column ?? "");
10608
- return record9.revision ? `${state2}; r${record9.revision}` : state2;
10789
+ const state2 = record11.column === "todo" ? "ready" : String(record11.column ?? "");
10790
+ return record11.revision ? `${state2}; r${record11.revision}` : state2;
10609
10791
  }
10610
- return String(record9.status ?? "");
10792
+ return String(record11.status ?? "");
10611
10793
  }
10612
- function referenceMarkup(entity, record9) {
10613
- const label = (record9.title?.trim() || `${entity} ${record9.id}`).replaceAll("]", ")");
10614
- return `@[${label}](pm:${entity}/${record9.id})`;
10794
+ function referenceMarkup(entity, record11) {
10795
+ const label = (record11.title?.trim() || `${entity} ${record11.id}`).replaceAll("]", ")");
10796
+ return `@[${label}](pm:${entity}/${record11.id})`;
10615
10797
  }
10616
10798
  var STUDIO_SECTION = {
10617
10799
  goal: "goals",
@@ -10619,19 +10801,19 @@ var STUDIO_SECTION = {
10619
10801
  decision: "decisions",
10620
10802
  bug: "bugs"
10621
10803
  };
10622
- function studioRecordUrl(ctx, entity, id) {
10804
+ function studioRecordUrl(ctx, entity, id2) {
10623
10805
  return new URL(
10624
- `/studio/pm/${STUDIO_SECTION[entity]}/${encodeURIComponent(id)}`,
10806
+ `/studio/pm/${STUDIO_SECTION[entity]}/${encodeURIComponent(id2)}`,
10625
10807
  ctx.platformUrl
10626
10808
  ).href;
10627
10809
  }
10628
- function studioRecordLink(ctx, entity, record9) {
10629
- const label = (record9.title?.trim() || `${entity} ${record9.id}`).replaceAll("]", ")");
10630
- return `[${label}](${studioRecordUrl(ctx, entity, record9.id)})`;
10810
+ function studioRecordLink(ctx, entity, record11) {
10811
+ const label = (record11.title?.trim() || `${entity} ${record11.id}`).replaceAll("]", ")");
10812
+ return `[${label}](${studioRecordUrl(ctx, entity, record11.id)})`;
10631
10813
  }
10632
- function printRecord(ctx, entity, record9) {
10814
+ function printRecord(ctx, entity, record11) {
10633
10815
  ctx.out.log(
10634
- `${record9.id} [${statusCol(entity, record9)}] ${record9.appId} ${studioRecordLink(ctx, entity, record9)}`
10816
+ `${record11.id} [${statusCol(entity, record11)}] ${record11.appId} ${studioRecordLink(ctx, entity, record11)}`
10635
10817
  );
10636
10818
  }
10637
10819
  function emit2(ctx, value2, human) {
@@ -10685,52 +10867,52 @@ async function pmAdd(ctx, entity, parsed) {
10685
10867
  input,
10686
10868
  mutationId: writeMutationId2(parsed)
10687
10869
  });
10688
- const record9 = { id: res.id, appId, title: String(input.title) };
10689
- emit2(ctx, res, () => ctx.out.log(`created ${entity}: ${studioRecordLink(ctx, entity, record9)}`));
10870
+ const record11 = { id: res.id, appId, title: String(input.title) };
10871
+ emit2(ctx, res, () => ctx.out.log(`created ${entity}: ${studioRecordLink(ctx, entity, record11)}`));
10690
10872
  }
10691
- async function pmGet(ctx, entity, id) {
10692
- const { record: record9 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
10693
- emit2(ctx, record9, () => printRecord(ctx, entity, record9));
10873
+ async function pmGet(ctx, entity, id2) {
10874
+ const { record: record11 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id2)}`);
10875
+ emit2(ctx, record11, () => printRecord(ctx, entity, record11));
10694
10876
  }
10695
- async function pmReference(ctx, entity, id) {
10696
- const { record: record9 } = await pmRequest(
10877
+ async function pmReference(ctx, entity, id2) {
10878
+ const { record: record11 } = await pmRequest(
10697
10879
  ctx,
10698
10880
  "GET",
10699
- `/${entity}/${encodeURIComponent(id)}`
10881
+ `/${entity}/${encodeURIComponent(id2)}`
10700
10882
  );
10701
- const markup = referenceMarkup(entity, record9);
10702
- emit2(ctx, { kind: `pm:${entity}`, id: record9.id, label: record9.title ?? "", markup }, () => {
10883
+ const markup = referenceMarkup(entity, record11);
10884
+ emit2(ctx, { kind: `pm:${entity}`, id: record11.id, label: record11.title ?? "", markup }, () => {
10703
10885
  ctx.out.log(markup);
10704
10886
  });
10705
10887
  }
10706
- async function pmSet(ctx, entity, id, parsed) {
10888
+ async function pmSet(ctx, entity, id2, parsed) {
10707
10889
  const patch2 = collectEntityFields(entity, parsed, true);
10708
10890
  if (Object.keys(patch2).length === 0)
10709
10891
  throw new Error("pm set needs at least one field flag (e.g. --status doing, --assignee me, --no-assignee)");
10710
- const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, {
10892
+ const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id2)}`, {
10711
10893
  patch: patch2,
10712
10894
  mutationId: writeMutationId2(parsed)
10713
10895
  });
10714
10896
  emit2(ctx, res, () => {
10715
- if (!res.record) return ctx.out.log(`updated ${entity} ${id}`);
10897
+ if (!res.record) return ctx.out.log(`updated ${entity} ${id2}`);
10716
10898
  ctx.out.log(`${entity}: ${studioRecordLink(ctx, entity, res.record)} \u2192 ${statusCol(entity, res.record)}`);
10717
10899
  });
10718
10900
  }
10719
- async function pmDone(ctx, entity, id, parsed) {
10901
+ async function pmDone(ctx, entity, id2, parsed) {
10720
10902
  const decisionId = stringOpt(parsed.options.decision);
10721
10903
  if (decisionId && entity !== "bug") throw new Error("--decision is only valid when completing a bug");
10722
10904
  const patch2 = { ...DONE[entity], ...decisionId ? { decisionId } : {} };
10723
- const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, {
10905
+ const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id2)}`, {
10724
10906
  patch: patch2,
10725
10907
  mutationId: writeMutationId2(parsed)
10726
10908
  });
10727
10909
  emit2(ctx, res, () => {
10728
- const label = res.record ? studioRecordLink(ctx, entity, res.record) : id;
10910
+ const label = res.record ? studioRecordLink(ctx, entity, res.record) : id2;
10729
10911
  const state2 = res.record ? statusCol(entity, res.record) : "done";
10730
10912
  ctx.out.log(`${entity}: ${label} \u2192 ${state2}`);
10731
10913
  });
10732
10914
  }
10733
- async function pmTaskLifecycle(ctx, id, action2, parsed) {
10915
+ async function pmTaskLifecycle(ctx, id2, action2, parsed) {
10734
10916
  const rawRevision = stringOpt(parsed.options["expected-revision"]);
10735
10917
  const expectedRevision = Number(rawRevision);
10736
10918
  if (!rawRevision || !Number.isSafeInteger(expectedRevision) || expectedRevision < 1) {
@@ -10740,7 +10922,7 @@ async function pmTaskLifecycle(ctx, id, action2, parsed) {
10740
10922
  const res = action2 === "ready" ? await pmRequest(
10741
10923
  ctx,
10742
10924
  "PATCH",
10743
- `/task/${encodeURIComponent(id)}`,
10925
+ `/task/${encodeURIComponent(id2)}`,
10744
10926
  {
10745
10927
  patch: {
10746
10928
  ...collectEntityFields("task", parsed, true),
@@ -10752,12 +10934,12 @@ async function pmTaskLifecycle(ctx, id, action2, parsed) {
10752
10934
  ) : await pmRequest(
10753
10935
  ctx,
10754
10936
  "POST",
10755
- `/task/${encodeURIComponent(id)}/${action2}`,
10937
+ `/task/${encodeURIComponent(id2)}/${action2}`,
10756
10938
  { expectedRevision, mutationId }
10757
10939
  );
10758
10940
  emit2(ctx, res, () => {
10759
10941
  const state2 = res.record ? statusCol("task", res.record) : action2;
10760
- const label = res.record ? studioRecordLink(ctx, "task", res.record) : id;
10942
+ const label = res.record ? studioRecordLink(ctx, "task", res.record) : id2;
10761
10943
  ctx.out.log(`task: ${label} \u2192 ${state2}`);
10762
10944
  });
10763
10945
  }
@@ -10786,9 +10968,9 @@ async function pmNext(ctx, parsed) {
10786
10968
  const result = {
10787
10969
  appId,
10788
10970
  projectId,
10789
- openGoals: goals.filter((record9) => record9.status === "open"),
10790
- doing: tasks.filter((record9) => record9.column === "doing"),
10791
- ready: tasks.filter((record9) => record9.column === "todo")
10971
+ openGoals: goals.filter((record11) => record11.status === "open"),
10972
+ doing: tasks.filter((record11) => record11.column === "doing"),
10973
+ ready: tasks.filter((record11) => record11.column === "todo")
10792
10974
  };
10793
10975
  emit2(ctx, result, () => {
10794
10976
  ctx.out.log(`${appId}: goal-aligned work intake (read only)`);
@@ -10799,10 +10981,10 @@ async function pmNext(ctx, parsed) {
10799
10981
  ]) {
10800
10982
  ctx.out.log(`${label}:`);
10801
10983
  if (!records.length) ctx.out.log("- (none)");
10802
- else for (const record9 of records) printRecord(
10984
+ else for (const record11 of records) printRecord(
10803
10985
  ctx,
10804
10986
  label === "open goals" ? "goal" : "task",
10805
- record9
10987
+ record11
10806
10988
  );
10807
10989
  }
10808
10990
  if (!result.openGoals.length) {
@@ -10826,9 +11008,9 @@ async function pmHandoff(ctx, parsed) {
10826
11008
  const handoff = {
10827
11009
  appId,
10828
11010
  projectId,
10829
- unmetGoals: goals.filter((record9) => record9.status !== "met"),
10830
- activeTasks: tasks.filter((record9) => record9.column !== "done"),
10831
- openBugs: bugs.filter((record9) => record9.status !== "fixed" && record9.status !== "wontfix")
11011
+ unmetGoals: goals.filter((record11) => record11.status !== "met"),
11012
+ activeTasks: tasks.filter((record11) => record11.column !== "done"),
11013
+ openBugs: bugs.filter((record11) => record11.status !== "fixed" && record11.status !== "wontfix")
10832
11014
  };
10833
11015
  const result = {
10834
11016
  ...handoff,
@@ -10847,45 +11029,45 @@ async function pmHandoff(ctx, parsed) {
10847
11029
  ]) {
10848
11030
  ctx.out.log(`${label}:`);
10849
11031
  if (!records.length) ctx.out.log("- (none)");
10850
- else for (const record9 of records) printRecord(
11032
+ else for (const record11 of records) printRecord(
10851
11033
  ctx,
10852
11034
  label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
10853
- record9
11035
+ record11
10854
11036
  );
10855
11037
  }
10856
11038
  });
10857
11039
  }
10858
- async function pmRemove(ctx, entity, id) {
10859
- await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id)}`);
10860
- ctx.out.log(`deleted ${entity} ${id}`);
11040
+ async function pmRemove(ctx, entity, id2) {
11041
+ await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id2)}`);
11042
+ ctx.out.log(`deleted ${entity} ${id2}`);
10861
11043
  }
10862
11044
 
10863
11045
  // src/pm-links.ts
10864
- async function pmLink(ctx, entity, id) {
10865
- const { record: record9 } = await pmRequest(
11046
+ async function pmLink(ctx, entity, id2) {
11047
+ const { record: record11 } = await pmRequest(
10866
11048
  ctx,
10867
11049
  "GET",
10868
- `/${entity}/${encodeURIComponent(id)}`
11050
+ `/${entity}/${encodeURIComponent(id2)}`
10869
11051
  );
10870
- const url = studioRecordUrl(ctx, entity, record9.id);
10871
- const markdown = studioRecordLink(ctx, entity, record9);
10872
- emit2(ctx, { kind: entity, id: record9.id, label: record9.title ?? "", url, markdown }, () => {
11052
+ const url = studioRecordUrl(ctx, entity, record11.id);
11053
+ const markdown = studioRecordLink(ctx, entity, record11);
11054
+ emit2(ctx, { kind: entity, id: record11.id, label: record11.title ?? "", url, markdown }, () => {
10873
11055
  ctx.out.log(markdown);
10874
11056
  });
10875
11057
  }
10876
11058
 
10877
11059
  // src/pm-comments.ts
10878
- async function pmComment(ctx, entity, id, parsed) {
11060
+ async function pmComment(ctx, entity, id2, parsed) {
10879
11061
  const body = stringOpt(parsed.options.body);
10880
11062
  if (!body) throw new Error('pm comment needs --body "..."');
10881
- await pmRequest(ctx, "POST", `/${entity}/${encodeURIComponent(id)}/comments`, {
11063
+ await pmRequest(ctx, "POST", `/${entity}/${encodeURIComponent(id2)}/comments`, {
10882
11064
  body,
10883
11065
  mutationId: writeMutationId2(parsed)
10884
11066
  });
10885
- ctx.out.log(`commented on ${entity} ${id}`);
11067
+ ctx.out.log(`commented on ${entity} ${id2}`);
10886
11068
  }
10887
- async function pmComments(ctx, entity, id) {
10888
- const { messages } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}/comments`);
11069
+ async function pmComments(ctx, entity, id2) {
11070
+ const { messages } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id2)}/comments`);
10889
11071
  emit2(ctx, messages, () => {
10890
11072
  if (messages.length === 0) ctx.out.log("(no comments)");
10891
11073
  else for (const message2 of messages) {
@@ -10903,12 +11085,12 @@ function fieldLine(change) {
10903
11085
  const before = change.before.length > 60 ? `${change.before.slice(0, 60)}\u2026` : change.before;
10904
11086
  return `${change.field} (was: ${before.replace(/\s+/g, " ")})`;
10905
11087
  }
10906
- async function pmHistory(ctx, entity, id, parsed) {
11088
+ async function pmHistory(ctx, entity, id2, parsed) {
10907
11089
  const limit = numberOpt(parsed.options.limit, "--limit");
10908
11090
  const page2 = await pmRequest(
10909
11091
  ctx,
10910
11092
  "GET",
10911
- `/${entity}/${encodeURIComponent(id)}/history${limit === void 0 ? "" : `?limit=${limit}`}`
11093
+ `/${entity}/${encodeURIComponent(id2)}/history${limit === void 0 ? "" : `?limit=${limit}`}`
10912
11094
  );
10913
11095
  emit2(ctx, page2, () => {
10914
11096
  if (!page2.entries.length) {
@@ -10996,16 +11178,16 @@ async function page(ctx, appId, cursor) {
10996
11178
  }
10997
11179
  return data;
10998
11180
  }
10999
- function recordState(record9) {
11000
- if (record9.column) return record9.column === "todo" ? "ready" : record9.column;
11001
- return String(record9.status ?? "");
11181
+ function recordState(record11) {
11182
+ if (record11.column) return record11.column === "todo" ? "ready" : record11.column;
11183
+ return String(record11.status ?? "");
11002
11184
  }
11003
11185
  function eventRecord(event) {
11004
11186
  return event.payload.payload;
11005
11187
  }
11006
11188
  function eventLabel(event) {
11007
- const record9 = eventRecord(event);
11008
- if (record9) return String(record9.title ?? event.payload.entityId);
11189
+ const record11 = eventRecord(event);
11190
+ if (record11) return String(record11.title ?? event.payload.entityId);
11009
11191
  const body = event.payload.message?.body?.replace(/\s+/g, " ").trim();
11010
11192
  return body || event.payload.entityId;
11011
11193
  }
@@ -11013,10 +11195,10 @@ function report2(ctx, parsed, result) {
11013
11195
  if (ctx.json) ctx.out.log(JSON.stringify(result, null, 2));
11014
11196
  else if (parsed.options.jsonl !== true && result.found) {
11015
11197
  for (const event of result.events ?? []) {
11016
- const record9 = eventRecord(event);
11017
- const state2 = record9 ? recordState(record9) : "comment";
11198
+ const record11 = eventRecord(event);
11199
+ const state2 = record11 ? recordState(record11) : "comment";
11018
11200
  ctx.out.log(
11019
- `${event.id} ${event.type} ${state2}${record9?.revision ? `; r${record9.revision}` : ""} ${eventLabel(event)}`
11201
+ `${event.id} ${event.type} ${state2}${record11?.revision ? `; r${record11.revision}` : ""} ${eventLabel(event)}`
11020
11202
  );
11021
11203
  }
11022
11204
  }
@@ -11090,8 +11272,8 @@ async function pmWatch(ctx, parsed) {
11090
11272
  }
11091
11273
  firstSuccess = false;
11092
11274
  const matching = current.events.filter((event) => {
11093
- const record9 = eventRecord(event);
11094
- const state2 = record9 ? recordState(record9).toLowerCase() : "";
11275
+ const record11 = eventRecord(event);
11276
+ const state2 = record11 ? recordState(record11).toLowerCase() : "";
11095
11277
  return (!entity || event.payload.entityKind === entity) && (!action2 || event.payload.action === action2) && (!wantedState || state2 === wantedState || wantedState === "todo" && state2 === "ready") && (!by || event.actor.id === by) && (!self || event.actor.id !== self);
11096
11278
  });
11097
11279
  for (const event of matching) {
@@ -11179,9 +11361,9 @@ async function pmProjectAdd(ctx, parsed) {
11179
11361
  });
11180
11362
  emit2(ctx, result, () => ctx.out.log(`created project: ${result.project.name} (${result.project.id})`));
11181
11363
  }
11182
- async function pmProjectUse(ctx, id) {
11364
+ async function pmProjectUse(ctx, id2) {
11183
11365
  if (!ctx.rootDir) throw new Error("pm project use needs a local project directory");
11184
- const { project } = await pmRequest(ctx, "GET", `/project/${encodeURIComponent(id)}`);
11366
+ const { project } = await pmRequest(ctx, "GET", `/project/${encodeURIComponent(id2)}`);
11185
11367
  if (project.status !== "active") throw new Error(`project ${project.name} is ${project.status}, not active`);
11186
11368
  writePmProjectContext(ctx.rootDir, { appId: project.appId, projectId: project.id });
11187
11369
  emit2(ctx, project, () => ctx.out.log(`using ${project.appId} / ${project.name} (${project.id}) in this worktree`));
@@ -11249,9 +11431,9 @@ function allowedOptions(entity, action2) {
11249
11431
  const entityOptions = action2 === "list" || action2 === "add" || action2 === "set" || action2 === "done" ? ENTITY_OPTIONS[entity][action2] : [];
11250
11432
  return [...COMMON_OPTIONS, ...ACTION_OPTIONS[action2], ...entityOptions];
11251
11433
  }
11252
- function requireId2(id, action2) {
11253
- if (!id) throw new Error(`"pm ... ${action2}" needs an item id`);
11254
- return id;
11434
+ function requireId2(id2, action2) {
11435
+ if (!id2) throw new Error(`"pm ... ${action2}" needs an item id`);
11436
+ return id2;
11255
11437
  }
11256
11438
  async function buildContext2(parsed, deps) {
11257
11439
  const context = await resolveOperatorContext(parsed, {
@@ -11342,34 +11524,34 @@ async function pmCommand(parsed, deps = {}) {
11342
11524
  throw new Error(`pm ${action2} is only valid for tasks`);
11343
11525
  }
11344
11526
  const ctx = await buildContext2(parsed, deps);
11345
- const id = parsed.positionals[3];
11527
+ const id2 = parsed.positionals[3];
11346
11528
  switch (action2) {
11347
11529
  case "list":
11348
11530
  return pmList(ctx, entity, parsed);
11349
11531
  case "add":
11350
11532
  return pmAdd(ctx, entity, parsed);
11351
11533
  case "get":
11352
- return pmGet(ctx, entity, requireId2(id, action2));
11534
+ return pmGet(ctx, entity, requireId2(id2, action2));
11353
11535
  case "set":
11354
- return pmSet(ctx, entity, requireId2(id, action2), parsed);
11536
+ return pmSet(ctx, entity, requireId2(id2, action2), parsed);
11355
11537
  case "done":
11356
- return pmDone(ctx, entity, requireId2(id, action2), parsed);
11538
+ return pmDone(ctx, entity, requireId2(id2, action2), parsed);
11357
11539
  case "comment":
11358
- return pmComment(ctx, entity, requireId2(id, action2), parsed);
11540
+ return pmComment(ctx, entity, requireId2(id2, action2), parsed);
11359
11541
  case "comments":
11360
- return pmComments(ctx, entity, requireId2(id, action2));
11542
+ return pmComments(ctx, entity, requireId2(id2, action2));
11361
11543
  case "history":
11362
- return pmHistory(ctx, entity, requireId2(id, action2), parsed);
11544
+ return pmHistory(ctx, entity, requireId2(id2, action2), parsed);
11363
11545
  case "rm":
11364
- return pmRemove(ctx, entity, requireId2(id, action2));
11546
+ return pmRemove(ctx, entity, requireId2(id2, action2));
11365
11547
  case "link":
11366
- return pmLink(ctx, entity, requireId2(id, action2));
11548
+ return pmLink(ctx, entity, requireId2(id2, action2));
11367
11549
  case "ref":
11368
- return pmReference(ctx, entity, requireId2(id, action2));
11550
+ return pmReference(ctx, entity, requireId2(id2, action2));
11369
11551
  case "ready":
11370
11552
  case "claim":
11371
11553
  case "release":
11372
- return pmTaskLifecycle(ctx, requireId2(id, action2), action2, parsed);
11554
+ return pmTaskLifecycle(ctx, requireId2(id2, action2), action2, parsed);
11373
11555
  }
11374
11556
  }
11375
11557
 
@@ -11472,17 +11654,17 @@ async function platformStatus(parsed, deps) {
11472
11654
  }
11473
11655
  }
11474
11656
  function isPlatformStatus(value2) {
11475
- if (!record6(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
11476
- if (!record6(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
11477
- if (!record6(value2.catalog) || !record6(value2.summary)) return false;
11657
+ if (!record7(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
11658
+ if (!record7(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
11659
+ if (!record7(value2.catalog) || !record7(value2.summary)) return false;
11478
11660
  return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
11479
11661
  }
11480
11662
  function apiMessage(value2) {
11481
- if (!record6(value2)) return "request failed";
11482
- const error = record6(value2.error) ? value2.error : value2;
11663
+ if (!record7(value2)) return "request failed";
11664
+ const error = record7(value2.error) ? value2.error : value2;
11483
11665
  return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
11484
11666
  }
11485
- function record6(value2) {
11667
+ function record7(value2) {
11486
11668
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
11487
11669
  }
11488
11670
 
@@ -11523,7 +11705,7 @@ function statusVerdict(reads) {
11523
11705
  severity: "degraded"
11524
11706
  });
11525
11707
  }
11526
- const performance = record7(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
11708
+ const performance = record8(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
11527
11709
  if (performance?.status === "unavailable") {
11528
11710
  reasons.push({
11529
11711
  source: "liveSync",
@@ -11604,7 +11786,7 @@ function statusVerdict(reads) {
11604
11786
  reasons
11605
11787
  };
11606
11788
  }
11607
- function record7(value2) {
11789
+ function record8(value2) {
11608
11790
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
11609
11791
  }
11610
11792
  function numeric2(value2) {
@@ -11632,7 +11814,7 @@ function printO11yStatus(status, out) {
11632
11814
  out.log(
11633
11815
  `o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
11634
11816
  );
11635
- const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record8) : [];
11817
+ const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record9) : [];
11636
11818
  const requests = routes.reduce(
11637
11819
  (total, row) => total + numeric3(row.requests),
11638
11820
  0
@@ -11644,39 +11826,39 @@ function printO11yStatus(status, out) {
11644
11826
  out.log(
11645
11827
  `application ${status.application.httpStatus} ${requests} requests ${errors} errors`
11646
11828
  );
11647
- const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record8) : [];
11829
+ const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record9) : [];
11648
11830
  out.log(
11649
11831
  `application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
11650
11832
  (row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
11651
11833
  ).join(", ") : "none observed"}`
11652
11834
  );
11653
11835
  out.log(liveSyncLine(status.liveSync));
11654
- const canaryDurations = record8(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
11836
+ const canaryDurations = record9(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
11655
11837
  out.log(
11656
11838
  `canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
11657
11839
  );
11658
- const collectorIngest = record8(status.collector.body.ingest) ? status.collector.body.ingest : {};
11659
- const collectorStorage = record8(collectorIngest.storage) ? collectorIngest.storage : {};
11840
+ const collectorIngest = record9(status.collector.body.ingest) ? status.collector.body.ingest : {};
11841
+ const collectorStorage = record9(collectorIngest.storage) ? collectorIngest.storage : {};
11660
11842
  out.log(
11661
11843
  `collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
11662
11844
  );
11663
- const providerMetrics = record8(status.provider.body.metrics) ? status.provider.body.metrics : {};
11664
- const providerCapacity = record8(status.provider.body.capacity) ? status.provider.body.capacity : {};
11665
- const workerMemory = record8(providerCapacity.memory) ? providerCapacity.memory : {};
11845
+ const providerMetrics = record9(status.provider.body.metrics) ? status.provider.body.metrics : {};
11846
+ const providerCapacity = record9(status.provider.body.capacity) ? status.provider.body.capacity : {};
11847
+ const workerMemory = record9(providerCapacity.memory) ? providerCapacity.memory : {};
11666
11848
  out.log(
11667
11849
  `cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors ${optionalBytes(workerMemory.headroomBytes)} isolate memory headroom`
11668
11850
  );
11669
11851
  for (const line of providerCapacityLines(status.providerCapacity)) {
11670
11852
  out.log(line);
11671
11853
  }
11672
- const coverage = record8(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
11673
- const coverageCounts = record8(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
11674
- const coverageBudget = record8(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
11854
+ const coverage = record9(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
11855
+ const coverageCounts = record9(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
11856
+ const coverageBudget = record9(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
11675
11857
  out.log(
11676
11858
  `request-coverage ${status.providerReconciliation.httpStatus} ${String(status.providerReconciliation.body.status ?? status.providerReconciliation.body.error ?? "unavailable")} ${optionalPercent(coverage.applicationCoverage)} application/provider ${numeric3(coverageCounts.applicationRequests)}/${numeric3(coverageCounts.providerRequests)} requests \xB1${optionalPercent(coverageBudget.maxRelativeError)} budget`
11677
11859
  );
11678
11860
  const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
11679
- const providerFreshness = record8(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
11861
+ const providerFreshness = record9(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
11680
11862
  out.log(
11681
11863
  `cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
11682
11864
  );
@@ -11685,17 +11867,17 @@ function printO11yStatus(status, out) {
11685
11867
  );
11686
11868
  }
11687
11869
  function providerCapacityLines(read3) {
11688
- const resources = record8(read3.body.resources) ? read3.body.resources : {};
11689
- const durableObjects = record8(resources.durableObjects) ? resources.durableObjects : {};
11690
- const periodic = record8(durableObjects.periodic) ? durableObjects.periodic : {};
11691
- const storage = record8(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
11692
- const d1 = record8(resources.d1) ? resources.d1 : {};
11693
- const d1Activity = record8(d1.activity) ? d1.activity : {};
11694
- const d1Storage = record8(d1.storage) ? d1.storage : {};
11695
- const d1Latency = record8(d1Activity.latency) ? d1Activity.latency : {};
11696
- const r2 = record8(resources.r2) ? resources.r2 : {};
11697
- const r2Operations = record8(r2.operations) ? r2.operations : {};
11698
- const r2Storage = record8(r2.storage) ? r2.storage : {};
11870
+ const resources = record9(read3.body.resources) ? read3.body.resources : {};
11871
+ const durableObjects = record9(resources.durableObjects) ? resources.durableObjects : {};
11872
+ const periodic = record9(durableObjects.periodic) ? durableObjects.periodic : {};
11873
+ const storage = record9(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
11874
+ const d1 = record9(resources.d1) ? resources.d1 : {};
11875
+ const d1Activity = record9(d1.activity) ? d1.activity : {};
11876
+ const d1Storage = record9(d1.storage) ? d1.storage : {};
11877
+ const d1Latency = record9(d1Activity.latency) ? d1Activity.latency : {};
11878
+ const r2 = record9(resources.r2) ? resources.r2 : {};
11879
+ const r2Operations = record9(r2.operations) ? r2.operations : {};
11880
+ const r2Storage = record9(r2.storage) ? r2.storage : {};
11699
11881
  const status = String(
11700
11882
  read3.body.status ?? read3.body.error ?? "unavailable"
11701
11883
  );
@@ -11706,11 +11888,11 @@ function providerCapacityLines(read3) {
11706
11888
  ];
11707
11889
  }
11708
11890
  function liveSyncLine(read3) {
11709
- const performance = record8(read3.body.performance) ? read3.body.performance : {};
11710
- const commitToSend = record8(performance.commitToSend) ? performance.commitToSend : {};
11891
+ const performance = record9(read3.body.performance) ? read3.body.performance : {};
11892
+ const commitToSend = record9(performance.commitToSend) ? performance.commitToSend : {};
11711
11893
  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`;
11712
11894
  }
11713
- function record8(value2) {
11895
+ function record9(value2) {
11714
11896
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
11715
11897
  }
11716
11898
  function numeric3(value2) {
@@ -11881,19 +12063,292 @@ function statusMinutes(value2) {
11881
12063
  }
11882
12064
  async function read2(url, headers, doFetch) {
11883
12065
  const response2 = await doFetch(url, { headers });
11884
- const text2 = await response2.text();
12066
+ const text3 = await response2.text();
11885
12067
  let body = {};
11886
- if (text2) {
12068
+ if (text3) {
11887
12069
  try {
11888
- const value2 = JSON.parse(text2);
12070
+ const value2 = JSON.parse(text3);
11889
12071
  body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
11890
12072
  } catch {
11891
- body = { message: text2.slice(0, 300) };
12073
+ body = { message: text3.slice(0, 300) };
11892
12074
  }
11893
12075
  }
11894
12076
  return { httpStatus: response2.status, body };
11895
12077
  }
11896
12078
 
12079
+ // src/monitoring-config.ts
12080
+ import { createHash as createHash5 } from "crypto";
12081
+ function monitoringWireConfig(cfg, env) {
12082
+ const monitoring = cfg.o11y?.monitoring;
12083
+ if (!monitoring) throw new Error("o11y.monitoring is not configured");
12084
+ if (!cfg.services.includes("o11y")) throw new Error('o11y.monitoring requires "o11y" in services');
12085
+ const authoredLink = cfg.links?.[env];
12086
+ if (!authoredLink) throw new Error(`links.${env} is required for live monitoring`);
12087
+ const baseUrl = new URL(authoredLink).toString();
12088
+ const selectedProbes = (monitoring.probes ?? []).filter((probe) => !probe.envs || probe.envs.includes(env));
12089
+ const probeIds = new Set(selectedProbes.map((probe) => probe.id));
12090
+ const selectedSlos = monitoring.slos.filter(
12091
+ (slo) => slo.indicator.type === "o11y-metric" || slo.indicator.probes.some((id2) => probeIds.has(id2))
12092
+ );
12093
+ if (selectedSlos.length === 0) throw new Error(`o11y.monitoring has no SLOs for env "${env}"`);
12094
+ for (const slo of selectedSlos) {
12095
+ if (slo.indicator.type !== "probe-success") continue;
12096
+ const unavailable = slo.indicator.probes.filter((id2) => !probeIds.has(id2));
12097
+ if (unavailable.length) throw new Error(`SLO "${slo.id}" mixes probes unavailable in env "${env}": ${unavailable.join(", ")}`);
12098
+ }
12099
+ const payload = {
12100
+ environment: env,
12101
+ baseUrl,
12102
+ probes: selectedProbes.map(normalizeProbe),
12103
+ slos: selectedSlos.map(normalizeSlo),
12104
+ ...notification(cfg.o11y.monitoring.notifications?.[env])
12105
+ };
12106
+ const revision = `sha256:${createHash5("sha256").update(canonical(payload)).digest("hex")}`;
12107
+ return { revision, ...payload };
12108
+ }
12109
+ function normalizeProbe(probe) {
12110
+ return {
12111
+ id: probe.id,
12112
+ route: probe.route,
12113
+ cadenceMinutes: durationMinutes(probe.every),
12114
+ timeoutMs: probe.timeout ?? 2e4,
12115
+ ...probe.ready?.selector ? { readySelector: probe.ready.selector } : {},
12116
+ expect: {
12117
+ status: probe.expect.status,
12118
+ ...probe.expect.titleIncludes ? { titleIncludes: probe.expect.titleIncludes } : {},
12119
+ textIncludes: probe.expect.textIncludes ?? [],
12120
+ accessibility: probe.expect.accessibility ?? []
12121
+ },
12122
+ enabled: probe.enabled !== false
12123
+ };
12124
+ }
12125
+ function normalizeSlo(slo) {
12126
+ return {
12127
+ id: slo.id,
12128
+ name: slo.name ?? slo.id,
12129
+ indicator: normalizeIndicator(slo.indicator),
12130
+ target: slo.target,
12131
+ windowMinutes: durationMinutes(slo.window),
12132
+ spike: {
12133
+ badChecks: slo.alerts?.spike?.badChecks ?? 2,
12134
+ withinChecks: slo.alerts?.spike?.withinChecks ?? 3,
12135
+ recoverAfter: slo.alerts?.spike?.recoverAfter ?? 2
12136
+ },
12137
+ trend: {
12138
+ burnRate: slo.alerts?.trend?.burnRate ?? 1,
12139
+ shortMinutes: durationMinutes(slo.alerts?.trend?.shortWindow ?? "6h"),
12140
+ longMinutes: durationMinutes(slo.alerts?.trend?.longWindow ?? "3d"),
12141
+ minBadChecks: slo.alerts?.trend?.minBadChecks ?? 2
12142
+ },
12143
+ enabled: slo.enabled !== false
12144
+ };
12145
+ }
12146
+ function normalizeIndicator(indicator) {
12147
+ if (indicator.type === "probe-success") {
12148
+ return { type: "probe-success", probes: [...new Set(indicator.probes)] };
12149
+ }
12150
+ return {
12151
+ type: "o11y-metric",
12152
+ metric: indicator.metric,
12153
+ comparator: indicator.comparator,
12154
+ threshold: indicator.threshold,
12155
+ cadenceMinutes: durationMinutes(indicator.every),
12156
+ observationWindowMinutes: durationMinutes(indicator.observationWindow),
12157
+ ...indicator.route ? { route: indicator.route } : {}
12158
+ };
12159
+ }
12160
+ function notification(policy) {
12161
+ if (!policy) return {};
12162
+ return {
12163
+ notifications: {
12164
+ email: [...new Set(policy.email.map((email) => email.trim().toLowerCase()))],
12165
+ timezone: policy.timezone,
12166
+ daily: policy.daily === void 0 ? "08:00" : policy.daily,
12167
+ weekly: policy.weekly === void 0 ? { day: "monday", at: "08:00" } : policy.weekly
12168
+ }
12169
+ };
12170
+ }
12171
+ function durationMinutes(value2) {
12172
+ const match = /^(\d+)(m|h|d)$/.exec(value2);
12173
+ if (!match) throw new Error(`unsupported duration ${value2}`);
12174
+ const amount = Number(match[1]);
12175
+ return amount * (match[2] === "d" ? 1440 : match[2] === "h" ? 60 : 1);
12176
+ }
12177
+ function canonical(value2) {
12178
+ if (Array.isArray(value2)) return `[${value2.map(canonical).join(",")}]`;
12179
+ if (value2 && typeof value2 === "object") {
12180
+ return `{${Object.entries(value2).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`).join(",")}}`;
12181
+ }
12182
+ return JSON.stringify(value2);
12183
+ }
12184
+
12185
+ // src/monitor-command.ts
12186
+ var OPTIONS = [
12187
+ "config",
12188
+ "context",
12189
+ "platform",
12190
+ "token",
12191
+ "email",
12192
+ "json",
12193
+ "app",
12194
+ "env",
12195
+ "open",
12196
+ "yes",
12197
+ "period",
12198
+ "limit",
12199
+ "runs"
12200
+ ];
12201
+ async function monitorCommand(parsed, deps = {}) {
12202
+ assertArgs(parsed, OPTIONS, 3);
12203
+ const action2 = parsed.positionals[1] ?? "status";
12204
+ if (!["plan", "apply", "run", "status", "incidents", "report"].includes(action2)) {
12205
+ throw new Error(`unknown monitor action "${action2}". Try "odla-ai monitor status --json".`);
12206
+ }
12207
+ const context = await resolveOperatorContext(parsed, {
12208
+ allowMissingConfig: action2 !== "plan" && action2 !== "apply",
12209
+ requireApp: true
12210
+ });
12211
+ if ((action2 === "plan" || action2 === "apply") && context.config.status !== "loaded") {
12212
+ throw new Error(`monitor ${action2} requires odla.config.mjs`);
12213
+ }
12214
+ const env = context.environment.value ?? context.cfg.envs[0] ?? "prod";
12215
+ const appId = context.app.value;
12216
+ const doFetch = deps.fetch ?? fetch;
12217
+ const out = deps.stdout ?? console;
12218
+ const token = await getDeveloperToken(
12219
+ context.cfg,
12220
+ {
12221
+ configPath: context.cfg.configPath,
12222
+ token: stringOpt(parsed.options.token),
12223
+ email: stringOpt(parsed.options.email),
12224
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
12225
+ openApprovalUrl: deps.openUrl
12226
+ },
12227
+ doFetch,
12228
+ out,
12229
+ action2 === "apply" || action2 === "run" ? { optionalProjectCapabilities: ["app.manage"] } : {}
12230
+ );
12231
+ const base = `${context.cfg.platformUrl}/o11y/${encodeURIComponent(appId)}/monitoring`;
12232
+ const headers = { authorization: `Bearer ${token}`, "content-type": "application/json" };
12233
+ const jsonOutput = parsed.options.json === true;
12234
+ if (action2 === "plan" || action2 === "apply") {
12235
+ const desired = monitoringWireConfig(context.cfg, env);
12236
+ const live = await request2(`${base}?env=${encodeURIComponent(env)}`, { headers }, doFetch);
12237
+ const currentRevision = record10(live.config) ? string(live.config.revision) : null;
12238
+ const changed = currentRevision !== desired.revision;
12239
+ const plan = {
12240
+ schemaVersion: 1,
12241
+ appId,
12242
+ env,
12243
+ currentRevision,
12244
+ desiredRevision: desired.revision,
12245
+ changed,
12246
+ probes: desired.probes.map((probe) => ({ id: probe.id, route: probe.route, cadenceMinutes: probe.cadenceMinutes })),
12247
+ slos: desired.slos.map((slo) => ({ id: slo.id, indicator: slo.indicator, target: slo.target, windowMinutes: slo.windowMinutes })),
12248
+ notifications: desired.notifications ? { recipients: desired.notifications.email.length, timezone: desired.notifications.timezone, daily: desired.notifications.daily, weekly: desired.notifications.weekly } : null
12249
+ };
12250
+ if (action2 === "plan") {
12251
+ emit3(plan, jsonOutput, out, () => {
12252
+ out.log(`monitor plan ${appId}/${env}: ${changed ? "changes pending" : "in sync"}`);
12253
+ out.log(`revision ${currentRevision ?? "not configured"} -> ${desired.revision}`);
12254
+ for (const probe of desired.probes) out.log(`probe ${probe.id} ${probe.route} every ${probe.cadenceMinutes}m`);
12255
+ for (const slo of desired.slos) out.log(`slo ${slo.id} ${slo.indicator.type} ${(slo.target * 100).toFixed(3)}% ${slo.windowMinutes}m`);
12256
+ });
12257
+ return;
12258
+ }
12259
+ if ((env === "prod" || env === "production") && parsed.options.yes !== true) {
12260
+ throw new Error(`refusing to apply live monitoring for "${env}" without --yes; run monitor plan first`);
12261
+ }
12262
+ if (!changed) {
12263
+ emit3({ ...plan, applied: false }, jsonOutput, out, () => out.log(`monitor apply ${appId}/${env}: already in sync`));
12264
+ return;
12265
+ }
12266
+ const applied = await request2(`${base}?env=${encodeURIComponent(env)}`, {
12267
+ method: "PUT",
12268
+ headers,
12269
+ body: JSON.stringify(desired)
12270
+ }, doFetch);
12271
+ emit3({ schemaVersion: 1, appId, env, ...applied }, jsonOutput, out, () => out.log(`monitor apply ${appId}/${env}: ${applied.changed === true ? "applied" : "unchanged"} ${desired.revision}`));
12272
+ return;
12273
+ }
12274
+ if (action2 === "run") {
12275
+ const probeId = parsed.positionals[2];
12276
+ if (!probeId) throw new Error("monitor run requires a probe id");
12277
+ const result2 = await request2(`${base}/probes/${encodeURIComponent(probeId)}/run?env=${encodeURIComponent(env)}`, {
12278
+ method: "POST",
12279
+ headers
12280
+ }, doFetch);
12281
+ emit3(result2, jsonOutput, out, () => {
12282
+ const run = record10(result2.run) ? result2.run : {};
12283
+ out.log(`monitor run ${appId}/${env}/${probeId}: ${string(run.outcome) ?? "unknown"}${run.failure_code ? ` (${String(run.failure_code)})` : ""}`);
12284
+ });
12285
+ return;
12286
+ }
12287
+ let path = action2;
12288
+ if (action2 === "report") {
12289
+ const period = stringOpt(parsed.options.period) ?? "daily";
12290
+ if (period !== "daily" && period !== "weekly") throw new Error("--period must be daily or weekly");
12291
+ path = `report?period=${period}`;
12292
+ } else if (action2 === "incidents") {
12293
+ const params = new URLSearchParams({ limit: String(numberOpt(parsed.options.limit, "--limit") ?? 100) });
12294
+ if (boolOpt(parsed.options.runs) === true) params.set("runs", "true");
12295
+ path = `incidents?${params}`;
12296
+ }
12297
+ const separator = path.includes("?") ? "&" : "?";
12298
+ const result = await request2(`${base}/${path}${separator}env=${encodeURIComponent(env)}`, { headers }, doFetch);
12299
+ emit3(result, jsonOutput, out, () => printRead(action2, appId, env, result, out));
12300
+ }
12301
+ async function request2(url, init, doFetch) {
12302
+ const response2 = await doFetch(url, init);
12303
+ const text3 = await response2.text();
12304
+ let body = {};
12305
+ try {
12306
+ const parsed = text3 ? JSON.parse(text3) : {};
12307
+ body = record10(parsed) ? parsed : { value: parsed };
12308
+ } catch {
12309
+ body = { message: text3.slice(0, 500) };
12310
+ }
12311
+ if (!response2.ok) {
12312
+ const error = record10(body.error) ? body.error : body;
12313
+ throw new Error(string(error.message) ?? string(error.code) ?? `monitor request failed (${response2.status})`);
12314
+ }
12315
+ return body;
12316
+ }
12317
+ function printRead(action2, appId, env, result, out) {
12318
+ if (action2 === "status") {
12319
+ out.log(`monitor status ${appId}/${env}: ${String(result.overall ?? (result.configured === false ? "not configured" : "unknown"))}`);
12320
+ const slos = Array.isArray(result.slos) ? result.slos.filter(record10) : [];
12321
+ for (const slo of slos) out.log(`slo ${String(slo.id)} ${String(slo.state)} ${percent(slo.observed)} observed ${percent(slo.budgetRemaining)} budget remaining`);
12322
+ const incidents = Array.isArray(result.openIncidents) ? result.openIncidents.length : 0;
12323
+ const gaps = Array.isArray(result.monitoringGaps) ? result.monitoringGaps.length : 0;
12324
+ out.log(`open incidents ${incidents}`);
12325
+ out.log(`monitoring gaps ${gaps}`);
12326
+ return;
12327
+ }
12328
+ if (action2 === "incidents") {
12329
+ const incidents = Array.isArray(result.incidents) ? result.incidents.filter(record10) : [];
12330
+ out.log(`monitor incidents ${appId}/${env}: ${incidents.length}`);
12331
+ for (const incident2 of incidents) out.log(`${String(incident2.state)} ${String(incident2.kind)} ${String(incident2.slo_id)} ${new Date(Number(incident2.opened_at)).toISOString()}`);
12332
+ return;
12333
+ }
12334
+ out.log(`monitor report ${appId}/${env}: ${String(result.period)} ${String(result.overall)}`);
12335
+ const probes = Array.isArray(result.probes) ? result.probes.filter(record10) : [];
12336
+ for (const probe of probes) out.log(`probe ${String(probe.id)} ${Number(probe.good)} good ${Number(probe.bad)} bad ${Number(probe.unknown)} unknown`);
12337
+ }
12338
+ function emit3(value2, json, out, human) {
12339
+ if (json) out.log(JSON.stringify(value2, null, 2));
12340
+ else human();
12341
+ }
12342
+ function record10(value2) {
12343
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
12344
+ }
12345
+ function string(value2) {
12346
+ return typeof value2 === "string" ? value2 : null;
12347
+ }
12348
+ function percent(value2) {
12349
+ return typeof value2 === "number" && Number.isFinite(value2) ? `${(value2 * 100).toFixed(2)}%` : "unknown";
12350
+ }
12351
+
11897
12352
  // src/provision.ts
11898
12353
  import { AppsError as AppsError2, createAppsClient as createAppsClient3, orderAppServices as orderAppServices3, tenantIdFor as tenantIdFor6 } from "@odla-ai/apps";
11899
12354
  import { putSecret as putSecret2 } from "@odla-ai/ai";
@@ -12052,8 +12507,8 @@ function runtimeUrl(cfg, suffix = "") {
12052
12507
  return `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials${suffix}`;
12053
12508
  }
12054
12509
  async function safeError(response2) {
12055
- const text2 = await response2.text();
12056
- return redactSecrets(text2.slice(0, 1e3));
12510
+ const text3 = await response2.text();
12511
+ return redactSecrets(text3.slice(0, 1e3));
12057
12512
  }
12058
12513
  async function finish(doFetch, cfg, token, sessionId, method) {
12059
12514
  return doFetch(runtimeUrl(cfg, `/${encodeURIComponent(sessionId)}`), {
@@ -12456,6 +12911,7 @@ var COMMAND_SURFACE = {
12456
12911
  doctor: {},
12457
12912
  help: {},
12458
12913
  init: {},
12914
+ monitor: { plan: {}, apply: {}, run: {}, status: {}, incidents: {}, report: {} },
12459
12915
  o11y: { status: {} },
12460
12916
  operations: { get: {}, wait: {} },
12461
12917
  platform: {
@@ -12696,12 +13152,12 @@ async function runbookRemove(ctx, slug) {
12696
13152
  // src/runbook-import.ts
12697
13153
  import { readFileSync as readFileSync10, readdirSync as readdirSync2, statSync } from "fs";
12698
13154
  import { basename as basename2, join as join13 } from "path";
12699
- function parseRunbook(text2, slug) {
12700
- let rest = text2;
13155
+ function parseRunbook(text3, slug) {
13156
+ let rest = text3;
12701
13157
  const meta = {};
12702
- const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text2);
13158
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text3);
12703
13159
  if (fm) {
12704
- rest = text2.slice(fm[0].length);
13160
+ rest = text3.slice(fm[0].length);
12705
13161
  for (const line of fm[1].split(/\r?\n/)) {
12706
13162
  const pair = /^(\w+)\s*:\s*(.+)$/.exec(line.trim());
12707
13163
  if (!pair) continue;
@@ -13470,9 +13926,9 @@ function printHostedSecurityIntent(out, intent) {
13470
13926
  }
13471
13927
  function assertHostedSecurityPlanReady(plan) {
13472
13928
  const reasons = [];
13473
- for (const [label, route2] of Object.entries(plan.routes)) {
13474
- if (!route2.enabled) reasons.push(`${label} is disabled`);
13475
- if (!route2.credentialReady) reasons.push(`${label} provider credential is unavailable`);
13929
+ for (const [label, route3] of Object.entries(plan.routes)) {
13930
+ if (!route3.enabled) reasons.push(`${label} is disabled`);
13931
+ if (!route3.credentialReady) reasons.push(`${label} provider credential is unavailable`);
13476
13932
  }
13477
13933
  if (!plan.independent) reasons.push("discovery and validation are not independently routed");
13478
13934
  if (plan.ready && reasons.length === 0) return;
@@ -13526,20 +13982,20 @@ function enforceHostedReportGate(report4, parsed, out, emitSuccess) {
13526
13982
  out.log(`security gate passed: 0 confirmed >= ${failOn}; 0 leads >= ${failOnCandidates ?? "disabled"}; coverage ${report4.coverageStatus}. This is not proof that the application is secure.`);
13527
13983
  }
13528
13984
  }
13529
- function printHostedSecurityPlanRoute(out, label, route2) {
13530
- const readiness = route2.enabled && route2.credentialReady ? "ready" : [
13531
- route2.enabled ? void 0 : "disabled",
13532
- route2.credentialReady ? void 0 : "credential unavailable"
13985
+ function printHostedSecurityPlanRoute(out, label, route3) {
13986
+ const readiness = route3.enabled && route3.credentialReady ? "ready" : [
13987
+ route3.enabled ? void 0 : "disabled",
13988
+ route3.credentialReady ? void 0 : "credential unavailable"
13533
13989
  ].filter(Boolean).join(", ");
13534
- out.log(` ${label}: ${route2.provider}/${route2.model} \xB7 policy v${route2.policyVersion} \xB7 ${readiness}`);
13535
- out.log(` bounds: ${route2.maxCallsPerRun} calls/run \xB7 ${route2.maxInputBytes} input bytes/call \xB7 ${route2.maxOutputTokens} output tokens/call`);
13990
+ out.log(` ${label}: ${route3.provider}/${route3.model} \xB7 policy v${route3.policyVersion} \xB7 ${readiness}`);
13991
+ out.log(` bounds: ${route3.maxCallsPerRun} calls/run \xB7 ${route3.maxInputBytes} input bytes/call \xB7 ${route3.maxOutputTokens} output tokens/call`);
13536
13992
  }
13537
13993
  function printHostedCoverage(out, job) {
13538
13994
  const coverage = job.coverage;
13539
13995
  out.log(` coverage: ${job.coverageStatus ?? "pending"}${coverage?.completeCells !== void 0 ? ` ${coverage.completeCells}/${coverage.totalCells ?? "?"}` : ""}${coverage?.shallowCells ? ` shallow=${coverage.shallowCells}` : ""}${coverage?.blockedCells ? ` blocked=${coverage.blockedCells}` : ""}${coverage?.unscheduledCells ? ` unscheduled=${coverage.unscheduledCells}` : ""}${coverage?.budgetExhaustedCells ? ` budget_exhausted=${coverage.budgetExhaustedCells}` : ""}`);
13540
13996
  }
13541
- function routeLabel(route2) {
13542
- return `${route2.provider}/${route2.model}${route2.policyVersion ? ` policy v${route2.policyVersion}` : ""}`;
13997
+ function routeLabel(route3) {
13998
+ return `${route3.provider}/${route3.model}${route3.policyVersion ? ` policy v${route3.policyVersion}` : ""}`;
13543
13999
  }
13544
14000
  var HOSTED_SEVERITIES = ["informational", "low", "medium", "high", "critical"];
13545
14001
  function hostedSeverity(value2, flag) {
@@ -13633,11 +14089,11 @@ function selectEnv(requested, declared, configPath, rootDir) {
13633
14089
  }
13634
14090
  return env;
13635
14091
  }
13636
- async function injectedToken(options, request2) {
13637
- const value2 = options.token ?? await options.getToken?.(Object.freeze({ ...request2 }));
14092
+ async function injectedToken(options, request3) {
14093
+ const value2 = options.token ?? await options.getToken?.(Object.freeze({ ...request3 }));
13638
14094
  if (typeof value2 !== "string" || value2.length < 8 || value2.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value2)) {
13639
14095
  throw new Error(
13640
- request2.selfAudit ? "Self-audit requires an injected, scoped platform security token" : "Hosted security requires an injected app developer token or getToken callback"
14096
+ request3.selfAudit ? "Self-audit requires an injected, scoped platform security token" : "Hosted security requires an injected app developer token or getToken callback"
13641
14097
  );
13642
14098
  }
13643
14099
  return value2;
@@ -13950,11 +14406,11 @@ async function runLocalSecurityCommand(parsed, dependencies) {
13950
14406
  sourceDisclosureAck: parsed.options["ack-redacted-source"] === true ? "redacted" : void 0,
13951
14407
  fetch: doFetch,
13952
14408
  stdout: out,
13953
- getToken: async (request2) => {
13954
- if (request2.scope === "platform:security:self") {
14409
+ getToken: async (request3) => {
14410
+ if (request3.scope === "platform:security:self") {
13955
14411
  return getScopedPlatformToken({
13956
- platform: request2.platform,
13957
- scope: request2.scope,
14412
+ platform: request3.platform,
14413
+ scope: request3.scope,
13958
14414
  email: stringOpt(parsed.options.email),
13959
14415
  open,
13960
14416
  fetch: doFetch,
@@ -13963,7 +14419,7 @@ async function runLocalSecurityCommand(parsed, dependencies) {
13963
14419
  });
13964
14420
  }
13965
14421
  const cfg = await loadProjectConfig(configPath);
13966
- if (platformAudience(cfg.platformUrl) !== platformAudience(request2.platform)) {
14422
+ if (platformAudience(cfg.platformUrl) !== platformAudience(request3.platform)) {
13967
14423
  throw new Error("--platform cannot reuse a project developer token from another platform; update odla.config.mjs and authenticate there");
13968
14424
  }
13969
14425
  return getDeveloperToken(
@@ -14168,10 +14624,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
14168
14624
  }
14169
14625
  if (command === "bug") {
14170
14626
  const action2 = parsed.positionals[1] ?? "list";
14171
- const canonical = action2 === "report" || action2 === "create" ? "add" : action2;
14627
+ const canonical2 = action2 === "report" || action2 === "create" ? "add" : action2;
14172
14628
  await pmCommand({
14173
14629
  ...parsed,
14174
- positionals: ["pm", "bug", canonical, ...parsed.positionals.slice(2)]
14630
+ positionals: ["pm", "bug", canonical2, ...parsed.positionals.slice(2)]
14175
14631
  }, runtime);
14176
14632
  return;
14177
14633
  }
@@ -14183,6 +14639,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
14183
14639
  await o11yCommand(parsed, runtime);
14184
14640
  return;
14185
14641
  }
14642
+ if (command === "monitor") {
14643
+ await monitorCommand(parsed, runtime);
14644
+ return;
14645
+ }
14186
14646
  if (command === "platform") {
14187
14647
  await platformCommand(parsed, runtime);
14188
14648
  return;
@@ -14297,6 +14757,8 @@ export {
14297
14757
  CODE_BUILD_RECIPES,
14298
14758
  codeConnect,
14299
14759
  runCodeRuntime,
14760
+ monitoringWireConfig,
14761
+ monitorCommand,
14300
14762
  provision,
14301
14763
  COMMAND_SURFACE,
14302
14764
  acceptedAfter,
@@ -14315,4 +14777,4 @@ export {
14315
14777
  isTerminalHostedSecurityStatus,
14316
14778
  runCli
14317
14779
  };
14318
- //# sourceMappingURL=chunk-YSQORU5J.js.map
14780
+ //# sourceMappingURL=chunk-LGNNX6AP.js.map