@odla-ai/cli 0.32.1 → 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) {
@@ -990,6 +990,111 @@ function safeText(value2, max) {
990
990
  return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
991
991
  }
992
992
 
993
+ // src/calendar-config.ts
994
+ function calendarServiceConfig(cfg, env) {
995
+ if (!cfg.services.includes("calendar")) throw new Error("calendar service is not enabled in config services");
996
+ if (!cfg.envs.includes(env) && env !== "prod") throw new Error(`calendar env "${env}" is not declared in config envs`);
997
+ const google = cfg.calendar?.google;
998
+ if (!google) throw new Error("calendar.google is required when the calendar service is enabled");
999
+ const configured = google.availabilityCalendars?.[env] ?? google.calendars?.[env];
1000
+ if (!configured?.length) throw new Error(`calendar.google.availabilityCalendars.${env} is required`);
1001
+ const availability = unique(configured.map((id2) => id2.trim()));
1002
+ return {
1003
+ provider: "google",
1004
+ access: "book",
1005
+ bookingCalendarId: google.bookingCalendar?.[env]?.trim() ?? availability[0],
1006
+ availabilityCalendars: availability
1007
+ };
1008
+ }
1009
+ function calendarBookingPageUrl(cfg, env) {
1010
+ const value2 = cfg.calendar?.google.bookingPageUrl?.[env];
1011
+ if (value2 === void 0 || value2 === null) return value2;
1012
+ return new URL(value2).toString();
1013
+ }
1014
+ function validateCalendarConfig(cfg, envs, services, path) {
1015
+ const enabled = services.includes("calendar");
1016
+ if (!cfg.calendar) {
1017
+ if (enabled) throw new Error(`${path}: calendar.google is required when services includes "calendar"`);
1018
+ return;
1019
+ }
1020
+ if (!isRecord5(cfg.calendar)) throw new Error(`${path}: calendar must be an object`);
1021
+ assertOnly2(cfg.calendar, ["google"], `${path}: calendar`);
1022
+ if (!isRecord5(cfg.calendar.google)) throw new Error(`${path}: calendar.google must be an object`);
1023
+ const google = cfg.calendar.google;
1024
+ assertOnly2(
1025
+ google,
1026
+ ["availabilityCalendars", "calendars", "bookingCalendar", "bookingPageUrl"],
1027
+ `${path}: calendar.google`
1028
+ );
1029
+ const availabilityKey = google.availabilityCalendars !== void 0 ? "availabilityCalendars" : google.calendars !== void 0 ? "calendars" : null;
1030
+ if (!availabilityKey || google.availabilityCalendars !== void 0 && google.calendars !== void 0) {
1031
+ throw new Error(`${path}: calendar.google requires exactly one of availabilityCalendars or calendars (legacy)`);
1032
+ }
1033
+ const availability = google[availabilityKey];
1034
+ if (!isRecord5(availability)) throw new Error(`${path}: calendar.google.${availabilityKey} must map env names to calendar ids`);
1035
+ const unknownEnv = Object.keys(availability).find((env) => !envs.includes(env) && env !== "prod");
1036
+ if (unknownEnv) throw new Error(`${path}: calendar.google.${availabilityKey}.${unknownEnv} is not in config envs`);
1037
+ for (const env of envs) {
1038
+ const ids = availability[env];
1039
+ if (!Array.isArray(ids) || ids.length === 0) {
1040
+ throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
1041
+ }
1042
+ }
1043
+ for (const [env, ids] of Object.entries(availability)) {
1044
+ if (!Array.isArray(ids) || ids.length === 0) {
1045
+ throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
1046
+ }
1047
+ if (ids.length > 10) {
1048
+ throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must contain at most 10 calendar ids`);
1049
+ }
1050
+ if (ids.some((id2) => !safeText2(id2, 1024))) {
1051
+ throw new Error(`${path}: calendar.google.${availabilityKey}.${env} contains an invalid calendar id`);
1052
+ }
1053
+ }
1054
+ if (google.bookingCalendar !== void 0) {
1055
+ if (!isRecord5(google.bookingCalendar)) throw new Error(`${path}: calendar.google.bookingCalendar must map env names to one calendar id`);
1056
+ const unknownBookingEnv = Object.keys(google.bookingCalendar).find((env) => !envs.includes(env) && env !== "prod");
1057
+ if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingCalendar.${unknownBookingEnv} is not in config envs`);
1058
+ for (const [env, value2] of Object.entries(google.bookingCalendar)) {
1059
+ if (!safeText2(value2, 1024)) {
1060
+ throw new Error(`${path}: calendar.google.bookingCalendar.${env} must be a calendar id`);
1061
+ }
1062
+ }
1063
+ }
1064
+ if (google.bookingPageUrl !== void 0) {
1065
+ if (!isRecord5(google.bookingPageUrl)) throw new Error(`${path}: calendar.google.bookingPageUrl must map env names to HTTPS URLs or null`);
1066
+ const unknownBookingEnv = Object.keys(google.bookingPageUrl).find((env) => !envs.includes(env) && env !== "prod");
1067
+ if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingPageUrl.${unknownBookingEnv} is not in config envs`);
1068
+ for (const [env, value2] of Object.entries(google.bookingPageUrl)) {
1069
+ if (value2 !== null && !safeHttpsUrl(value2)) {
1070
+ throw new Error(`${path}: calendar.google.bookingPageUrl.${env} must be an HTTPS URL without credentials or fragment`);
1071
+ }
1072
+ }
1073
+ }
1074
+ }
1075
+ function assertOnly2(value2, allowed, label) {
1076
+ const extra = Object.keys(value2).find((key) => !allowed.includes(key));
1077
+ if (extra) throw new Error(`${label}.${extra} is not supported`);
1078
+ }
1079
+ function isRecord5(value2) {
1080
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
1081
+ }
1082
+ function safeText2(value2, max) {
1083
+ return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
1084
+ }
1085
+ function safeHttpsUrl(value2) {
1086
+ if (typeof value2 !== "string" || value2.length > 2048) return false;
1087
+ try {
1088
+ const url = new URL(value2);
1089
+ return url.protocol === "https:" && !url.username && !url.password && !url.hash;
1090
+ } catch {
1091
+ return false;
1092
+ }
1093
+ }
1094
+ function unique(values) {
1095
+ return [...new Set(values.filter(Boolean))];
1096
+ }
1097
+
993
1098
  // src/integration-validation.ts
994
1099
  function validateIntegrations(cfg, path, defaultServices) {
995
1100
  if (cfg.integrations === void 0) return;
@@ -997,36 +1102,61 @@ function validateIntegrations(cfg, path, defaultServices) {
997
1102
  const ids = /* @__PURE__ */ new Set();
998
1103
  for (const [index, integration] of cfg.integrations.entries()) {
999
1104
  const at = `${path}: integrations[${index}]`;
1000
- if (!isRecord5(integration)) throw new Error(`${at} must be an object`);
1105
+ if (!isRecord6(integration)) throw new Error(`${at} must be an object`);
1001
1106
  if (!validId(integration.id)) throw new Error(`${at}.id must be lowercase letters, numbers, and hyphens`);
1002
1107
  if (ids.has(integration.id)) throw new Error(`${path}: duplicate integration id "${integration.id}"`);
1003
1108
  ids.add(integration.id);
1004
- if (!safeText2(integration.title, 200)) throw new Error(`${at}.title is required`);
1005
- if (!safeText2(integration.npm, 200)) throw new Error(`${at}.npm is required`);
1006
- if (integration.schema !== void 0 && (!isRecord5(integration.schema) || !isRecord5(integration.schema.entities))) {
1109
+ if (!safeText3(integration.title, 200)) throw new Error(`${at}.title is required`);
1110
+ if (!safeText3(integration.npm, 200)) throw new Error(`${at}.npm is required`);
1111
+ if (integration.schema !== void 0 && (!isRecord6(integration.schema) || !isRecord6(integration.schema.entities))) {
1007
1112
  throw new Error(`${at}.schema must contain an entities object`);
1008
1113
  }
1009
- if (integration.rules !== void 0 && !isRecord5(integration.rules)) throw new Error(`${at}.rules must be an object`);
1114
+ if (integration.rules !== void 0 && !isRecord6(integration.rules)) throw new Error(`${at}.rules must be an object`);
1010
1115
  validateSeeds(integration, at);
1011
1116
  validateProbes(integration, at);
1117
+ validateSecrets(integration.secrets, at);
1012
1118
  }
1013
1119
  const needsDb = cfg.integrations.some((integration) => integration.schema || integration.rules || integration.seeds?.length);
1014
- const services = unique(cfg.services?.length ? cfg.services : defaultServices);
1120
+ const services = unique2(cfg.services?.length ? cfg.services : defaultServices);
1015
1121
  if (needsDb && !services.includes("db")) throw new Error(`${path}: schema/rules/seed integrations require the db service`);
1016
1122
  }
1123
+ var SECRET_NAME = /^\$?[a-z][a-z0-9_]*$/;
1124
+ function validateSecrets(value2, at) {
1125
+ if (value2 === void 0) return;
1126
+ if (!Array.isArray(value2)) throw new Error(`${at}.secrets must be an array`);
1127
+ const names = /* @__PURE__ */ new Set();
1128
+ for (const [index, secret] of value2.entries()) {
1129
+ const sat = `${at}.secrets[${index}]`;
1130
+ if (!isRecord6(secret)) throw new Error(`${sat} must be an object`);
1131
+ if (typeof secret.name !== "string" || !SECRET_NAME.test(secret.name) || secret.name.length > 64) {
1132
+ throw new Error(`${sat}.name must be lowercase snake_case (optionally "$"-prefixed when reserved), e.g. "clerk_webhook_secret"`);
1133
+ }
1134
+ const dollar = secret.name.startsWith("$");
1135
+ if (dollar !== (secret.reserved === true)) {
1136
+ throw new Error(
1137
+ dollar ? `${sat}.name is "$"-prefixed, so it must also set reserved: true` : `${sat} sets reserved: true, so its name must be "$"-prefixed`
1138
+ );
1139
+ }
1140
+ if (!safeText3(secret.description, 500)) throw new Error(`${sat}.description is required \u2014 it is what doctor and the docs show`);
1141
+ if (secret.pattern !== void 0 && !safeText3(secret.pattern, 64)) throw new Error(`${sat}.pattern must be a non-empty prefix string`);
1142
+ if (secret.required !== void 0 && typeof secret.required !== "boolean") throw new Error(`${sat}.required must be a boolean`);
1143
+ if (names.has(secret.name)) throw new Error(`${at} declares secret "${secret.name}" twice`);
1144
+ names.add(secret.name);
1145
+ }
1146
+ }
1017
1147
  function validateSeeds(integration, at) {
1018
1148
  if (integration.seeds === void 0) return;
1019
1149
  if (!Array.isArray(integration.seeds)) throw new Error(`${at}.seeds must be an array`);
1020
1150
  const ids = /* @__PURE__ */ new Set();
1021
1151
  for (const [index, seed] of integration.seeds.entries()) {
1022
1152
  const sat = `${at}.seeds[${index}]`;
1023
- if (!isRecord5(seed) || !safeText2(seed.id, 200) || !safeText2(seed.ns, 200)) throw new Error(`${sat} requires id and ns`);
1153
+ if (!isRecord6(seed) || !safeText3(seed.id, 200) || !safeText3(seed.ns, 200)) throw new Error(`${sat} requires id and ns`);
1024
1154
  if (ids.has(seed.id)) throw new Error(`${at} has duplicate seed id "${seed.id}"`);
1025
1155
  ids.add(seed.id);
1026
- if (!isRecord5(seed.key) || !safeText2(seed.key.attr, 200) || !safeText2(seed.key.value, 2048)) {
1156
+ if (!isRecord6(seed.key) || !safeText3(seed.key.attr, 200) || !safeText3(seed.key.value, 2048)) {
1027
1157
  throw new Error(`${sat}.key requires string attr and value`);
1028
1158
  }
1029
- if (!isRecord5(seed.attrs)) throw new Error(`${sat}.attrs must be an object`);
1159
+ if (!isRecord6(seed.attrs)) throw new Error(`${sat}.attrs must be an object`);
1030
1160
  if (Object.hasOwn(seed.attrs, seed.key.attr) && seed.attrs[seed.key.attr] !== seed.key.value) {
1031
1161
  throw new Error(`${sat}.attrs.${seed.key.attr} conflicts with its natural key`);
1032
1162
  }
@@ -1037,16 +1167,16 @@ function validateProbes(integration, at) {
1037
1167
  if (!Array.isArray(integration.probes)) throw new Error(`${at}.probes must be an array`);
1038
1168
  for (const [index, probe] of integration.probes.entries()) {
1039
1169
  const pat = `${at}.probes[${index}]`;
1040
- if (!isRecord5(probe) || !safeProbePath(probe.path)) throw new Error(`${pat}.path must be an absolute path without query or fragment`);
1170
+ if (!isRecord6(probe) || !safeProbePath(probe.path)) throw new Error(`${pat}.path must be an absolute path without query or fragment`);
1041
1171
  if (!Number.isInteger(probe.expectedStatus) || probe.expectedStatus < 100 || probe.expectedStatus > 599) {
1042
1172
  throw new Error(`${pat}.expectedStatus must be an HTTP status`);
1043
1173
  }
1044
1174
  }
1045
1175
  }
1046
- function isRecord5(value2) {
1176
+ function isRecord6(value2) {
1047
1177
  return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
1048
1178
  }
1049
- function safeText2(value2, max) {
1179
+ function safeText3(value2, max) {
1050
1180
  return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
1051
1181
  }
1052
1182
  function safeProbePath(value2) {
@@ -1055,10 +1185,179 @@ function safeProbePath(value2) {
1055
1185
  function validId(value2) {
1056
1186
  return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
1057
1187
  }
1058
- function unique(values) {
1188
+ function unique2(values) {
1059
1189
  return [...new Set(values.filter(Boolean))];
1060
1190
  }
1061
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
+
1062
1361
  // src/config.ts
1063
1362
  var DEFAULT_PLATFORM = "https://odla.ai";
1064
1363
  var DEFAULT_ENVS = ["dev"];
@@ -1075,10 +1374,11 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
1075
1374
  validateRawConfig(raw, resolved);
1076
1375
  const platformUrl = trimSlash(process.env.ODLA_PLATFORM_URL || raw.platformUrl || DEFAULT_PLATFORM);
1077
1376
  const dbEndpoint = trimSlash(process.env.ODLA_DB_ENDPOINT || raw.dbEndpoint || platformUrl);
1078
- const envs = unique2(raw.envs?.length ? raw.envs : DEFAULT_ENVS);
1079
- const services = unique2(raw.services?.length ? raw.services : DEFAULT_SERVICES);
1377
+ const envs = unique3(raw.envs?.length ? raw.envs : DEFAULT_ENVS);
1378
+ const services = unique3(raw.services?.length ? raw.services : DEFAULT_SERVICES);
1080
1379
  validateServices(services, resolved);
1081
- validateCalendarConfig(raw, unique2([...envs, ...options.additionalEnvs ?? []]), services, resolved);
1380
+ validateCalendarConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
1381
+ validateMonitoringConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
1082
1382
  const local = {
1083
1383
  tokenFile: resolve2(rootDir, raw.local?.tokenFile ?? ".odla/dev-token.json"),
1084
1384
  credentialsFile: resolve2(rootDir, raw.local?.credentialsFile ?? ".odla/credentials.local.json"),
@@ -1126,26 +1426,6 @@ function buildPlan(cfg) {
1126
1426
  aiProvider: cfg.ai?.provider
1127
1427
  };
1128
1428
  }
1129
- function calendarServiceConfig(cfg, env) {
1130
- if (!cfg.services.includes("calendar")) throw new Error("calendar service is not enabled in config services");
1131
- if (!cfg.envs.includes(env) && env !== "prod") throw new Error(`calendar env "${env}" is not declared in config envs`);
1132
- const google = cfg.calendar?.google;
1133
- if (!google) throw new Error("calendar.google is required when the calendar service is enabled");
1134
- const configured = google.availabilityCalendars?.[env] ?? google.calendars?.[env];
1135
- if (!configured?.length) throw new Error(`calendar.google.availabilityCalendars.${env} is required`);
1136
- const availability = unique2(configured.map((id) => id.trim()));
1137
- return {
1138
- provider: "google",
1139
- access: "book",
1140
- bookingCalendarId: google.bookingCalendar?.[env]?.trim() ?? availability[0],
1141
- availabilityCalendars: availability
1142
- };
1143
- }
1144
- function calendarBookingPageUrl(cfg, env) {
1145
- const value2 = cfg.calendar?.google.bookingPageUrl?.[env];
1146
- if (value2 === void 0 || value2 === null) return value2;
1147
- return new URL(value2).toString();
1148
- }
1149
1429
  function rulesFromSchema(schema) {
1150
1430
  const entities = serializedEntities(schema);
1151
1431
  return Object.fromEntries(
@@ -1177,69 +1457,9 @@ function validateRawConfig(raw, path) {
1177
1457
  throw new Error(`${path}: services must be an array of non-empty names`);
1178
1458
  }
1179
1459
  validateAiConfig(cfg, path);
1460
+ validateSecrets(cfg.secrets, `${path}: config`);
1180
1461
  validateIntegrations(cfg, path, DEFAULT_SERVICES);
1181
1462
  }
1182
- function validateCalendarConfig(cfg, envs, services, path) {
1183
- const enabled = services.includes("calendar");
1184
- if (!cfg.calendar) {
1185
- if (enabled) throw new Error(`${path}: calendar.google is required when services includes "calendar"`);
1186
- return;
1187
- }
1188
- if (!isRecord6(cfg.calendar)) throw new Error(`${path}: calendar must be an object`);
1189
- assertOnly2(cfg.calendar, ["google"], `${path}: calendar`);
1190
- if (!isRecord6(cfg.calendar.google)) throw new Error(`${path}: calendar.google must be an object`);
1191
- const google = cfg.calendar.google;
1192
- assertOnly2(
1193
- google,
1194
- ["availabilityCalendars", "calendars", "bookingCalendar", "bookingPageUrl"],
1195
- `${path}: calendar.google`
1196
- );
1197
- const availabilityKey = google.availabilityCalendars !== void 0 ? "availabilityCalendars" : google.calendars !== void 0 ? "calendars" : null;
1198
- if (!availabilityKey || google.availabilityCalendars !== void 0 && google.calendars !== void 0) {
1199
- throw new Error(`${path}: calendar.google requires exactly one of availabilityCalendars or calendars (legacy)`);
1200
- }
1201
- const availability = google[availabilityKey];
1202
- if (!isRecord6(availability)) throw new Error(`${path}: calendar.google.${availabilityKey} must map env names to calendar ids`);
1203
- const unknownEnv = Object.keys(availability).find((env) => !envs.includes(env) && env !== "prod");
1204
- if (unknownEnv) throw new Error(`${path}: calendar.google.${availabilityKey}.${unknownEnv} is not in config envs`);
1205
- for (const env of envs) {
1206
- const ids = availability[env];
1207
- if (!Array.isArray(ids) || ids.length === 0) {
1208
- throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
1209
- }
1210
- }
1211
- for (const [env, ids] of Object.entries(availability)) {
1212
- if (!Array.isArray(ids) || ids.length === 0) {
1213
- throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
1214
- }
1215
- if (ids.length > 10) {
1216
- throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must contain at most 10 calendar ids`);
1217
- }
1218
- if (ids.some((id) => !safeText3(id, 1024))) {
1219
- throw new Error(`${path}: calendar.google.${availabilityKey}.${env} contains an invalid calendar id`);
1220
- }
1221
- }
1222
- if (google.bookingCalendar !== void 0) {
1223
- if (!isRecord6(google.bookingCalendar)) throw new Error(`${path}: calendar.google.bookingCalendar must map env names to one calendar id`);
1224
- const unknownBookingEnv = Object.keys(google.bookingCalendar).find((env) => !envs.includes(env) && env !== "prod");
1225
- if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingCalendar.${unknownBookingEnv} is not in config envs`);
1226
- for (const [env, value2] of Object.entries(google.bookingCalendar)) {
1227
- if (!safeText3(value2, 1024)) {
1228
- throw new Error(`${path}: calendar.google.bookingCalendar.${env} must be a calendar id`);
1229
- }
1230
- }
1231
- }
1232
- if (google.bookingPageUrl !== void 0) {
1233
- if (!isRecord6(google.bookingPageUrl)) throw new Error(`${path}: calendar.google.bookingPageUrl must map env names to HTTPS URLs or null`);
1234
- const unknownBookingEnv = Object.keys(google.bookingPageUrl).find((env) => !envs.includes(env) && env !== "prod");
1235
- if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingPageUrl.${unknownBookingEnv} is not in config envs`);
1236
- for (const [env, value2] of Object.entries(google.bookingPageUrl)) {
1237
- if (value2 !== null && !safeHttpsUrl(value2)) {
1238
- throw new Error(`${path}: calendar.google.bookingPageUrl.${env} must be an HTTPS URL without credentials or fragment`);
1239
- }
1240
- }
1241
- }
1242
- }
1243
1463
  function validateServices(services, path) {
1244
1464
  for (const service of services) {
1245
1465
  const definition = appServiceDefinition(service);
@@ -1253,25 +1473,6 @@ function validateServices(services, path) {
1253
1473
  }
1254
1474
  }
1255
1475
  }
1256
- function assertOnly2(value2, allowed, label) {
1257
- const extra = Object.keys(value2).find((key) => !allowed.includes(key));
1258
- if (extra) throw new Error(`${label}.${extra} is not supported`);
1259
- }
1260
- function isRecord6(value2) {
1261
- return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
1262
- }
1263
- function safeText3(value2, max) {
1264
- return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
1265
- }
1266
- function safeHttpsUrl(value2) {
1267
- if (typeof value2 !== "string" || value2.length > 2048) return false;
1268
- try {
1269
- const url = new URL(value2);
1270
- return url.protocol === "https:" && !url.username && !url.password && !url.hash;
1271
- } catch {
1272
- return false;
1273
- }
1274
- }
1275
1476
  function validId2(value2) {
1276
1477
  return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
1277
1478
  }
@@ -1286,7 +1487,7 @@ async function loadConfigModule(path) {
1286
1487
  function trimSlash(value2) {
1287
1488
  return value2.replace(/\/+$/, "");
1288
1489
  }
1289
- function unique2(values) {
1490
+ function unique3(values) {
1290
1491
  return [...new Set(values.filter(Boolean))];
1291
1492
  }
1292
1493
 
@@ -1579,7 +1780,7 @@ async function adminCommand(parsed, deps = {}) {
1579
1780
  import process11 from "process";
1580
1781
 
1581
1782
  // src/whoami-command.ts
1582
- var text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
1783
+ var text2 = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
1583
1784
  function principalKind(value2, machine) {
1584
1785
  return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
1585
1786
  }
@@ -1592,12 +1793,12 @@ function credentialKind(value2, machine, scopes) {
1592
1793
  function managerOf(value2) {
1593
1794
  if (!value2 || typeof value2 !== "object") return null;
1594
1795
  const row = value2;
1595
- const principalId = text(row.principalId);
1796
+ const principalId = text2(row.principalId);
1596
1797
  if (!principalId) return null;
1597
1798
  return {
1598
1799
  principalId,
1599
- displayName: text(row.displayName) ?? "Unnamed member",
1600
- handle: text(row.handle) ?? ""
1800
+ displayName: text2(row.displayName) ?? "Unnamed member",
1801
+ handle: text2(row.handle) ?? ""
1601
1802
  };
1602
1803
  }
1603
1804
  function unnamedPrincipal(kind) {
@@ -1611,14 +1812,14 @@ async function fetchIdentity(platformUrl, token, doFetch) {
1611
1812
  });
1612
1813
  if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
1613
1814
  const body = await res.json();
1614
- const developerId = text(body.developerId) ?? "";
1815
+ const developerId = text2(body.developerId) ?? "";
1615
1816
  const machine = body.machine === true;
1616
1817
  const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
1617
- const principalId = text(body.principalId) ?? developerId;
1618
- const email = text(body.email);
1818
+ const principalId = text2(body.principalId) ?? developerId;
1819
+ const email = text2(body.email);
1619
1820
  const kind = principalKind(body.principalKind, machine);
1620
- const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
1621
- const handle = text(body.handle) ?? "";
1821
+ const displayName = text2(body.displayName) ?? email ?? unnamedPrincipal(kind);
1822
+ const handle = text2(body.handle) ?? "";
1622
1823
  const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
1623
1824
  return {
1624
1825
  developerId,
@@ -1628,7 +1829,7 @@ async function fetchIdentity(platformUrl, token, doFetch) {
1628
1829
  handle,
1629
1830
  manager: managerOf(body.manager),
1630
1831
  credential: {
1631
- id: text(credential2.id),
1832
+ id: text2(credential2.id),
1632
1833
  kind: credentialKind(credential2.kind, machine, scopes)
1633
1834
  },
1634
1835
  email,
@@ -1823,13 +2024,13 @@ async function agentCommand(parsed, deps = {}) {
1823
2024
  const base = `${cfg.dbEndpoint}/app/${encodeURIComponent(tenant)}/admin/agent-jobs`;
1824
2025
  const headers = { authorization: `Bearer ${credential2}` };
1825
2026
  if (action2 === "retry") {
1826
- const id = parsed.positionals[2];
1827
- 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 });
1828
2029
  const body2 = await readJson(res2);
1829
2030
  if (!res2.ok) throw new Error(`agent retry failed (${res2.status}): ${errorMessage(body2)}`);
1830
2031
  const result2 = { v: 1, appId: cfg.app.id, env, tenant, ...body2 };
1831
2032
  if (parsed.options.json === true) out.log(JSON.stringify(result2, null, 2));
1832
- else out.log(`${tenant}: requeued ${id}`);
2033
+ else out.log(`${tenant}: requeued ${id2}`);
1833
2034
  return;
1834
2035
  }
1835
2036
  const state2 = stringOpt(parsed.options.state);
@@ -1913,8 +2114,8 @@ async function appImport(options) {
1913
2114
  const out = options.stdout ?? console;
1914
2115
  const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
1915
2116
  const { tenant } = resolveTenant(cfg, options.env);
1916
- const text2 = options.file === "-" ? (options.readStdin ?? (() => readFileSync4(0, "utf8")))() : readFileSync4(options.file, "utf8");
1917
- 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);
1918
2119
  if (format === "namespace-map" && options.ns) {
1919
2120
  throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
1920
2121
  }
@@ -2130,7 +2331,7 @@ var EXTENSIONS = {
2130
2331
  "text/html": "html",
2131
2332
  "application/json": "json"
2132
2333
  };
2133
- var encode = (text2) => new TextEncoder().encode(text2);
2334
+ var encode = (text3) => new TextEncoder().encode(text3);
2134
2335
  function assetFileName(uuid, mime) {
2135
2336
  const ext = EXTENSIONS[mime.split(";")[0].trim().toLowerCase()] ?? "bin";
2136
2337
  return `${uuid.replace(/[^a-zA-Z0-9._-]/g, "_")}.${ext}`;
@@ -2292,18 +2493,18 @@ async function readCalendarStatus(ctx) {
2292
2493
  }
2293
2494
  async function discoverGoogleCalendars(ctx) {
2294
2495
  const raw = await calendarJson(ctx, "/calendars", {});
2295
- const value2 = record(raw);
2496
+ const value2 = record2(raw);
2296
2497
  if (!value2 || !Array.isArray(value2.calendars)) throw new Error("calendar discovery returned an invalid response");
2297
2498
  return value2.calendars.map((item, index) => {
2298
- const calendar = record(item);
2299
- const id = textField(calendar?.id, 1024);
2300
- 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}`);
2301
2502
  const role = calendar.accessRole;
2302
2503
  if (role !== void 0 && role !== "freeBusyReader" && role !== "reader" && role !== "writer" && role !== "owner") {
2303
2504
  throw new Error(`calendar discovery returned an invalid access role at index ${index}`);
2304
2505
  }
2305
2506
  return {
2306
- id,
2507
+ id: id2,
2307
2508
  ...optionalText("summary", calendar.summary, 500),
2308
2509
  ...typeof calendar.primary === "boolean" ? { primary: calendar.primary } : {},
2309
2510
  ...typeof calendar.selected === "boolean" ? { selected: calendar.selected } : {},
@@ -2331,10 +2532,10 @@ async function pollCalendarConnection(ctx, attemptId) {
2331
2532
  }
2332
2533
  function parseCalendarStatus(raw, env) {
2333
2534
  const outer = wrapped(raw, "calendar");
2334
- const value2 = record(outer.attempt) ?? record(outer.status) ?? outer;
2335
- const connection = record(value2.connection) ?? {};
2336
- const config = record(value2.config) ?? record(outer.config) ?? {};
2337
- 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;
2338
2539
  const stateValue = calendarState(value2.status ?? value2.state ?? connection.status ?? connection.state);
2339
2540
  if (!stateValue) {
2340
2541
  throw new Error("calendar status returned an invalid connection state");
@@ -2347,7 +2548,7 @@ function parseCalendarStatus(raw, env) {
2347
2548
  if (accessValue !== void 0 && accessValue !== "book" && accessValue !== "read") {
2348
2549
  throw new Error("calendar status returned unsupported access");
2349
2550
  }
2350
- const errorValue = record(value2.error) ?? record(connection.error);
2551
+ const errorValue = record2(value2.error) ?? record2(connection.error);
2351
2552
  const errorCode2 = textField(value2.lastErrorCode, 128);
2352
2553
  const bookingPageValue = Object.hasOwn(value2, "bookingPageUrl") ? value2.bookingPageUrl : Object.hasOwn(config, "bookingPageUrl") ? config.bookingPageUrl : googleConfig.bookingPageUrl;
2353
2554
  const connected = typeof (value2.connected ?? connection.connected) === "boolean" ? Boolean(value2.connected ?? connection.connected) : ["healthy", "degraded"].includes(stateValue);
@@ -2417,11 +2618,11 @@ async function calendarJson(ctx, suffix, init) {
2417
2618
  return body;
2418
2619
  }
2419
2620
  function wrapped(raw, key) {
2420
- const outer = record(raw);
2621
+ const outer = record2(raw);
2421
2622
  if (!outer) throw new Error("calendar returned an invalid response");
2422
- return record(outer[key]) ?? outer;
2623
+ return record2(outer[key]) ?? outer;
2423
2624
  }
2424
- function record(value2) {
2625
+ function record2(value2) {
2425
2626
  return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
2426
2627
  }
2427
2628
  function textField(value2, max) {
@@ -2434,9 +2635,9 @@ function calendarIds(value2) {
2434
2635
  if (!Array.isArray(value2)) return [];
2435
2636
  return [...new Set(value2.flatMap((item) => {
2436
2637
  if (typeof item === "string") return textField(item, 4096) ? [item] : [];
2437
- const calendar = record(item);
2438
- const id = textField(calendar?.id, 4096);
2439
- 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] : [];
2440
2641
  }))];
2441
2642
  }
2442
2643
  function timestamp3(value2) {
@@ -2671,6 +2872,7 @@ var CAPABILITIES = {
2671
2872
  "validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
2672
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",
2673
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",
2674
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",
2675
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",
2676
2878
  "inspect or bounded-wait one exact durable config-operation journal entry and verify every terminal receipt digest before returning it to remote automation",
@@ -2683,7 +2885,8 @@ var CAPABILITIES = {
2683
2885
  "install and import the selected odla SDKs",
2684
2886
  "wrap the Worker with withObservability and choose useful telemetry",
2685
2887
  "install capability packages, mount their runtime routes, and make application-specific schema, rules, auth, UI, and migration decisions",
2686
- "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"
2687
2890
  ],
2688
2891
  human: [
2689
2892
  "provide the existing odla account email, then sign in and explicitly review/approve the exact device code",
@@ -2696,6 +2899,7 @@ var CAPABILITIES = {
2696
2899
  ],
2697
2900
  studio: [
2698
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",
2699
2903
  "let signed-in users inventory/revoke their own agent grants and admins audit/global-revoke them",
2700
2904
  "review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens",
2701
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",
@@ -2815,9 +3019,9 @@ function canonicalValue(value2) {
2815
3019
  }
2816
3020
  if (Array.isArray(value2)) return value2.map(canonicalValue);
2817
3021
  if (value2 && typeof value2 === "object") {
2818
- const record9 = value2;
3022
+ const record11 = value2;
2819
3023
  return Object.fromEntries(
2820
- 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])])
2821
3025
  );
2822
3026
  }
2823
3027
  throw new TypeError("canonical JSON rejects unsupported values");
@@ -2842,8 +3046,8 @@ function readPlan(path) {
2842
3046
  "invalid_plan"
2843
3047
  );
2844
3048
  }
2845
- if (!record2(value2) || value2.schemaVersion !== "odla.config-plan/v2") invalidPlan("unsupported plan schema");
2846
- 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") {
2847
3051
  invalidPlan("plan scope is invalid");
2848
3052
  }
2849
3053
  if (!DIGEST.test(String(value2.desiredRevision)) || !DIGEST.test(String(value2.observedRevision))) {
@@ -2893,10 +3097,10 @@ function assertOperationId(value2) {
2893
3097
  function assertActions(actions) {
2894
3098
  const ids = /* @__PURE__ */ new Set();
2895
3099
  for (const action2 of actions) {
2896
- if (!record2(action2)) invalidPlan("every plan action must be an object");
2897
- const id = String(action2.id ?? "");
2898
- if (!ACTION_ID.test(id) || ids.has(id)) invalidPlan("plan action ids must be unique frozen ids");
2899
- 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);
2900
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))) {
2901
3105
  invalidPlan("plan action metadata is invalid");
2902
3106
  }
@@ -2922,14 +3126,14 @@ function assertConditionalAction(action2) {
2922
3126
  if (action2.path !== (action2.kind === "configure_service" ? `${base}.config` : base)) {
2923
3127
  invalidPlan("service action path is invalid");
2924
3128
  }
2925
- if (action2.applySupport !== "provision" || !record2(action2.after)) {
3129
+ if (action2.applySupport !== "provision" || !record3(action2.after)) {
2926
3130
  invalidPlan("service action payload is invalid");
2927
3131
  }
2928
3132
  if (action2.kind === "enable_service") {
2929
- if (action2.after.enabled !== true || action2.before !== null && !record2(action2.before)) {
3133
+ if (action2.after.enabled !== true || action2.before !== null && !record3(action2.before)) {
2930
3134
  invalidPlan("service enable action is invalid");
2931
3135
  }
2932
- } else if (!record2(action2.before)) {
3136
+ } else if (!record3(action2.before)) {
2933
3137
  invalidPlan("service configure action is invalid");
2934
3138
  }
2935
3139
  }
@@ -2946,7 +3150,7 @@ function linkState(value2) {
2946
3150
  function invalidPlan(message2) {
2947
3151
  throw new ConfigOperationCommandError(message2, "invalid_plan");
2948
3152
  }
2949
- function record2(value2) {
3153
+ function record3(value2) {
2950
3154
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
2951
3155
  }
2952
3156
 
@@ -2985,9 +3189,9 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
2985
3189
  }
2986
3190
  throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
2987
3191
  }
2988
- function errorCode(text2) {
3192
+ function errorCode(text3) {
2989
3193
  try {
2990
- const body = JSON.parse(text2);
3194
+ const body = JSON.parse(text3);
2991
3195
  return typeof body.error?.code === "string" ? body.error.code : null;
2992
3196
  } catch {
2993
3197
  return null;
@@ -3130,7 +3334,7 @@ async function configApply(options) {
3130
3334
  throw new ConfigOperationCommandError("--idempotency-key is not a safe 1-120 character key", "invalid_plan");
3131
3335
  }
3132
3336
  const client = await operationClient(cfg, options, "apply");
3133
- const request2 = {
3337
+ const request3 = {
3134
3338
  schemaVersion: "odla.config-operation-request/v1",
3135
3339
  expectedRevision: plan.registryRevision,
3136
3340
  desiredRevision: plan.desiredRevision,
@@ -3142,7 +3346,7 @@ async function configApply(options) {
3142
3346
  };
3143
3347
  let receipt;
3144
3348
  try {
3145
- receipt = await client.applyConfigOperation(cfg.app.id, request2);
3349
+ receipt = await client.applyConfigOperation(cfg.app.id, request3);
3146
3350
  } catch (error) {
3147
3351
  const retained = retainedReceipt(error);
3148
3352
  if (retained) {
@@ -3241,8 +3445,8 @@ function failureForReceipt(receipt) {
3241
3445
  return new ConfigOperationCommandError(receipt.error?.message ?? `config operation ${receipt.state}`, "config_operation_failed");
3242
3446
  }
3243
3447
  function retainedReceipt(error) {
3244
- if (!(error instanceof AppsError) || !record3(error.details)) return null;
3245
- 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;
3246
3450
  }
3247
3451
  function normalizeRequestError(error) {
3248
3452
  if (!(error instanceof AppsError)) return error instanceof Error ? error : new Error(String(error));
@@ -3255,7 +3459,7 @@ function normalizeRequestError(error) {
3255
3459
  }
3256
3460
  return new ConfigOperationCommandError(error.message, error.code || "config_operation_failed");
3257
3461
  }
3258
- function record3(value2) {
3462
+ function record4(value2) {
3259
3463
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
3260
3464
  }
3261
3465
 
@@ -3722,15 +3926,15 @@ function readWranglerConfig(path) {
3722
3926
  return null;
3723
3927
  }
3724
3928
  }
3725
- function stripJsonComments(text2) {
3929
+ function stripJsonComments(text3) {
3726
3930
  let result = "";
3727
3931
  let inString = false;
3728
- for (let i = 0; i < text2.length; i++) {
3729
- const ch = text2[i];
3932
+ for (let i = 0; i < text3.length; i++) {
3933
+ const ch = text3[i];
3730
3934
  if (inString) {
3731
3935
  result += ch;
3732
3936
  if (ch === "\\") {
3733
- result += text2[i + 1] ?? "";
3937
+ result += text3[i + 1] ?? "";
3734
3938
  i++;
3735
3939
  } else if (ch === '"') {
3736
3940
  inString = false;
@@ -3742,14 +3946,14 @@ function stripJsonComments(text2) {
3742
3946
  result += ch;
3743
3947
  continue;
3744
3948
  }
3745
- if (ch === "/" && text2[i + 1] === "/") {
3746
- while (i < text2.length && text2[i] !== "\n") i++;
3949
+ if (ch === "/" && text3[i + 1] === "/") {
3950
+ while (i < text3.length && text3[i] !== "\n") i++;
3747
3951
  result += "\n";
3748
3952
  continue;
3749
3953
  }
3750
- if (ch === "/" && text2[i + 1] === "*") {
3954
+ if (ch === "/" && text3[i + 1] === "*") {
3751
3955
  i += 2;
3752
- while (i < text2.length && !(text2[i] === "*" && text2[i + 1] === "/")) i++;
3956
+ while (i < text3.length && !(text3[i] === "*" && text3[i + 1] === "/")) i++;
3753
3957
  i++;
3754
3958
  continue;
3755
3959
  }
@@ -3788,7 +3992,7 @@ async function wranglerRuntimeTarget(run, opts) {
3788
3992
  throw new Error(`wrangler is not logged in \u2014 run "wrangler login" (a browser step for the human)`);
3789
3993
  }
3790
3994
  const discovered = [...new Set(`${whoami.stdout}
3791
- ${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()) ?? [])];
3792
3996
  const accountId = configuredAccount.toLowerCase() || (discovered.length === 1 ? discovered[0] : "");
3793
3997
  if (!/^[a-f0-9]{32}$/.test(accountId) || configuredAccount && discovered.length > 0 && !discovered.includes(accountId)) {
3794
3998
  throw new Error("Wrangler account is ambiguous or does not match account_id in the config");
@@ -4068,6 +4272,67 @@ function isRecord7(value2) {
4068
4272
  return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
4069
4273
  }
4070
4274
 
4275
+ // src/secret-contract.ts
4276
+ var APP_SOURCE = "app";
4277
+ function resolveSecretContract(cfg) {
4278
+ const byName = /* @__PURE__ */ new Map();
4279
+ const declarations = [
4280
+ ...(cfg.secrets ?? []).map((secret) => ({ source: APP_SOURCE, secret })),
4281
+ ...(cfg.integrations ?? []).flatMap(
4282
+ (integration) => (integration.secrets ?? []).map((secret) => ({ source: integration.id, secret }))
4283
+ )
4284
+ ];
4285
+ for (const { source, secret } of declarations) {
4286
+ const existing = byName.get(secret.name);
4287
+ if (!existing) {
4288
+ byName.set(secret.name, { ...secret, required: secret.required !== false, sources: [source] });
4289
+ continue;
4290
+ }
4291
+ existing.sources.push(source);
4292
+ existing.required = existing.required || secret.required !== false;
4293
+ existing.pattern ??= secret.pattern;
4294
+ }
4295
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
4296
+ }
4297
+ function secretContractWarnings(contract, cfg) {
4298
+ const warnings = [];
4299
+ const declaredPatterns = /* @__PURE__ */ new Map();
4300
+ for (const integration of cfg.integrations ?? []) {
4301
+ for (const secret of integration.secrets ?? []) {
4302
+ if (!secret.pattern) continue;
4303
+ const seen = declaredPatterns.get(secret.name) ?? /* @__PURE__ */ new Map();
4304
+ seen.set(integration.id, secret.pattern);
4305
+ declaredPatterns.set(secret.name, seen);
4306
+ }
4307
+ }
4308
+ for (const secret of cfg.secrets ?? []) {
4309
+ if (!secret.pattern) continue;
4310
+ const seen = declaredPatterns.get(secret.name) ?? /* @__PURE__ */ new Map();
4311
+ seen.set(APP_SOURCE, secret.pattern);
4312
+ declaredPatterns.set(secret.name, seen);
4313
+ }
4314
+ for (const [name, seen] of declaredPatterns) {
4315
+ const distinct = [...new Set(seen.values())];
4316
+ if (distinct.length > 1) {
4317
+ const detail = [...seen].map(([source, pattern]) => `${source} expects "${pattern}"`).join(", ");
4318
+ warnings.push(`secret "${name}" has conflicting patterns \u2014 ${detail}; one of them will reject a valid value`);
4319
+ }
4320
+ }
4321
+ if (contract.length > 0 && !cfg.services.includes("db")) {
4322
+ const names = contract.map((secret) => secret.name).join(", ");
4323
+ warnings.push(`secrets are declared (${names}) but the db service is off \u2014 nothing can read the tenant vault`);
4324
+ }
4325
+ return warnings;
4326
+ }
4327
+ function formatSecretContract(contract) {
4328
+ return contract.map((secret) => {
4329
+ const flags = [secret.required ? "required" : "optional"];
4330
+ if (secret.reserved) flags.push("reserved");
4331
+ if (secret.pattern) flags.push(`${secret.pattern}\u2026`);
4332
+ return ` ${secret.name} (${flags.join(", ")}) \u2014 ${secret.sources.join(", ")}`;
4333
+ });
4334
+ }
4335
+
4071
4336
  // src/doctor.ts
4072
4337
  async function doctor(options) {
4073
4338
  const out = options.stdout ?? console;
@@ -4084,6 +4349,9 @@ async function doctor(options) {
4084
4349
  out.log(`schema: ${schema ? `${entities.length} entities` : "none"}`);
4085
4350
  out.log(`rules: ${rules ? `${Object.keys(rules).length} namespaces` : "none"}`);
4086
4351
  out.log(`ai: ${cfg.services.includes("ai") ? cfg.ai?.provider ? `byok/${cfg.ai.provider}` : "hosted" : "not enabled"}`);
4352
+ const contract = resolveSecretContract(cfg);
4353
+ out.log(`secrets: ${contract.length ? `${contract.length} declared` : "none declared"}`);
4354
+ for (const line of formatSecretContract(contract)) out.log(line);
4087
4355
  if (cfg.services.includes("calendar")) {
4088
4356
  const calendar = cfg.envs.map((env) => {
4089
4357
  const resolved = calendarServiceConfig(cfg, env);
@@ -4104,6 +4372,7 @@ async function doctor(options) {
4104
4372
  }
4105
4373
  }
4106
4374
  warnings.push(...integrationWarnings(database.integrations, schema, rules));
4375
+ warnings.push(...secretContractWarnings(contract, cfg));
4107
4376
  if (cfg.services.includes("ai") && cfg.ai?.mode === "byok" && !cfg.ai.provider) {
4108
4377
  warnings.push("ai.mode is byok but ai.provider is not set");
4109
4378
  }
@@ -4198,9 +4467,9 @@ function initProject(options) {
4198
4467
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
4199
4468
  out.log("updated .gitignore for local odla credentials");
4200
4469
  }
4201
- function writeIfMissing(path, text2) {
4470
+ function writeIfMissing(path, text3) {
4202
4471
  if (existsSync8(path)) return;
4203
- writeFileSync2(path, text2);
4472
+ writeFileSync2(path, text3);
4204
4473
  }
4205
4474
  function configTemplate(input) {
4206
4475
  const calendar = input.services.includes("calendar") ? ` calendar: {
@@ -4410,13 +4679,13 @@ async function secretsSetClerkKey(options) {
4410
4679
  body: JSON.stringify({ value: value2 })
4411
4680
  });
4412
4681
  if (!res.ok) {
4413
- const text2 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
4414
- 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"}`);
4415
4684
  }
4416
4685
  out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
4417
4686
  }
4418
- function scrubValue(text2, value2) {
4419
- return redactSecrets(text2).split(value2).join("[value redacted]");
4687
+ function scrubValue(text3, value2) {
4688
+ return redactSecrets(text3).split(value2).join("[value redacted]");
4420
4689
  }
4421
4690
  async function resolveVaultWrite(options) {
4422
4691
  const out = options.stdout ?? console;
@@ -4430,6 +4699,71 @@ async function resolveVaultWrite(options) {
4430
4699
  return { cfg, tenantId: tenantIdFor3(cfg.app.id, env), value: value2, doFetch, out };
4431
4700
  }
4432
4701
 
4702
+ // src/secrets-status.ts
4703
+ import { tenantIdFor as tenantIdFor4 } from "@odla-ai/apps";
4704
+ async function secretsStatus(options) {
4705
+ const out = options.stdout ?? console;
4706
+ const doFetch = options.fetch ?? fetch;
4707
+ const cfg = await loadProjectConfig(options.configPath);
4708
+ if (!cfg.envs.includes(options.env)) {
4709
+ throw new Error(`env "${options.env}" is not in config envs (${cfg.envs.join(", ")})`);
4710
+ }
4711
+ const tenantId = tenantIdFor4(cfg.app.id, options.env);
4712
+ const contract = resolveSecretContract(cfg);
4713
+ const token = await getDeveloperToken(cfg, options, doFetch, out);
4714
+ const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/secrets`, {
4715
+ headers: { authorization: `Bearer ${token}` }
4716
+ });
4717
+ if (!res.ok) {
4718
+ const detail = (await res.text().catch(() => "")).slice(0, 300);
4719
+ throw new Error(`list secrets for ${tenantId} failed (${res.status}): ${detail || "request failed"}`);
4720
+ }
4721
+ const body = await res.json();
4722
+ const stored = new Set((body.secrets ?? []).map((entry) => String(entry.name)));
4723
+ const report4 = buildReport(cfg.app.id, options.env, tenantId, contract, stored);
4724
+ if (options.json) out.log(JSON.stringify(report4, null, 2));
4725
+ else printReport(report4, out);
4726
+ return report4;
4727
+ }
4728
+ function buildReport(appId, env, tenant, contract, stored) {
4729
+ const declared = new Set(contract.map((secret) => secret.name));
4730
+ const rows = contract.map((secret) => ({
4731
+ name: secret.name,
4732
+ state: secret.reserved ? "reserved" : stored.has(secret.name) ? "set" : "missing",
4733
+ required: secret.required,
4734
+ sources: secret.sources,
4735
+ description: secret.description
4736
+ }));
4737
+ for (const name of [...stored].sort()) {
4738
+ if (!declared.has(name)) rows.push({ name, state: "undeclared", required: false, sources: [] });
4739
+ }
4740
+ const ok = rows.every((row) => row.state !== "missing" || !row.required);
4741
+ return { app: appId, env, tenant, secrets: rows, ok };
4742
+ }
4743
+ function printReport(report4, out) {
4744
+ out.log(`${report4.app} (${report4.tenant})`);
4745
+ if (report4.secrets.length === 0) {
4746
+ out.log(" no secrets declared and none stored");
4747
+ return;
4748
+ }
4749
+ for (const row of report4.secrets) {
4750
+ const label = row.state === "missing" && !row.required ? "missing (optional)" : row.state;
4751
+ const suffix = row.sources.length ? ` \u2014 ${row.sources.join(", ")}` : "";
4752
+ out.log(` ${label.padEnd(18)} ${row.name}${suffix}`);
4753
+ }
4754
+ const missing = report4.secrets.filter((row) => row.state === "missing" && row.required);
4755
+ if (missing.length) {
4756
+ out.log("");
4757
+ for (const row of missing) {
4758
+ out.log(`${row.name} is required but not set \u2014 "odla-ai secrets set ${row.name} --env ${report4.env} --stdin"`);
4759
+ }
4760
+ }
4761
+ if (report4.secrets.some((row) => row.state === "reserved")) {
4762
+ out.log("");
4763
+ out.log('"reserved" slots are never enumerated by the vault; presence cannot be confirmed here.');
4764
+ }
4765
+ }
4766
+
4433
4767
  // src/skill.ts
4434
4768
  import { existsSync as existsSync9, lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync8, readdirSync, writeFileSync as writeFileSync3 } from "fs";
4435
4769
  import { homedir as homedir2 } from "os";
@@ -4485,8 +4819,8 @@ alwaysApply: false
4485
4819
 
4486
4820
  ${PROJECT_INSTRUCTIONS}
4487
4821
  `;
4488
- function claudeAdapter(skill, canonical) {
4489
- 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---/);
4490
4824
  if (!match) throw new Error(`bundled skill ${skill} has no YAML frontmatter`);
4491
4825
  const lines = match[1].split(/\r?\n/);
4492
4826
  const frontmatter = [];
@@ -4556,8 +4890,8 @@ function installSkill(options = {}) {
4556
4890
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
4557
4891
  if (harnesses.includes("claude")) {
4558
4892
  for (const skill of skillNames(files)) {
4559
- const canonical = readFileSync8(join9(sourceDir, skill, "SKILL.md"), "utf8");
4560
- 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));
4561
4895
  }
4562
4896
  rememberTarget("claude", claudeRoot);
4563
4897
  }
@@ -4833,7 +5167,7 @@ function assertCalendarHealthy(status, expected) {
4833
5167
  if (!status.writable) throw new Error('calendar grant does not cover booking writes; run "odla-ai calendar connect" to re-consent');
4834
5168
  const hasEventsScope = status.grantedScopes.some((scope) => scope === GOOGLE_CALENDAR_EVENTS_SCOPE);
4835
5169
  if (!hasEventsScope) throw new Error("calendar connection is missing calendar.events consent");
4836
- const missing = expected.availabilityCalendars.filter((id) => !status.calendars.includes(id));
5170
+ const missing = expected.availabilityCalendars.filter((id2) => !status.calendars.includes(id2));
4837
5171
  if (missing.length) throw new Error(`calendar connection is missing configured calendars: ${missing.join(", ")}`);
4838
5172
  }
4839
5173
  async function getJson(doFetch, url, bearer) {
@@ -4899,9 +5233,22 @@ async function secretsCommand(parsed, deps) {
4899
5233
  await (sub === "set" ? secretsSet(options) : secretsSetClerkKey(options));
4900
5234
  return;
4901
5235
  }
5236
+ if (sub === "status") {
5237
+ assertArgs(parsed, ["config", "env", "token", "email", "json"], 2);
5238
+ await secretsStatus({
5239
+ configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
5240
+ env: requiredString(parsed.options.env, "--env"),
5241
+ json: parsed.options.json === true,
5242
+ token: stringOpt(parsed.options.token),
5243
+ email: stringOpt(parsed.options.email),
5244
+ fetch: deps.fetch,
5245
+ stdout: deps.stdout
5246
+ });
5247
+ return;
5248
+ }
4902
5249
  if (sub !== "push") {
4903
5250
  throw new Error(
4904
- `unknown secrets subcommand "${sub ?? ""}". Try "odla-ai secrets push --env dev", "odla-ai secrets set <name> --env dev --stdin", or "odla-ai secrets set-clerk-key --env dev --stdin".`
5251
+ `unknown secrets subcommand "${sub ?? ""}". Try "odla-ai secrets push --env dev", "odla-ai secrets status --env dev", "odla-ai secrets set <name> --env dev --stdin", or "odla-ai secrets set-clerk-key --env dev --stdin".`
4905
5252
  );
4906
5253
  }
4907
5254
  assertArgs(parsed, ["config", "env", "dry-run", "yes"], 2);
@@ -5220,8 +5567,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
5220
5567
  const maxFiles = options.maxFiles ?? 2e4;
5221
5568
  const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
5222
5569
  const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
5223
- const entries = inventory.flatMap((record9) => {
5224
- 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);
5225
5572
  return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
5226
5573
  });
5227
5574
  if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
@@ -5425,7 +5772,7 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
5425
5772
  }
5426
5773
  }
5427
5774
 
5428
- // ../harness/dist/chunk-5FFR7U4L.js
5775
+ // ../harness/dist/chunk-ANNX7VGK.js
5429
5776
  import { createHash as createHash3 } from "crypto";
5430
5777
  import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
5431
5778
  import { relative as relative4, resolve as resolve10 } from "path";
@@ -5469,8 +5816,8 @@ function normalize(value2) {
5469
5816
  if (Array.isArray(value2)) return value2.map(normalize);
5470
5817
  if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
5471
5818
  if (typeof value2 === "object") {
5472
- const record9 = value2;
5473
- 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])]));
5474
5821
  }
5475
5822
  throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
5476
5823
  }
@@ -5485,9 +5832,9 @@ function dependenciesOf(values, influence = "data") {
5485
5832
  result.push({ ref, influence, promptSafetyAtUse: value2.label.promptSafety });
5486
5833
  }
5487
5834
  }
5488
- const unique3 = /* @__PURE__ */ new Map();
5489
- for (const dep of result) unique3.set(`${dep.ref.kind}\0${dep.ref.id}\0${dep.influence}\0${dep.promptSafetyAtUse}`, dep);
5490
- return [...unique3.values()];
5835
+ const unique4 = /* @__PURE__ */ new Map();
5836
+ for (const dep of result) unique4.set(`${dep.ref.kind}\0${dep.ref.id}\0${dep.influence}\0${dep.promptSafetyAtUse}`, dep);
5837
+ return [...unique4.values()];
5491
5838
  }
5492
5839
 
5493
5840
  // ../camel/dist/chunk-4DQ6BIHP.js
@@ -5544,7 +5891,7 @@ function copyRef(ref) {
5544
5891
  function normalizeReaders(readers) {
5545
5892
  if (readers.kind === "public") return Object.freeze({ kind: "public" });
5546
5893
  const principalIds = [...new Set(readers.principalIds)].sort();
5547
- 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.");
5548
5895
  return Object.freeze({ kind: "principals", principalIds: Object.freeze(principalIds) });
5549
5896
  }
5550
5897
 
@@ -5554,7 +5901,7 @@ var SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
5554
5901
  var ID = /^[A-Za-z0-9._:-]{1,160}$/;
5555
5902
  async function digestCodeVerificationReceipt(fields) {
5556
5903
  validate(fields);
5557
- const canonical = {
5904
+ const canonical2 = {
5558
5905
  schemaVersion: fields.schemaVersion,
5559
5906
  verificationId: fields.verificationId,
5560
5907
  trustedBaseCommitSha: fields.trustedBaseCommitSha,
@@ -5581,7 +5928,7 @@ async function digestCodeVerificationReceipt(fields) {
5581
5928
  changedTestsRequireReview: fields.changedTestsRequireReview,
5582
5929
  outcome: fields.outcome
5583
5930
  };
5584
- return `sha256:${await sha256Hex(canonicalJson2(canonical))}`;
5931
+ return `sha256:${await sha256Hex(canonicalJson2(canonical2))}`;
5585
5932
  }
5586
5933
  function validate(fields) {
5587
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) {
@@ -5762,7 +6109,7 @@ function validateSnapshot(snapshot, limits) {
5762
6109
  }
5763
6110
  }
5764
6111
 
5765
- // ../harness/dist/chunk-5FFR7U4L.js
6112
+ // ../harness/dist/chunk-ANNX7VGK.js
5766
6113
  import { spawn as spawn4 } from "child_process";
5767
6114
  import { lstat as lstat2 } from "fs/promises";
5768
6115
  import { resolve as resolve23, sep as sep3 } from "path";
@@ -5799,7 +6146,7 @@ async function createConversionRegistry(config) {
5799
6146
  if (await conversionPolicyDigest(definition) !== policy.digest) throw new CamelError("state_conflict", "Conversion policy digest mismatch.");
5800
6147
  if (policy.output.kind === "registered_id") {
5801
6148
  const registry = config.registeredIds?.[policy.output.registryId];
5802
- 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);
5803
6150
  if (!registry || !validValues || registry.digest !== policy.output.registryDigest || await registeredIdRegistryDigest(registry.values) !== registry.digest) {
5804
6151
  throw new CamelError("state_conflict", "Registered-ID registry digest mismatch.");
5805
6152
  }
@@ -5807,63 +6154,63 @@ async function createConversionRegistry(config) {
5807
6154
  policies.set(policy.conversionId, Object.freeze(policy));
5808
6155
  }
5809
6156
  const outputCounts = /* @__PURE__ */ new Map();
5810
- const get = (id, kind) => {
5811
- const policy = policies.get(id);
6157
+ const get = (id2, kind) => {
6158
+ const policy = policies.get(id2);
5812
6159
  if (!policy || policy.output.kind !== kind) throw new CamelError("conversion_rejected", "Conversion policy is missing or has the wrong output kind.");
5813
6160
  return policy;
5814
6161
  };
5815
- const checked = (source, id, kind) => {
5816
- const policy = get(id, kind);
6162
+ const checked = (source, id2, kind) => {
6163
+ const policy = get(id2, kind);
5817
6164
  if (utf8Length(source.value) > policy.maximumSourceBytes) throw new CamelError("limit_exceeded", "Unsafe conversion input exceeds its byte bound.");
5818
6165
  return policy;
5819
6166
  };
5820
- const emit3 = (source, policy, value2) => convert(source, policy, value2, outputCounts);
6167
+ const emit4 = (source, policy, value2) => convert(source, policy, value2, outputCounts);
5821
6168
  const operations = Object.freeze({
5822
- boolean: async (value2, id) => {
5823
- const policy = checked(value2, id, "boolean");
5824
- 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));
5825
6172
  },
5826
- integer: async (value2, id) => {
5827
- const policy = checked(value2, id, "integer");
5828
- 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));
5829
6176
  },
5830
- finiteNumber: async (value2, id) => {
5831
- const policy = checked(value2, id, "finite_number");
5832
- 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));
5833
6180
  },
5834
- enum: async (value2, id) => {
5835
- const policy = checked(value2, id, "enum");
5836
- 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));
5837
6184
  },
5838
- date: async (value2, id) => {
5839
- const policy = checked(value2, id, "date");
5840
- 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));
5841
6188
  },
5842
- registeredId: async (value2, id) => {
5843
- const policy = checked(value2, id, "registered_id");
6189
+ registeredId: async (value2, id2) => {
6190
+ const policy = checked(value2, id2, "registered_id");
5844
6191
  const registry = config.registeredIds?.[policy.output.registryId];
5845
6192
  const output = typeof value2.value === "string" ? registry?.values[value2.value] : void 0;
5846
6193
  if (!output) throw new CamelError("conversion_rejected", "Registered-ID conversion rejected the candidate.");
5847
- return emit3(value2, policy, output);
6194
+ return emit4(value2, policy, output);
5848
6195
  },
5849
- digest: async (value2, id) => {
5850
- const policy = checked(value2, id, "digest");
6196
+ digest: async (value2, id2) => {
6197
+ const policy = checked(value2, id2, "digest");
5851
6198
  if (!(value2.value instanceof Uint8Array)) throw new CamelError("conversion_rejected", "Digest conversion requires bytes.");
5852
- return emit3(value2, policy, await sha256Hex(value2.value));
6199
+ return emit4(value2, policy, await sha256Hex(value2.value));
5853
6200
  },
5854
- measure: async (value2, metric, id) => {
5855
- const policy = checked(value2, id, "integer");
6201
+ measure: async (value2, metric, id2) => {
6202
+ const policy = checked(value2, id2, "integer");
5856
6203
  const measured = measure(value2.value, metric);
5857
- return emit3(value2, policy, boundedInteger(measured, policy.output));
6204
+ return emit4(value2, policy, boundedInteger(measured, policy.output));
5858
6205
  },
5859
- test: async (value2, predicateId, id) => {
5860
- const policy = checked(value2, id, "boolean");
6206
+ test: async (value2, predicateId, id2) => {
6207
+ const policy = checked(value2, id2, "boolean");
5861
6208
  const predicate = config.predicates?.[predicateId];
5862
6209
  if (!predicate) throw new CamelError("conversion_rejected", "Predicate is not registered.");
5863
- return emit3(value2, policy, evaluatePredicate(value2.value, predicate, config.registeredIds));
6210
+ return emit4(value2, policy, evaluatePredicate(value2.value, predicate, config.registeredIds));
5864
6211
  }
5865
6212
  });
5866
- return Object.freeze({ operations, policy: (id) => policies.get(id) ?? missingPolicy() });
6213
+ return Object.freeze({ operations, policy: (id2) => policies.get(id2) ?? missingPolicy() });
5867
6214
  }
5868
6215
  async function convert(source, policy, value2, counts) {
5869
6216
  const sourceKey = sourceIdentity(source);
@@ -5906,8 +6253,8 @@ function boundedInteger(value2, spec) {
5906
6253
  }
5907
6254
  function boundedNumber(value2, spec) {
5908
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.");
5909
- const text2 = String(value2);
5910
- 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.");
5911
6258
  return value2;
5912
6259
  }
5913
6260
  function enumMember(value2, spec) {
@@ -5958,10 +6305,10 @@ function createCamelIngress(constants2 = []) {
5958
6305
  const ingress = {
5959
6306
  userInstruction: (value2, input) => createSafeInternal(value2, "user_instruction", metadata("user_instruction", input.id, input.readers)),
5960
6307
  systemPolicy: (value2, input) => createSafeInternal(value2, "system_policy", metadata("system_policy", input.id, input.readers)),
5961
- control: (id) => {
5962
- const item = byId.get(id);
6308
+ control: (id2) => {
6309
+ const item = byId.get(id2);
5963
6310
  if (!item) throw new CamelError("permission_denied", "Unknown control constant.");
5964
- return createSafeInternal(item.value, "harness_constant", metadata("harness", id, item.readers));
6311
+ return createSafeInternal(item.value, "harness_constant", metadata("harness", id2, item.readers));
5965
6312
  },
5966
6313
  external: (value2, label) => createUnsafeInternal(value2, label),
5967
6314
  quarantinedOutput: (value2, input) => {
@@ -5985,9 +6332,9 @@ function assertNoUnsafeConstant(value2, seen = /* @__PURE__ */ new WeakSet()) {
5985
6332
  }
5986
6333
  for (const child of Object.values(value2)) assertNoUnsafeConstant(child, seen);
5987
6334
  }
5988
- function metadata(kind, id, readers) {
5989
- if (!id) throw new CamelError("state_conflict", "Provenance IDs must be non-empty.");
5990
- 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 }] };
5991
6338
  }
5992
6339
 
5993
6340
  // ../camel/dist/policy.js
@@ -6051,7 +6398,7 @@ function isControlOwned(value2) {
6051
6398
  return value2.label.safeBasis === "system_policy" || value2.label.safeBasis === "harness_constant";
6052
6399
  }
6053
6400
  function copyRegistries(registries) {
6054
- 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]) })])));
6055
6402
  }
6056
6403
  function validateUnsafeSelector(path, value2, tool) {
6057
6404
  const policy = tool.unsafeSelectorPolicy;
@@ -6063,20 +6410,20 @@ function validateUnsafeSelector(path, value2, tool) {
6063
6410
  return void 0;
6064
6411
  }
6065
6412
  function looksLikeDestination(value2) {
6066
- const text2 = value2.trim();
6067
- 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);
6068
6415
  }
6069
6416
 
6070
- // ../harness/dist/chunk-5FFR7U4L.js
6417
+ // ../harness/dist/chunk-ANNX7VGK.js
6071
6418
  import { readFile as readFile4, stat as stat2 } from "fs/promises";
6072
6419
  import { readFile as readFile3 } from "fs/promises";
6073
6420
  import { join as join33 } from "path";
6074
6421
 
6075
6422
  // ../graph/dist/chunk-PS2SO4UP.js
6076
6423
  var nodeId = (kind, name) => `${kind}:${name}`;
6077
- function parseNodeId(id) {
6078
- const at = id.indexOf(":");
6079
- 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) };
6080
6427
  }
6081
6428
  var GraphBuilder = class {
6082
6429
  byId = /* @__PURE__ */ new Map();
@@ -6084,14 +6431,14 @@ var GraphBuilder = class {
6084
6431
  seen = /* @__PURE__ */ new Set();
6085
6432
  /** Add or enrich a node. Later attributes win; the kind never changes. */
6086
6433
  node(kind, name, attrs) {
6087
- const id = nodeId(kind, name);
6088
- const existing = this.byId.get(id);
6434
+ const id2 = nodeId(kind, name);
6435
+ const existing = this.byId.get(id2);
6089
6436
  if (existing) {
6090
- if (attrs) this.byId.set(id, { ...existing, attrs: { ...existing.attrs, ...attrs } });
6091
- return id;
6437
+ if (attrs) this.byId.set(id2, { ...existing, attrs: { ...existing.attrs, ...attrs } });
6438
+ return id2;
6092
6439
  }
6093
- this.byId.set(id, { id, kind, name, ...attrs ? { attrs } : {} });
6094
- return id;
6440
+ this.byId.set(id2, { id: id2, kind, name, ...attrs ? { attrs } : {} });
6441
+ return id2;
6095
6442
  }
6096
6443
  /**
6097
6444
  * Add a directed edge, minting either endpoint if it is not known yet.
@@ -6101,10 +6448,10 @@ var GraphBuilder = class {
6101
6448
  * by how often someone repeated an import.
6102
6449
  */
6103
6450
  edge(from, kind, to, attrs) {
6104
- for (const id of [from, to]) {
6105
- if (!this.byId.has(id)) {
6106
- const parsed = parseNodeId(id);
6107
- 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 });
6108
6455
  }
6109
6456
  }
6110
6457
  const key = `${from} ${kind} ${to}`;
@@ -6137,18 +6484,18 @@ function nodesOfKind(graph, kind) {
6137
6484
 
6138
6485
  // ../graph/dist/index.js
6139
6486
  var follows = (kinds, edge) => !kinds || kinds.includes(edge.kind);
6140
- function incident(graph, id, traversal = {}) {
6487
+ function incident(graph, id2, traversal = {}) {
6141
6488
  const direction = traversal.direction ?? "out";
6142
- const forward = direction === "out" || direction === "both" ? graph.out.get(id) ?? [] : [];
6143
- 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) ?? [] : [];
6144
6491
  return [...forward, ...backward].filter((edge) => follows(traversal.kinds, edge));
6145
6492
  }
6146
6493
  var otherEnd = (edge, from) => edge.from === from ? edge.to : edge.from;
6147
- function neighbors(graph, id, traversal = {}) {
6494
+ function neighbors(graph, id2, traversal = {}) {
6148
6495
  const seen = /* @__PURE__ */ new Set();
6149
- for (const edge of incident(graph, id, traversal)) {
6150
- const other = otherEnd(edge, id);
6151
- 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);
6152
6499
  }
6153
6500
  return [...seen];
6154
6501
  }
@@ -6232,9 +6579,9 @@ async function extractImports(builder, input) {
6232
6579
  const sources = input.paths.filter(isSourcePath);
6233
6580
  const known = new Set(sources);
6234
6581
  for (const path of sources) {
6235
- let text2;
6582
+ let text3;
6236
6583
  try {
6237
- text2 = await input.read(path);
6584
+ text3 = await input.read(path);
6238
6585
  } catch {
6239
6586
  continue;
6240
6587
  }
@@ -6242,13 +6589,13 @@ async function extractImports(builder, input) {
6242
6589
  const file = builder.node(FILE, path, pkg ? { pkg } : void 0);
6243
6590
  if (pkg) builder.edge(builder.node(PACKAGE, pkg), CONTAINS, file);
6244
6591
  const specifiers = /* @__PURE__ */ new Set();
6245
- for (const match of text2.matchAll(IMPORT_FROM)) specifiers.add(match[1]);
6246
- 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]);
6247
6594
  for (const specifier of specifiers) {
6248
6595
  const resolved = resolveImport(path, specifier, known);
6249
6596
  if (resolved) builder.edge(file, IMPORTS, nodeId(FILE, resolved));
6250
6597
  }
6251
- for (const name of exportedNames(text2)) {
6598
+ for (const name of exportedNames(text3)) {
6252
6599
  builder.edge(file, EXPORTS, builder.node(SYMBOL, name));
6253
6600
  }
6254
6601
  }
@@ -6289,16 +6636,16 @@ async function extractData(builder, input) {
6289
6636
  };
6290
6637
  for (const path of input.paths) {
6291
6638
  if (!SOURCE_FILE.test(path) || input.ignore?.(path)) continue;
6292
- let text2;
6639
+ let text3;
6293
6640
  try {
6294
- text2 = await input.read(path);
6641
+ text3 = await input.read(path);
6295
6642
  } catch {
6296
6643
  continue;
6297
6644
  }
6298
- for (const statement of text2.matchAll(STATEMENT)) {
6645
+ for (const statement of text3.matchAll(STATEMENT)) {
6299
6646
  const verb = statement[1].toUpperCase().replace(/\s+/g, " ");
6300
6647
  const start = statement.index ?? 0;
6301
- const rest = text2.slice(start + statement[0].length, start + STATEMENT_WINDOW);
6648
+ const rest = text3.slice(start + statement[0].length, start + STATEMENT_WINDOW);
6302
6649
  if (verb === "SELECT") {
6303
6650
  for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
6304
6651
  continue;
@@ -6314,16 +6661,16 @@ async function extractData(builder, input) {
6314
6661
  for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
6315
6662
  }
6316
6663
  }
6317
- for (const match of text2.matchAll(NS_CONST)) {
6318
- 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));
6319
6666
  }
6320
- for (const match of text2.matchAll(NS_LITERAL)) {
6321
- 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));
6322
6669
  }
6323
6670
  }
6324
6671
  }
6325
- function accessFor(text2, index) {
6326
- 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);
6327
6674
  return /\b(?:transact|update|delete|create|insert|Ops)\b/.test(window) ? WRITES : READS;
6328
6675
  }
6329
6676
  async function buildCodeGraph(input) {
@@ -6335,7 +6682,7 @@ async function buildCodeGraph(input) {
6335
6682
  return builder.build();
6336
6683
  }
6337
6684
 
6338
- // ../harness/dist/chunk-5FFR7U4L.js
6685
+ // ../harness/dist/chunk-ANNX7VGK.js
6339
6686
  import { createHash as createHash32 } from "crypto";
6340
6687
  async function digestStagedWorkspace(root, limits) {
6341
6688
  const files = [];
@@ -6454,13 +6801,13 @@ function createCodeRuntimeControlClient(options) {
6454
6801
  if (!Number.isSafeInteger(modelRequestTimeoutMs) || modelRequestTimeoutMs < 3e4 || modelRequestTimeoutMs > 30 * 6e4) {
6455
6802
  throw new TypeError("modelRequestTimeoutMs must be an integer from 30000 to 1800000");
6456
6803
  }
6457
- const request2 = options.fetch ?? fetch;
6804
+ const request3 = options.fetch ?? fetch;
6458
6805
  const call2 = async (path, body, timeoutMs = requestTimeoutMs) => {
6459
6806
  const timeout = AbortSignal.timeout(timeoutMs);
6460
6807
  const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
6461
6808
  let response2;
6462
6809
  try {
6463
- response2 = await request2(`${endpoint}${path}`, {
6810
+ response2 = await request3(`${endpoint}${path}`, {
6464
6811
  method: "POST",
6465
6812
  headers: { authorization: `Bearer ${options.token}`, "content-type": "application/json" },
6466
6813
  body: JSON.stringify(body),
@@ -6473,7 +6820,7 @@ function createCodeRuntimeControlClient(options) {
6473
6820
  }
6474
6821
  const value2 = await response2.json().catch(() => null);
6475
6822
  if (!response2.ok) {
6476
- const problem = record4(record4(value2)?.error);
6823
+ const problem = record5(record5(value2)?.error);
6477
6824
  throw new CodeRuntimeControlError(
6478
6825
  typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
6479
6826
  response2.status,
@@ -6495,12 +6842,12 @@ function createCodeRuntimeControlClient(options) {
6495
6842
  await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
6496
6843
  ),
6497
6844
  infer: async (sessionId, inference) => {
6498
- const value2 = record4(await call2(
6845
+ const value2 = record5(await call2(
6499
6846
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
6500
6847
  inference,
6501
6848
  modelRequestTimeoutMs
6502
6849
  ));
6503
- 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)) {
6504
6851
  throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
6505
6852
  }
6506
6853
  return value2;
@@ -6522,6 +6869,16 @@ function createCodeRuntimeControlClient(options) {
6522
6869
  }
6523
6870
  await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
6524
6871
  },
6872
+ recallMemories: async (sessionId, subjects, limit) => {
6873
+ const response2 = await call2(
6874
+ `/registry/code/runtime/sessions/${validSessionId(sessionId)}/recall`,
6875
+ { subjects: [...subjects], limit }
6876
+ );
6877
+ return Array.isArray(response2.memories) ? response2.memories : [];
6878
+ },
6879
+ rememberMemory: async (sessionId, memory) => {
6880
+ await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/remember`, memory);
6881
+ },
6525
6882
  reportSessionFailure: async (sessionId, message2) => {
6526
6883
  if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
6527
6884
  await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
@@ -6558,12 +6915,12 @@ function validateHeartbeat(version, capabilities) {
6558
6915
  }
6559
6916
  }
6560
6917
  function parseSnapshot(value2) {
6561
- const root = record4(value2);
6562
- const host = record4(root?.host);
6918
+ const root = record5(value2);
6919
+ const host = record5(root?.host);
6563
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");
6564
6921
  const bindingIds = /* @__PURE__ */ new Set();
6565
6922
  const bindings = root.bindings.map((item) => {
6566
- const binding = record4(item);
6923
+ const binding = record5(item);
6567
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)) {
6568
6925
  throw invalid("binding");
6569
6926
  }
@@ -6573,10 +6930,10 @@ function parseSnapshot(value2) {
6573
6930
  const commandIds = /* @__PURE__ */ new Set();
6574
6931
  const commandSequences = /* @__PURE__ */ new Set();
6575
6932
  const commands = root.commands.map((item) => {
6576
- const command = record4(item);
6933
+ const command = record5(item);
6577
6934
  const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
6578
6935
  const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
6579
- 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");
6580
6937
  commandIds.add(command.commandId);
6581
6938
  commandSequences.add(sequenceKey);
6582
6939
  return command;
@@ -6584,10 +6941,10 @@ function parseSnapshot(value2) {
6584
6941
  return { host, bindings, commands };
6585
6942
  }
6586
6943
  async function parseSource(value2) {
6587
- const snapshot = record4(record4(value2)?.snapshot);
6944
+ const snapshot = record5(record5(value2)?.snapshot);
6588
6945
  if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
6589
6946
  const files = snapshot.files.map((value22) => {
6590
- const file = record4(value22);
6947
+ const file = record5(value22);
6591
6948
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
6592
6949
  return { path: file.path, content: file.content };
6593
6950
  });
@@ -6596,11 +6953,11 @@ async function parseSource(value2) {
6596
6953
  const aliases = /* @__PURE__ */ new Set();
6597
6954
  const references = [];
6598
6955
  for (const item of referencesValue) {
6599
- const reference = record4(item);
6956
+ const reference = record5(item);
6600
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");
6601
6958
  aliases.add(reference.alias);
6602
6959
  const referenceFiles = reference.files.map((entry) => {
6603
- const file = record4(entry);
6960
+ const file = record5(entry);
6604
6961
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
6605
6962
  return { path: file.path, content: file.content };
6606
6963
  });
@@ -6615,18 +6972,18 @@ async function parseSource(value2) {
6615
6972
  return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
6616
6973
  }
6617
6974
  function parseReview(value2) {
6618
- const review = record4(record4(value2)?.review);
6975
+ const review = record5(record5(value2)?.review);
6619
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");
6620
6977
  return review;
6621
6978
  }
6622
6979
  function parseCandidate(value2) {
6623
- const candidate = record4(record4(value2)?.candidate);
6980
+ const candidate = record5(record5(value2)?.candidate);
6624
6981
  if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
6625
6982
  throw invalid("candidate");
6626
6983
  }
6627
6984
  return { candidateId: candidate.candidateId, status: candidate.status };
6628
6985
  }
6629
- 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;
6630
6987
  var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
6631
6988
  var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
6632
6989
  var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
@@ -6722,8 +7079,8 @@ function gitApply(cwd, patch2, check) {
6722
7079
  });
6723
7080
  let stderr = "";
6724
7081
  child.stderr.setEncoding("utf8");
6725
- child.stderr.on("data", (text2) => {
6726
- 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);
6727
7084
  });
6728
7085
  child.once("error", reject);
6729
7086
  child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr.trim().slice(0, 500)))));
@@ -7591,7 +7948,7 @@ async function runCodeAgentAttempt(options) {
7591
7948
  }
7592
7949
  }
7593
7950
  async function handleCodeRuntimeInference(input) {
7594
- const { command, metadata: metadata2, request: request2, state: state2 } = input;
7951
+ const { command, metadata: metadata2, request: request3, state: state2 } = input;
7595
7952
  if (state2.tokens >= metadata2.maxTokensPerInteraction) {
7596
7953
  if (!state2.noticeEmitted) {
7597
7954
  state2.noticeEmitted = true;
@@ -7604,7 +7961,7 @@ async function handleCodeRuntimeInference(input) {
7604
7961
  return {
7605
7962
  protocolVersion: HARNESS_PROTOCOL_VERSION,
7606
7963
  type: "inference.response",
7607
- requestId: request2.requestId,
7964
+ requestId: request3.requestId,
7608
7965
  response: {
7609
7966
  id: `budget:${command.commandId}`,
7610
7967
  provider: "openai",
@@ -7618,9 +7975,9 @@ async function handleCodeRuntimeInference(input) {
7618
7975
  }
7619
7976
  const startedAt = Date.now();
7620
7977
  const response2 = await input.control.infer(command.sessionId, {
7621
- requestId: request2.requestId,
7978
+ requestId: request3.requestId,
7622
7979
  interactionId: command.commandId,
7623
- call: request2.call
7980
+ call: request3.call
7624
7981
  });
7625
7982
  state2.tokens += response2.receipt.inputTokens + response2.receipt.outputTokens;
7626
7983
  await input.event({
@@ -7637,14 +7994,14 @@ async function handleCodeRuntimeInference(input) {
7637
7994
  return {
7638
7995
  protocolVersion: HARNESS_PROTOCOL_VERSION,
7639
7996
  type: "inference.response",
7640
- requestId: request2.requestId,
7997
+ requestId: request3.requestId,
7641
7998
  response: response2.response
7642
7999
  };
7643
8000
  }
7644
8001
  function createCodeRuntimeInference(options) {
7645
8002
  let seq = 0;
7646
8003
  return {
7647
- chat: async (request2) => {
8004
+ chat: async (request3) => {
7648
8005
  const requestId = `${options.command.commandId}:${++seq}`;
7649
8006
  const answer = await handleCodeRuntimeInference({
7650
8007
  command: options.command,
@@ -7656,7 +8013,7 @@ function createCodeRuntimeInference(options) {
7656
8013
  protocolVersion: HARNESS_PROTOCOL_VERSION,
7657
8014
  type: "inference.request",
7658
8015
  requestId,
7659
- call: request2
8016
+ call: request3
7660
8017
  }
7661
8018
  });
7662
8019
  if (answer.type !== "inference.response") throw new TypeError("brokered inference returned the wrong frame");
@@ -7862,9 +8219,9 @@ async function safePrefix(base, paths, prefix) {
7862
8219
  function descriptor(name, effect, argumentRoles) {
7863
8220
  return { name, version: 1, effect, inputSchema: { type: "object" }, argumentRoles, policyId: `odla.code.${name}.v1` };
7864
8221
  }
7865
- async function conversionPolicy(id, output) {
8222
+ async function conversionPolicy(id2, output) {
7866
8223
  const definition = {
7867
- conversionId: id,
8224
+ conversionId: id2,
7868
8225
  version: 1,
7869
8226
  output,
7870
8227
  maximumSourceBytes: 1e6,
@@ -7873,18 +8230,18 @@ async function conversionPolicy(id, output) {
7873
8230
  };
7874
8231
  return { ...definition, digest: await conversionPolicyDigest(definition) };
7875
8232
  }
7876
- async function registeredPolicy(id, registryId, values) {
8233
+ async function registeredPolicy(id2, registryId, values) {
7877
8234
  const mapping = Object.fromEntries(values.map((value2) => [value2, value2]));
7878
- return conversionPolicy(id, {
8235
+ return conversionPolicy(id2, {
7879
8236
  kind: "registered_id",
7880
8237
  registryId,
7881
8238
  registryDigest: await registeredIdRegistryDigest(mapping)
7882
8239
  });
7883
8240
  }
7884
8241
  async function conversionRegistry(policies, values) {
7885
- 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]) => {
7886
8243
  const mapping = Object.fromEntries(entries.map((value2) => [value2, value2]));
7887
- return [id, { values: mapping, digest: await registeredIdRegistryDigest(mapping) }];
8244
+ return [id2, { values: mapping, digest: await registeredIdRegistryDigest(mapping) }];
7888
8245
  })));
7889
8246
  return createConversionRegistry({ policies, registeredIds });
7890
8247
  }
@@ -7938,10 +8295,10 @@ function decision(input, policy, approvalConsumed, tool, actionDigest) {
7938
8295
  actionDigest: actionDigest ?? (policy.outcome === "require_approval" ? policy.actionDigest : "")
7939
8296
  };
7940
8297
  }
7941
- function policyContext(context, request2, options, extra) {
8298
+ function policyContext(context, request3, options, extra) {
7942
8299
  return {
7943
8300
  lease: context.lease,
7944
- request: request2,
8301
+ request: request3,
7945
8302
  workspaceId: `workspace:${context.lease.task.attemptId}`,
7946
8303
  readers: { kind: "principals", principalIds: [options.readerId] },
7947
8304
  ...extra
@@ -7960,8 +8317,8 @@ function optionalInteger(value2) {
7960
8317
  if (!Number.isSafeInteger(value2) || value2 < 1) throw new TypeError("line bounds must be positive integers");
7961
8318
  return value2;
7962
8319
  }
7963
- function response(request2, ok, content2, details) {
7964
- 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 } : {} };
7965
8322
  }
7966
8323
  var cache = /* @__PURE__ */ new Map();
7967
8324
  function workspaceGraphs(workspaceDir, paths) {
@@ -7977,7 +8334,7 @@ function workspaceGraphs(workspaceDir, paths) {
7977
8334
  cache.set(workspaceDir, built);
7978
8335
  return built;
7979
8336
  }
7980
- var shortId = (id) => id.slice(id.indexOf(":") + 1);
8337
+ var shortId = (id2) => id2.slice(id2.indexOf(":") + 1);
7981
8338
  function renderOverview(graphs, prefix) {
7982
8339
  const rows = rollup(graphs.graph, FILE, prefix === void 0 ? {} : { prefix });
7983
8340
  if (rows.length === 0) return prefix ? `No source under "${prefix}".` : "No source files.";
@@ -7986,19 +8343,19 @@ function renderOverview(graphs, prefix) {
7986
8343
  return [`${total} source files. Directories, largest first \u2014 read one with sandbox.list --prefix.`, ...lines].join("\n");
7987
8344
  }
7988
8345
  function renderWhereIs(graphs, symbol) {
7989
- const sites = neighbors(graphs.graph, nodeId(SYMBOL, symbol), { direction: "in", kinds: ["exports"] }).map((id) => ({
7990
- path: shortId(id),
7991
- pkg: neighbors(graphs.graph, id, { direction: "in", kinds: ["contains"] })[0],
7992
- 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
7993
8350
  })).sort((left, right) => right.dependents - left.dependents || left.path.localeCompare(right.path));
7994
8351
  if (sites.length === 0) return `No exported symbol named "${symbol}". Try sandbox.search for a textual match.`;
7995
8352
  return sites.slice(0, 20).map((site) => `${site.path}${site.pkg ? ` [${shortId(site.pkg)}]` : ""} ${site.dependents} dependents`).join("\n");
7996
8353
  }
7997
8354
  function renderWhoImports(graphs, path) {
7998
- const id = nodeId(FILE, path);
7999
- 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] });
8000
8357
  if (importers.length === 0) {
8001
- 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.`;
8002
8359
  }
8003
8360
  return importers.slice(0, 40).map(shortId).sort().join("\n");
8004
8361
  }
@@ -8021,11 +8378,11 @@ var GRAPH_TOOLS = /* @__PURE__ */ new Set([
8021
8378
  "sandbox.who_imports",
8022
8379
  "sandbox.who_touches"
8023
8380
  ]);
8024
- async function read(context, request2, options, policy) {
8025
- exactKeys(request2.input, ["path", "startLine", "endLine"]);
8026
- const path = stringField(request2.input, "path");
8027
- const startLine = optionalInteger(request2.input.startLine) ?? 1;
8028
- 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;
8029
8386
  if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
8030
8387
  throw new TypeError("requested line range exceeds its bound");
8031
8388
  }
@@ -8033,8 +8390,8 @@ async function read(context, request2, options, policy) {
8033
8390
  if (!paths.includes(path)) {
8034
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.`);
8035
8392
  }
8036
- const allowed = await policy.read(policyContext(context, request2, options, { paths, path, startLine, endLine }));
8037
- 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");
8038
8395
  const target = resolveCodePath(context.workspaceDir, path);
8039
8396
  const info = await stat2(target);
8040
8397
  if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
@@ -8047,74 +8404,74 @@ async function read(context, request2, options, policy) {
8047
8404
  if (Buffer.byteLength(content2) > (options.maxReadBytes ?? 128 * 1024)) {
8048
8405
  throw new TypeError("read result exceeds its byte bound");
8049
8406
  }
8050
- 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) });
8051
8408
  }
8052
- async function list(context, request2, options, policy) {
8053
- exactKeys(request2.input, ["prefix", "maxEntries"]);
8054
- 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;
8055
8412
  const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
8056
- const maxEntries = optionalInteger(request2.input.maxEntries) ?? 1e3;
8413
+ const maxEntries = optionalInteger(request3.input.maxEntries) ?? 1e3;
8057
8414
  if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
8058
8415
  const paths = await registeredFiles(context.workspaceDir, 2e4);
8059
- const allowed = await policy.list(policyContext(context, request2, options, { paths, ...prefix ? { prefix } : {} }));
8060
- 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");
8061
8418
  const entries = listWorkspace(paths, { ...prefix ? { prefix } : {}, maxEntries });
8062
8419
  if (!entries.length) {
8063
- 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 });
8064
8421
  }
8065
8422
  const truncated = entries.length < paths.length && entries.length === maxEntries;
8066
8423
  const hint = !prefix && paths.length > 500 ? `
8067
8424
  \u2026 ${paths.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
8068
8425
  return response(
8069
- request2,
8426
+ request3,
8070
8427
  true,
8071
8428
  `${entries.join("\n")}${truncated ? `
8072
8429
  \u2026 truncated at ${maxEntries} entries` : ""}${hint}`,
8073
8430
  { count: entries.length, truncated }
8074
8431
  );
8075
8432
  }
8076
- async function search(context, request2, options, policy) {
8077
- exactKeys(request2.input, ["query", "prefix", "maxResults", "caseSensitive"]);
8078
- 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");
8079
8436
  if (query.length > 512) throw new TypeError("search query exceeds its bound");
8080
- const raw = request2.input.prefix;
8437
+ const raw = request3.input.prefix;
8081
8438
  const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
8082
- const maxResults = optionalInteger(request2.input.maxResults) ?? 100;
8439
+ const maxResults = optionalInteger(request3.input.maxResults) ?? 100;
8083
8440
  if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
8084
- 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;
8085
8442
  const paths = await registeredFiles(context.workspaceDir, 2e4);
8086
- const allowed = await policy.search(policyContext(context, request2, options, { paths, query, ...prefix ? { prefix } : {} }));
8087
- 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");
8088
8445
  const matches = await searchWorkspace(context.workspaceDir, paths, {
8089
8446
  query,
8090
8447
  maxResults,
8091
8448
  caseSensitive,
8092
8449
  ...prefix ? { prefix } : {}
8093
8450
  });
8094
- if (!matches.length) return response(request2, true, `No match for "${query}".`, { count: 0 });
8095
- 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"), {
8096
8453
  count: matches.length
8097
8454
  });
8098
8455
  }
8099
- async function graphQuery(context, request2, options, policy) {
8100
- exactKeys(request2.input, ["query"]);
8101
- const raw = request2.input.query;
8456
+ async function graphQuery(context, request3, options, policy) {
8457
+ exactKeys(request3.input, ["query"]);
8458
+ const raw = request3.input.query;
8102
8459
  const query = typeof raw === "string" ? raw : "";
8103
8460
  if (query.length > 512) throw new TypeError("query exceeds its bound");
8104
- const allowed = await policy.graph(policyContext(context, request2, options, {
8105
- tool: request2.tool,
8461
+ const allowed = await policy.graph(policyContext(context, request3, options, {
8462
+ tool: request3.tool,
8106
8463
  selector: query
8107
8464
  }));
8108
- if (!allowed) return response(request2, false, "tool denied by CaMeL policy");
8465
+ if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8109
8466
  const paths = await registeredFiles(context.workspaceDir, 2e4);
8110
8467
  const graphs = await workspaceGraphs(context.workspaceDir, paths);
8111
- if (request2.tool === "sandbox.overview") {
8112
- 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));
8113
8470
  }
8114
- if (!query) throw new TypeError(`${request2.tool} requires a query`);
8115
- if (request2.tool === "sandbox.where_is") return response(request2, true, renderWhereIs(graphs, query));
8116
- if (request2.tool === "sandbox.who_imports") return response(request2, true, renderWhoImports(graphs, query));
8117
- 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));
8118
8475
  }
8119
8476
  function createCodeToolBroker(options) {
8120
8477
  validateOptions(options);
@@ -8122,24 +8479,24 @@ function createCodeToolBroker(options) {
8122
8479
  const policy = createCodePolicyGate(options);
8123
8480
  let tail = Promise.resolve();
8124
8481
  return {
8125
- execute(context, request2) {
8126
- 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));
8127
8484
  tail = result.then(() => void 0, () => void 0);
8128
8485
  return result;
8129
8486
  }
8130
8487
  };
8131
8488
  }
8132
- async function route(context, request2, options, recipes, policy) {
8489
+ async function route2(context, request3, options, recipes, policy) {
8133
8490
  try {
8134
8491
  if (context.signal?.aborted) throw new TypeError("tool request was cancelled");
8135
- if (request2.tool === "sandbox.read") return await read(context, request2, options, policy);
8136
- if (request2.tool === "sandbox.list") return await list(context, request2, options, policy);
8137
- if (request2.tool === "sandbox.search") return await search(context, request2, options, policy);
8138
- if (GRAPH_TOOLS.has(request2.tool)) return await graphQuery(context, request2, options, policy);
8139
- if (request2.tool === "sandbox.apply_patch") return await patch(context, request2, options, policy);
8140
- 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);
8141
8498
  } catch (reason) {
8142
- return response(request2, false, toolFailureMessage(reason));
8499
+ return response(request3, false, toolFailureMessage(reason));
8143
8500
  }
8144
8501
  }
8145
8502
  function toolFailureMessage(reason) {
@@ -8151,34 +8508,34 @@ function toolFailureMessage(reason) {
8151
8508
  if (code === "EACCES" || code === "EPERM") return "that path is not readable through this tool";
8152
8509
  return "tool failed closed";
8153
8510
  }
8154
- async function patch(context, request2, options, policy) {
8155
- exactKeys(request2.input, ["patch"]);
8156
- 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");
8157
8514
  const paths = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
8158
8515
  if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
8159
8516
  throw new TypeError("patch targets a read-only reference source");
8160
8517
  }
8161
- const allowed = await policy.patch(policyContext(context, request2, options, { patch: value2 }));
8162
- 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");
8163
8520
  await applyCodePatch(context.workspaceDir, value2, paths);
8164
- 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 });
8165
8522
  }
8166
- async function recipe(context, request2, options, recipes, policy) {
8167
- exactKeys(request2.input, ["recipeId"]);
8168
- 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");
8169
8526
  const digestLimits = {
8170
8527
  maxFiles: options.maxRecipeWorkspaceFiles ?? 2e4,
8171
8528
  maxBytes: options.maxRecipeWorkspaceBytes ?? 512 * 1024 * 1024
8172
8529
  };
8173
8530
  const sourceDigest = await digestStagedWorkspace(context.workspaceDir, digestLimits);
8174
- const allowed = await policy.recipe(policyContext(context, request2, options, {
8531
+ const allowed = await policy.recipe(policyContext(context, request3, options, {
8175
8532
  recipeIds: [...recipes.keys()].sort(),
8176
8533
  recipeId,
8177
8534
  sourceDigest
8178
8535
  }));
8179
- if (!allowed) return response(request2, false, "tool denied by CaMeL policy");
8536
+ if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8180
8537
  const selected = recipes.get(recipeId);
8181
- if (!selected) return response(request2, false, "build recipe is not registered");
8538
+ if (!selected) return response(request3, false, "build recipe is not registered");
8182
8539
  const staged = await stageWorkspace(context.workspaceDir, {
8183
8540
  maxFiles: digestLimits.maxFiles,
8184
8541
  maxBytes: digestLimits.maxBytes
@@ -8195,7 +8552,7 @@ async function recipe(context, request2, options, recipes, policy) {
8195
8552
  const output = [result.stdout, result.stderr].filter(Boolean).join("\n");
8196
8553
  const ok = result.exitCode === 0 && !result.outputLimitExceeded && !result.timedOut;
8197
8554
  const status = result.timedOut ? "timed out" : result.outputLimitExceeded ? "exceeded output limit" : ok ? "passed" : `failed with exit ${result.exitCode}`;
8198
- return response(request2, ok, `Recipe ${recipeId} ${status}.${output ? `
8555
+ return response(request3, ok, `Recipe ${recipeId} ${status}.${output ? `
8199
8556
  ${output}` : ""}`, {
8200
8557
  recipeId,
8201
8558
  exitCode: result.exitCode,
@@ -8216,13 +8573,36 @@ function validateOptions(options) {
8216
8573
  throw new TypeError("Code tool broker read-only prefix is invalid");
8217
8574
  }
8218
8575
  }
8576
+ var MAX_MEMORY_BODY = 4e3;
8577
+ function validateMemory(memory) {
8578
+ if (!memory.subject.includes(":")) {
8579
+ throw new TypeError(`memory subject must be a graph node id, got "${memory.subject}"`);
8580
+ }
8581
+ const body = memory.body.trim();
8582
+ if (!body) throw new TypeError("a memory needs a body");
8583
+ if (body.length > MAX_MEMORY_BODY) throw new TypeError("memory body exceeds its bound");
8584
+ if (!memory.authorId.trim()) throw new TypeError("a memory needs an author");
8585
+ }
8586
+ function hazardFromAttempt(input) {
8587
+ const body = [
8588
+ `Attempt ${input.attempt} at "${input.goal.slice(0, 200)}" failed its proof.`,
8589
+ input.feedback.replace(/\s+/g, " ").slice(0, MAX_MEMORY_BODY - 300)
8590
+ ].join(" ");
8591
+ return input.touched.slice(0, 10).map((path) => ({
8592
+ subject: path.includes(":") ? path : `file:${path}`,
8593
+ kind: "hazard",
8594
+ body,
8595
+ evidence: { kind: "gate", ref: input.verificationId },
8596
+ authorId: input.authorId
8597
+ }));
8598
+ }
8219
8599
  async function runGoal(spec, attempt) {
8220
8600
  assertBudget(spec.budget);
8221
8601
  const now = spec.now ?? Date.now;
8222
8602
  const startedAt = now();
8223
8603
  const attempts = [];
8224
8604
  const boardErrors = [];
8225
- const emit3 = async (event) => {
8605
+ const emit4 = async (event) => {
8226
8606
  if (!spec.onEvent) return;
8227
8607
  try {
8228
8608
  await spec.onEvent(event);
@@ -8235,7 +8615,7 @@ async function runGoal(spec, attempt) {
8235
8615
  let costKnown = false;
8236
8616
  const finish2 = async (stoppedReason) => {
8237
8617
  const met = stoppedReason === "proof_passed";
8238
- 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 } : {} } : {
8239
8619
  type: "goal_abandoned",
8240
8620
  reason: stoppedReason,
8241
8621
  attempts: attempts.length,
@@ -8256,7 +8636,7 @@ async function runGoal(spec, attempt) {
8256
8636
  if (spec.signal?.aborted) return finish2("cancelled");
8257
8637
  if (spec.budget.deadline !== void 0 && now() >= spec.budget.deadline) return finish2("deadline");
8258
8638
  const prompt = index === 1 ? openingPrompt(spec) : retryPrompt(spec, attempts.at(-1));
8259
- await emit3({ type: "attempt_started", attempt: index, prompt });
8639
+ await emit4({ type: "attempt_started", attempt: index, prompt });
8260
8640
  const outcome = await attempt({
8261
8641
  attempt: index,
8262
8642
  prompt,
@@ -8276,7 +8656,7 @@ async function runGoal(spec, attempt) {
8276
8656
  ...outcome.error === void 0 ? {} : { error: outcome.error }
8277
8657
  });
8278
8658
  if (outcome.gatePassed) return finish2("proof_passed");
8279
- await emit3({
8659
+ await emit4({
8280
8660
  type: "attempt_failed",
8281
8661
  attempt: index,
8282
8662
  feedback: outcome.feedback,
@@ -8325,7 +8705,7 @@ function createCodeRuntimeToolBroker(input, lease, role) {
8325
8705
  readerId: `code-session:${lease.task.taskId}`,
8326
8706
  readOnlyPrefixes: [".odla-references"]
8327
8707
  });
8328
- 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" }) };
8329
8709
  }
8330
8710
  var POSITIVE = (value2) => Number.isFinite(value2) && Number(value2) > 0 ? Number(value2) : void 0;
8331
8711
  function codeGoalSpec(payload) {
@@ -8407,6 +8787,9 @@ function pursueRuntimeGoal(input) {
8407
8787
  return { gatePassed: false, feedback: "", tokens: outcome.tokens, error: outcome.error };
8408
8788
  }
8409
8789
  const verdict = await input.gate(attempt);
8790
+ if (!verdict.passed && input.memory) {
8791
+ await rememberFailure(input, attempt, verdict.feedback);
8792
+ }
8410
8793
  return {
8411
8794
  gatePassed: verdict.passed,
8412
8795
  feedback: verdict.feedback,
@@ -8417,6 +8800,25 @@ function pursueRuntimeGoal(input) {
8417
8800
  }
8418
8801
  );
8419
8802
  }
8803
+ async function rememberFailure(input, attempt, feedback) {
8804
+ if (!input.memory || !feedback.trim()) return;
8805
+ try {
8806
+ const touched = await input.touched?.(attempt) ?? [];
8807
+ if (touched.length === 0) return;
8808
+ for (const memory of hazardFromAttempt({
8809
+ goal: input.spec.goal,
8810
+ attempt,
8811
+ feedback,
8812
+ touched,
8813
+ verificationId: `goal-${attempt}`,
8814
+ authorId: input.memory.authorId
8815
+ })) {
8816
+ validateMemory(memory);
8817
+ await input.memory.store.remember(memory);
8818
+ }
8819
+ } catch {
8820
+ }
8821
+ }
8420
8822
  function goalEventLine(event) {
8421
8823
  if (event.type === "attempt_started") return `Goal attempt ${event.attempt} starting.`;
8422
8824
  if (event.type === "attempt_failed") return `Attempt ${event.attempt} did not satisfy the proof.`;
@@ -8691,18 +9093,18 @@ var CodePiRuntimeEngine = class {
8691
9093
  /** Report every brokered effect as it starts and finishes. */
8692
9094
  #observed(command, active, broker) {
8693
9095
  return {
8694
- execute: async (context, request2) => {
9096
+ execute: async (context, request3) => {
8695
9097
  const startedAt = Date.now();
8696
9098
  await this.#event(
8697
9099
  command,
8698
- { type: "tool", phase: "started", tool: request2.tool },
9100
+ { type: "tool", phase: "started", tool: request3.tool },
8699
9101
  active.conversationRefs
8700
9102
  ).catch(() => void 0);
8701
- const response2 = await broker.execute(context, request2);
9103
+ const response2 = await broker.execute(context, request3);
8702
9104
  await this.#event(command, {
8703
9105
  type: "tool",
8704
9106
  phase: "completed",
8705
- tool: request2.tool,
9107
+ tool: request3.tool,
8706
9108
  ok: response2.ok,
8707
9109
  durationMs: Date.now() - startedAt
8708
9110
  }, active.conversationRefs).catch(() => void 0);
@@ -8842,7 +9244,7 @@ async function waitForHostedPoll(milliseconds, signal) {
8842
9244
  }
8843
9245
  function isValidHostedSecurityPlan(value2, env) {
8844
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;
8845
- 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;
8846
9248
  return validRoute(value2.routes?.discovery, "security.discovery") && validRoute(value2.routes?.validation, "security.validation");
8847
9249
  }
8848
9250
  function hostedSecurityCredential(value2) {
@@ -9191,20 +9593,20 @@ async function runCodeRuntime(input) {
9191
9593
  }
9192
9594
  }
9193
9595
  function parseConnection(value2, appId, appEnv) {
9194
- const root = record5(value2);
9195
- const host = record5(root?.host);
9196
- const offer = record5(root?.offer);
9197
- 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);
9198
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)) {
9199
9601
  throw new Error("connect Code host returned an invalid response");
9200
9602
  }
9201
9603
  return root;
9202
9604
  }
9203
9605
  function apiFailure(action2, status, value2) {
9204
- const message2 = record5(record5(value2)?.error)?.message;
9606
+ const message2 = record6(record6(value2)?.error)?.message;
9205
9607
  return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
9206
9608
  }
9207
- function record5(value2) {
9609
+ function record6(value2) {
9208
9610
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
9209
9611
  }
9210
9612
 
@@ -9423,9 +9825,9 @@ async function credentialCommand(parsed, deps = {}) {
9423
9825
  }, doFetch, out, { optionalProjectCapabilities: ["app.manage"] });
9424
9826
  const base = `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials`;
9425
9827
  if (action2 === "revoke") {
9426
- const id = parsed.positionals[2];
9427
- if (!id) throw new Error("credentials revoke requires the exact receipt id from credentials list");
9428
- 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)}`, {
9429
9831
  method: "DELETE",
9430
9832
  headers: { authorization: `Bearer ${token}` }
9431
9833
  });
@@ -9533,6 +9935,12 @@ Usage:
9533
9935
  odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
9534
9936
  odla-ai context remove <name> --yes [--json]
9535
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]
9536
9944
  odla-ai platform status [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
9537
9945
  odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
9538
9946
  odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
@@ -9670,6 +10078,9 @@ Commands:
9670
10078
  canary, collector ingest/scheduler trust, Cloudflare-owned
9671
10079
  runtime metrics, and a machine verdict.
9672
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.
9673
10084
  platform Read canonical fleet health, releases, provider load/freshness,
9674
10085
  explicit unknowns, and next actions through a read-only grant.
9675
10086
  provision Register services, compose integrations, persist credentials, optionally push secrets.
@@ -9681,7 +10092,9 @@ Commands:
9681
10092
  copilot, gemini, or agents (repeatable or comma-separated).
9682
10093
  secrets Push configured db/o11y secrets into the Worker via wrangler
9683
10094
  stdin; set stores a tenant-vault secret and set-clerk-key the
9684
- reserved Clerk secret key, write-only from stdin or an env var.
10095
+ reserved Clerk secret key, write-only from stdin or an env var;
10096
+ status compares the secrets the config declares against the
10097
+ names the environment's vault holds (--json for a report).
9685
10098
  version Print the CLI version.
9686
10099
 
9687
10100
  Safety:
@@ -9871,7 +10284,7 @@ async function discussList(ctx, parsed) {
9871
10284
  }
9872
10285
  });
9873
10286
  }
9874
- async function discussRead(ctx, id, parsed) {
10287
+ async function discussRead(ctx, id2, parsed) {
9875
10288
  const requestedLimit = stringOpt(parsed.options.limit);
9876
10289
  const requestedOffset = stringOpt(parsed.options.offset);
9877
10290
  if (requestedLimit !== void 0 || requestedOffset !== void 0) {
@@ -9879,7 +10292,7 @@ async function discussRead(ctx, id, parsed) {
9879
10292
  limit: requestedLimit ?? "200",
9880
10293
  offset: requestedOffset ?? "0"
9881
10294
  });
9882
- const page2 = await request(ctx, "GET", `/topics/${encodeURIComponent(id)}?${query}`);
10295
+ const page2 = await request(ctx, "GET", `/topics/${encodeURIComponent(id2)}?${query}`);
9883
10296
  emit(
9884
10297
  ctx,
9885
10298
  page2,
@@ -9899,7 +10312,7 @@ async function discussRead(ctx, id, parsed) {
9899
10312
  const page2 = await request(
9900
10313
  ctx,
9901
10314
  "GET",
9902
- `/topics/${encodeURIComponent(id)}?limit=200&offset=${offset}`
10315
+ `/topics/${encodeURIComponent(id2)}?limit=200&offset=${offset}`
9903
10316
  );
9904
10317
  topic = page2.topic;
9905
10318
  for (const post of page2.posts) posts.set(post.id, post);
@@ -9941,20 +10354,20 @@ async function discussPost(ctx, parsed) {
9941
10354
  });
9942
10355
  emit(ctx, created, () => ctx.out.log(`opened topic ${created.id}`));
9943
10356
  }
9944
- async function discussReply(ctx, id, parsed) {
10357
+ async function discussReply(ctx, id2, parsed) {
9945
10358
  const created = await request(
9946
10359
  ctx,
9947
10360
  "POST",
9948
- `/topics/${encodeURIComponent(id)}/replies`,
10361
+ `/topics/${encodeURIComponent(id2)}/replies`,
9949
10362
  { ...content(parsed), mutationId: writeMutationId(parsed) }
9950
10363
  );
9951
10364
  emit(ctx, created, () => ctx.out.log(`replied ${created.id}`));
9952
10365
  }
9953
- async function discussResolve(ctx, id, resolved, parsed) {
10366
+ async function discussResolve(ctx, id2, resolved, parsed) {
9954
10367
  const result = await request(
9955
10368
  ctx,
9956
10369
  "PATCH",
9957
- `/topics/${encodeURIComponent(id)}`,
10370
+ `/topics/${encodeURIComponent(id2)}`,
9958
10371
  { resolved, mutationId: writeMutationId(parsed) }
9959
10372
  );
9960
10373
  emit(ctx, result, () => ctx.out.log(`${resolved ? "resolved" : "reopened"} ${result.id}`));
@@ -10228,9 +10641,9 @@ var ALLOWED = [
10228
10641
  "context",
10229
10642
  "open"
10230
10643
  ];
10231
- function requireId(id, action2) {
10232
- if (!id) throw new Error(`"discuss ${action2}" needs a topic id`);
10233
- return id;
10644
+ function requireId(id2, action2) {
10645
+ if (!id2) throw new Error(`"discuss ${action2}" needs a topic id`);
10646
+ return id2;
10234
10647
  }
10235
10648
  async function buildContext(parsed, deps) {
10236
10649
  const context = await resolveOperatorContext(parsed, {
@@ -10265,7 +10678,7 @@ async function buildContext(parsed, deps) {
10265
10678
  async function discussCommand(parsed, deps = {}) {
10266
10679
  assertArgs(parsed, ALLOWED, 3);
10267
10680
  const action2 = parsed.positionals[1];
10268
- const id = parsed.positionals[2];
10681
+ const id2 = parsed.positionals[2];
10269
10682
  if (!action2) throw new Error('"discuss" needs an action. Run "odla-ai help".');
10270
10683
  const ctx = await buildContext(parsed, deps);
10271
10684
  switch (action2) {
@@ -10275,17 +10688,17 @@ async function discussCommand(parsed, deps = {}) {
10275
10688
  case "topics":
10276
10689
  return discussList(ctx, parsed);
10277
10690
  case "read":
10278
- return discussRead(ctx, requireId(id, "read"), parsed);
10691
+ return discussRead(ctx, requireId(id2, "read"), parsed);
10279
10692
  case "post":
10280
10693
  return discussPost(ctx, parsed);
10281
10694
  case "reply":
10282
- return discussReply(ctx, requireId(id, "reply"), parsed);
10695
+ return discussReply(ctx, requireId(id2, "reply"), parsed);
10283
10696
  case "resolve":
10284
- return discussResolve(ctx, requireId(id, "resolve"), parsed.options.reopen !== true, parsed);
10697
+ return discussResolve(ctx, requireId(id2, "resolve"), parsed.options.reopen !== true, parsed);
10285
10698
  case "who":
10286
10699
  return discussWho(ctx, parsed);
10287
10700
  case "watch": {
10288
- const result = await discussWatch(ctx, id, parsed);
10701
+ const result = await discussWatch(ctx, id2, parsed);
10289
10702
  if (!result.found) throw new WatchTimeoutError(result.cursor);
10290
10703
  return;
10291
10704
  }
@@ -10356,8 +10769,8 @@ function collectFields(parsed, allowClear) {
10356
10769
  if (allowClear) out[spec.key] = null;
10357
10770
  continue;
10358
10771
  }
10359
- const text2 = stringOpt(value2);
10360
- out[spec.key] = spec.num ? Number(text2) : text2;
10772
+ const text3 = stringOpt(value2);
10773
+ out[spec.key] = spec.num ? Number(text3) : text3;
10361
10774
  }
10362
10775
  return out;
10363
10776
  }
@@ -10370,17 +10783,17 @@ function collectEntityFields(entity, parsed, allowClear) {
10370
10783
  if (entity === "task" && fields.column === "ready") fields.column = "todo";
10371
10784
  return fields;
10372
10785
  }
10373
- function statusCol(entity, record9) {
10374
- if (entity === "bug") return `${record9.status ?? ""}/${record9.severity ?? ""}`;
10786
+ function statusCol(entity, record11) {
10787
+ if (entity === "bug") return `${record11.status ?? ""}/${record11.severity ?? ""}`;
10375
10788
  if (entity === "task") {
10376
- const state2 = record9.column === "todo" ? "ready" : String(record9.column ?? "");
10377
- 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;
10378
10791
  }
10379
- return String(record9.status ?? "");
10792
+ return String(record11.status ?? "");
10380
10793
  }
10381
- function referenceMarkup(entity, record9) {
10382
- const label = (record9.title?.trim() || `${entity} ${record9.id}`).replaceAll("]", ")");
10383
- 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})`;
10384
10797
  }
10385
10798
  var STUDIO_SECTION = {
10386
10799
  goal: "goals",
@@ -10388,19 +10801,19 @@ var STUDIO_SECTION = {
10388
10801
  decision: "decisions",
10389
10802
  bug: "bugs"
10390
10803
  };
10391
- function studioRecordUrl(ctx, entity, id) {
10804
+ function studioRecordUrl(ctx, entity, id2) {
10392
10805
  return new URL(
10393
- `/studio/pm/${STUDIO_SECTION[entity]}/${encodeURIComponent(id)}`,
10806
+ `/studio/pm/${STUDIO_SECTION[entity]}/${encodeURIComponent(id2)}`,
10394
10807
  ctx.platformUrl
10395
10808
  ).href;
10396
10809
  }
10397
- function studioRecordLink(ctx, entity, record9) {
10398
- const label = (record9.title?.trim() || `${entity} ${record9.id}`).replaceAll("]", ")");
10399
- 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)})`;
10400
10813
  }
10401
- function printRecord(ctx, entity, record9) {
10814
+ function printRecord(ctx, entity, record11) {
10402
10815
  ctx.out.log(
10403
- `${record9.id} [${statusCol(entity, record9)}] ${record9.appId} ${studioRecordLink(ctx, entity, record9)}`
10816
+ `${record11.id} [${statusCol(entity, record11)}] ${record11.appId} ${studioRecordLink(ctx, entity, record11)}`
10404
10817
  );
10405
10818
  }
10406
10819
  function emit2(ctx, value2, human) {
@@ -10454,52 +10867,52 @@ async function pmAdd(ctx, entity, parsed) {
10454
10867
  input,
10455
10868
  mutationId: writeMutationId2(parsed)
10456
10869
  });
10457
- const record9 = { id: res.id, appId, title: String(input.title) };
10458
- 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)}`));
10459
10872
  }
10460
- async function pmGet(ctx, entity, id) {
10461
- const { record: record9 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
10462
- 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));
10463
10876
  }
10464
- async function pmReference(ctx, entity, id) {
10465
- const { record: record9 } = await pmRequest(
10877
+ async function pmReference(ctx, entity, id2) {
10878
+ const { record: record11 } = await pmRequest(
10466
10879
  ctx,
10467
10880
  "GET",
10468
- `/${entity}/${encodeURIComponent(id)}`
10881
+ `/${entity}/${encodeURIComponent(id2)}`
10469
10882
  );
10470
- const markup = referenceMarkup(entity, record9);
10471
- 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 }, () => {
10472
10885
  ctx.out.log(markup);
10473
10886
  });
10474
10887
  }
10475
- async function pmSet(ctx, entity, id, parsed) {
10888
+ async function pmSet(ctx, entity, id2, parsed) {
10476
10889
  const patch2 = collectEntityFields(entity, parsed, true);
10477
10890
  if (Object.keys(patch2).length === 0)
10478
10891
  throw new Error("pm set needs at least one field flag (e.g. --status doing, --assignee me, --no-assignee)");
10479
- const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, {
10892
+ const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id2)}`, {
10480
10893
  patch: patch2,
10481
10894
  mutationId: writeMutationId2(parsed)
10482
10895
  });
10483
10896
  emit2(ctx, res, () => {
10484
- if (!res.record) return ctx.out.log(`updated ${entity} ${id}`);
10897
+ if (!res.record) return ctx.out.log(`updated ${entity} ${id2}`);
10485
10898
  ctx.out.log(`${entity}: ${studioRecordLink(ctx, entity, res.record)} \u2192 ${statusCol(entity, res.record)}`);
10486
10899
  });
10487
10900
  }
10488
- async function pmDone(ctx, entity, id, parsed) {
10901
+ async function pmDone(ctx, entity, id2, parsed) {
10489
10902
  const decisionId = stringOpt(parsed.options.decision);
10490
10903
  if (decisionId && entity !== "bug") throw new Error("--decision is only valid when completing a bug");
10491
10904
  const patch2 = { ...DONE[entity], ...decisionId ? { decisionId } : {} };
10492
- const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, {
10905
+ const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id2)}`, {
10493
10906
  patch: patch2,
10494
10907
  mutationId: writeMutationId2(parsed)
10495
10908
  });
10496
10909
  emit2(ctx, res, () => {
10497
- const label = res.record ? studioRecordLink(ctx, entity, res.record) : id;
10910
+ const label = res.record ? studioRecordLink(ctx, entity, res.record) : id2;
10498
10911
  const state2 = res.record ? statusCol(entity, res.record) : "done";
10499
10912
  ctx.out.log(`${entity}: ${label} \u2192 ${state2}`);
10500
10913
  });
10501
10914
  }
10502
- async function pmTaskLifecycle(ctx, id, action2, parsed) {
10915
+ async function pmTaskLifecycle(ctx, id2, action2, parsed) {
10503
10916
  const rawRevision = stringOpt(parsed.options["expected-revision"]);
10504
10917
  const expectedRevision = Number(rawRevision);
10505
10918
  if (!rawRevision || !Number.isSafeInteger(expectedRevision) || expectedRevision < 1) {
@@ -10509,7 +10922,7 @@ async function pmTaskLifecycle(ctx, id, action2, parsed) {
10509
10922
  const res = action2 === "ready" ? await pmRequest(
10510
10923
  ctx,
10511
10924
  "PATCH",
10512
- `/task/${encodeURIComponent(id)}`,
10925
+ `/task/${encodeURIComponent(id2)}`,
10513
10926
  {
10514
10927
  patch: {
10515
10928
  ...collectEntityFields("task", parsed, true),
@@ -10521,12 +10934,12 @@ async function pmTaskLifecycle(ctx, id, action2, parsed) {
10521
10934
  ) : await pmRequest(
10522
10935
  ctx,
10523
10936
  "POST",
10524
- `/task/${encodeURIComponent(id)}/${action2}`,
10937
+ `/task/${encodeURIComponent(id2)}/${action2}`,
10525
10938
  { expectedRevision, mutationId }
10526
10939
  );
10527
10940
  emit2(ctx, res, () => {
10528
10941
  const state2 = res.record ? statusCol("task", res.record) : action2;
10529
- const label = res.record ? studioRecordLink(ctx, "task", res.record) : id;
10942
+ const label = res.record ? studioRecordLink(ctx, "task", res.record) : id2;
10530
10943
  ctx.out.log(`task: ${label} \u2192 ${state2}`);
10531
10944
  });
10532
10945
  }
@@ -10555,9 +10968,9 @@ async function pmNext(ctx, parsed) {
10555
10968
  const result = {
10556
10969
  appId,
10557
10970
  projectId,
10558
- openGoals: goals.filter((record9) => record9.status === "open"),
10559
- doing: tasks.filter((record9) => record9.column === "doing"),
10560
- 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")
10561
10974
  };
10562
10975
  emit2(ctx, result, () => {
10563
10976
  ctx.out.log(`${appId}: goal-aligned work intake (read only)`);
@@ -10568,10 +10981,10 @@ async function pmNext(ctx, parsed) {
10568
10981
  ]) {
10569
10982
  ctx.out.log(`${label}:`);
10570
10983
  if (!records.length) ctx.out.log("- (none)");
10571
- else for (const record9 of records) printRecord(
10984
+ else for (const record11 of records) printRecord(
10572
10985
  ctx,
10573
10986
  label === "open goals" ? "goal" : "task",
10574
- record9
10987
+ record11
10575
10988
  );
10576
10989
  }
10577
10990
  if (!result.openGoals.length) {
@@ -10595,9 +11008,9 @@ async function pmHandoff(ctx, parsed) {
10595
11008
  const handoff = {
10596
11009
  appId,
10597
11010
  projectId,
10598
- unmetGoals: goals.filter((record9) => record9.status !== "met"),
10599
- activeTasks: tasks.filter((record9) => record9.column !== "done"),
10600
- 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")
10601
11014
  };
10602
11015
  const result = {
10603
11016
  ...handoff,
@@ -10616,45 +11029,45 @@ async function pmHandoff(ctx, parsed) {
10616
11029
  ]) {
10617
11030
  ctx.out.log(`${label}:`);
10618
11031
  if (!records.length) ctx.out.log("- (none)");
10619
- else for (const record9 of records) printRecord(
11032
+ else for (const record11 of records) printRecord(
10620
11033
  ctx,
10621
11034
  label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
10622
- record9
11035
+ record11
10623
11036
  );
10624
11037
  }
10625
11038
  });
10626
11039
  }
10627
- async function pmRemove(ctx, entity, id) {
10628
- await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id)}`);
10629
- 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}`);
10630
11043
  }
10631
11044
 
10632
11045
  // src/pm-links.ts
10633
- async function pmLink(ctx, entity, id) {
10634
- const { record: record9 } = await pmRequest(
11046
+ async function pmLink(ctx, entity, id2) {
11047
+ const { record: record11 } = await pmRequest(
10635
11048
  ctx,
10636
11049
  "GET",
10637
- `/${entity}/${encodeURIComponent(id)}`
11050
+ `/${entity}/${encodeURIComponent(id2)}`
10638
11051
  );
10639
- const url = studioRecordUrl(ctx, entity, record9.id);
10640
- const markdown = studioRecordLink(ctx, entity, record9);
10641
- 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 }, () => {
10642
11055
  ctx.out.log(markdown);
10643
11056
  });
10644
11057
  }
10645
11058
 
10646
11059
  // src/pm-comments.ts
10647
- async function pmComment(ctx, entity, id, parsed) {
11060
+ async function pmComment(ctx, entity, id2, parsed) {
10648
11061
  const body = stringOpt(parsed.options.body);
10649
11062
  if (!body) throw new Error('pm comment needs --body "..."');
10650
- await pmRequest(ctx, "POST", `/${entity}/${encodeURIComponent(id)}/comments`, {
11063
+ await pmRequest(ctx, "POST", `/${entity}/${encodeURIComponent(id2)}/comments`, {
10651
11064
  body,
10652
11065
  mutationId: writeMutationId2(parsed)
10653
11066
  });
10654
- ctx.out.log(`commented on ${entity} ${id}`);
11067
+ ctx.out.log(`commented on ${entity} ${id2}`);
10655
11068
  }
10656
- async function pmComments(ctx, entity, id) {
10657
- 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`);
10658
11071
  emit2(ctx, messages, () => {
10659
11072
  if (messages.length === 0) ctx.out.log("(no comments)");
10660
11073
  else for (const message2 of messages) {
@@ -10672,12 +11085,12 @@ function fieldLine(change) {
10672
11085
  const before = change.before.length > 60 ? `${change.before.slice(0, 60)}\u2026` : change.before;
10673
11086
  return `${change.field} (was: ${before.replace(/\s+/g, " ")})`;
10674
11087
  }
10675
- async function pmHistory(ctx, entity, id, parsed) {
11088
+ async function pmHistory(ctx, entity, id2, parsed) {
10676
11089
  const limit = numberOpt(parsed.options.limit, "--limit");
10677
11090
  const page2 = await pmRequest(
10678
11091
  ctx,
10679
11092
  "GET",
10680
- `/${entity}/${encodeURIComponent(id)}/history${limit === void 0 ? "" : `?limit=${limit}`}`
11093
+ `/${entity}/${encodeURIComponent(id2)}/history${limit === void 0 ? "" : `?limit=${limit}`}`
10681
11094
  );
10682
11095
  emit2(ctx, page2, () => {
10683
11096
  if (!page2.entries.length) {
@@ -10765,16 +11178,16 @@ async function page(ctx, appId, cursor) {
10765
11178
  }
10766
11179
  return data;
10767
11180
  }
10768
- function recordState(record9) {
10769
- if (record9.column) return record9.column === "todo" ? "ready" : record9.column;
10770
- return String(record9.status ?? "");
11181
+ function recordState(record11) {
11182
+ if (record11.column) return record11.column === "todo" ? "ready" : record11.column;
11183
+ return String(record11.status ?? "");
10771
11184
  }
10772
11185
  function eventRecord(event) {
10773
11186
  return event.payload.payload;
10774
11187
  }
10775
11188
  function eventLabel(event) {
10776
- const record9 = eventRecord(event);
10777
- if (record9) return String(record9.title ?? event.payload.entityId);
11189
+ const record11 = eventRecord(event);
11190
+ if (record11) return String(record11.title ?? event.payload.entityId);
10778
11191
  const body = event.payload.message?.body?.replace(/\s+/g, " ").trim();
10779
11192
  return body || event.payload.entityId;
10780
11193
  }
@@ -10782,10 +11195,10 @@ function report2(ctx, parsed, result) {
10782
11195
  if (ctx.json) ctx.out.log(JSON.stringify(result, null, 2));
10783
11196
  else if (parsed.options.jsonl !== true && result.found) {
10784
11197
  for (const event of result.events ?? []) {
10785
- const record9 = eventRecord(event);
10786
- const state2 = record9 ? recordState(record9) : "comment";
11198
+ const record11 = eventRecord(event);
11199
+ const state2 = record11 ? recordState(record11) : "comment";
10787
11200
  ctx.out.log(
10788
- `${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)}`
10789
11202
  );
10790
11203
  }
10791
11204
  }
@@ -10859,8 +11272,8 @@ async function pmWatch(ctx, parsed) {
10859
11272
  }
10860
11273
  firstSuccess = false;
10861
11274
  const matching = current.events.filter((event) => {
10862
- const record9 = eventRecord(event);
10863
- const state2 = record9 ? recordState(record9).toLowerCase() : "";
11275
+ const record11 = eventRecord(event);
11276
+ const state2 = record11 ? recordState(record11).toLowerCase() : "";
10864
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);
10865
11278
  });
10866
11279
  for (const event of matching) {
@@ -10948,9 +11361,9 @@ async function pmProjectAdd(ctx, parsed) {
10948
11361
  });
10949
11362
  emit2(ctx, result, () => ctx.out.log(`created project: ${result.project.name} (${result.project.id})`));
10950
11363
  }
10951
- async function pmProjectUse(ctx, id) {
11364
+ async function pmProjectUse(ctx, id2) {
10952
11365
  if (!ctx.rootDir) throw new Error("pm project use needs a local project directory");
10953
- const { project } = await pmRequest(ctx, "GET", `/project/${encodeURIComponent(id)}`);
11366
+ const { project } = await pmRequest(ctx, "GET", `/project/${encodeURIComponent(id2)}`);
10954
11367
  if (project.status !== "active") throw new Error(`project ${project.name} is ${project.status}, not active`);
10955
11368
  writePmProjectContext(ctx.rootDir, { appId: project.appId, projectId: project.id });
10956
11369
  emit2(ctx, project, () => ctx.out.log(`using ${project.appId} / ${project.name} (${project.id}) in this worktree`));
@@ -11018,9 +11431,9 @@ function allowedOptions(entity, action2) {
11018
11431
  const entityOptions = action2 === "list" || action2 === "add" || action2 === "set" || action2 === "done" ? ENTITY_OPTIONS[entity][action2] : [];
11019
11432
  return [...COMMON_OPTIONS, ...ACTION_OPTIONS[action2], ...entityOptions];
11020
11433
  }
11021
- function requireId2(id, action2) {
11022
- if (!id) throw new Error(`"pm ... ${action2}" needs an item id`);
11023
- return id;
11434
+ function requireId2(id2, action2) {
11435
+ if (!id2) throw new Error(`"pm ... ${action2}" needs an item id`);
11436
+ return id2;
11024
11437
  }
11025
11438
  async function buildContext2(parsed, deps) {
11026
11439
  const context = await resolveOperatorContext(parsed, {
@@ -11111,34 +11524,34 @@ async function pmCommand(parsed, deps = {}) {
11111
11524
  throw new Error(`pm ${action2} is only valid for tasks`);
11112
11525
  }
11113
11526
  const ctx = await buildContext2(parsed, deps);
11114
- const id = parsed.positionals[3];
11527
+ const id2 = parsed.positionals[3];
11115
11528
  switch (action2) {
11116
11529
  case "list":
11117
11530
  return pmList(ctx, entity, parsed);
11118
11531
  case "add":
11119
11532
  return pmAdd(ctx, entity, parsed);
11120
11533
  case "get":
11121
- return pmGet(ctx, entity, requireId2(id, action2));
11534
+ return pmGet(ctx, entity, requireId2(id2, action2));
11122
11535
  case "set":
11123
- return pmSet(ctx, entity, requireId2(id, action2), parsed);
11536
+ return pmSet(ctx, entity, requireId2(id2, action2), parsed);
11124
11537
  case "done":
11125
- return pmDone(ctx, entity, requireId2(id, action2), parsed);
11538
+ return pmDone(ctx, entity, requireId2(id2, action2), parsed);
11126
11539
  case "comment":
11127
- return pmComment(ctx, entity, requireId2(id, action2), parsed);
11540
+ return pmComment(ctx, entity, requireId2(id2, action2), parsed);
11128
11541
  case "comments":
11129
- return pmComments(ctx, entity, requireId2(id, action2));
11542
+ return pmComments(ctx, entity, requireId2(id2, action2));
11130
11543
  case "history":
11131
- return pmHistory(ctx, entity, requireId2(id, action2), parsed);
11544
+ return pmHistory(ctx, entity, requireId2(id2, action2), parsed);
11132
11545
  case "rm":
11133
- return pmRemove(ctx, entity, requireId2(id, action2));
11546
+ return pmRemove(ctx, entity, requireId2(id2, action2));
11134
11547
  case "link":
11135
- return pmLink(ctx, entity, requireId2(id, action2));
11548
+ return pmLink(ctx, entity, requireId2(id2, action2));
11136
11549
  case "ref":
11137
- return pmReference(ctx, entity, requireId2(id, action2));
11550
+ return pmReference(ctx, entity, requireId2(id2, action2));
11138
11551
  case "ready":
11139
11552
  case "claim":
11140
11553
  case "release":
11141
- return pmTaskLifecycle(ctx, requireId2(id, action2), action2, parsed);
11554
+ return pmTaskLifecycle(ctx, requireId2(id2, action2), action2, parsed);
11142
11555
  }
11143
11556
  }
11144
11557
 
@@ -11241,17 +11654,17 @@ async function platformStatus(parsed, deps) {
11241
11654
  }
11242
11655
  }
11243
11656
  function isPlatformStatus(value2) {
11244
- if (!record6(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
11245
- if (!record6(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
11246
- 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;
11247
11660
  return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
11248
11661
  }
11249
11662
  function apiMessage(value2) {
11250
- if (!record6(value2)) return "request failed";
11251
- const error = record6(value2.error) ? value2.error : value2;
11663
+ if (!record7(value2)) return "request failed";
11664
+ const error = record7(value2.error) ? value2.error : value2;
11252
11665
  return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
11253
11666
  }
11254
- function record6(value2) {
11667
+ function record7(value2) {
11255
11668
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
11256
11669
  }
11257
11670
 
@@ -11292,7 +11705,7 @@ function statusVerdict(reads) {
11292
11705
  severity: "degraded"
11293
11706
  });
11294
11707
  }
11295
- 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;
11296
11709
  if (performance?.status === "unavailable") {
11297
11710
  reasons.push({
11298
11711
  source: "liveSync",
@@ -11373,7 +11786,7 @@ function statusVerdict(reads) {
11373
11786
  reasons
11374
11787
  };
11375
11788
  }
11376
- function record7(value2) {
11789
+ function record8(value2) {
11377
11790
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
11378
11791
  }
11379
11792
  function numeric2(value2) {
@@ -11401,7 +11814,7 @@ function printO11yStatus(status, out) {
11401
11814
  out.log(
11402
11815
  `o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
11403
11816
  );
11404
- 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) : [];
11405
11818
  const requests = routes.reduce(
11406
11819
  (total, row) => total + numeric3(row.requests),
11407
11820
  0
@@ -11413,39 +11826,39 @@ function printO11yStatus(status, out) {
11413
11826
  out.log(
11414
11827
  `application ${status.application.httpStatus} ${requests} requests ${errors} errors`
11415
11828
  );
11416
- 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) : [];
11417
11830
  out.log(
11418
11831
  `application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
11419
11832
  (row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
11420
11833
  ).join(", ") : "none observed"}`
11421
11834
  );
11422
11835
  out.log(liveSyncLine(status.liveSync));
11423
- const canaryDurations = record8(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
11836
+ const canaryDurations = record9(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
11424
11837
  out.log(
11425
11838
  `canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
11426
11839
  );
11427
- const collectorIngest = record8(status.collector.body.ingest) ? status.collector.body.ingest : {};
11428
- 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 : {};
11429
11842
  out.log(
11430
11843
  `collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
11431
11844
  );
11432
- const providerMetrics = record8(status.provider.body.metrics) ? status.provider.body.metrics : {};
11433
- const providerCapacity = record8(status.provider.body.capacity) ? status.provider.body.capacity : {};
11434
- 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 : {};
11435
11848
  out.log(
11436
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`
11437
11850
  );
11438
11851
  for (const line of providerCapacityLines(status.providerCapacity)) {
11439
11852
  out.log(line);
11440
11853
  }
11441
- const coverage = record8(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
11442
- const coverageCounts = record8(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
11443
- 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 : {};
11444
11857
  out.log(
11445
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`
11446
11859
  );
11447
11860
  const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
11448
- const providerFreshness = record8(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
11861
+ const providerFreshness = record9(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
11449
11862
  out.log(
11450
11863
  `cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
11451
11864
  );
@@ -11454,17 +11867,17 @@ function printO11yStatus(status, out) {
11454
11867
  );
11455
11868
  }
11456
11869
  function providerCapacityLines(read3) {
11457
- const resources = record8(read3.body.resources) ? read3.body.resources : {};
11458
- const durableObjects = record8(resources.durableObjects) ? resources.durableObjects : {};
11459
- const periodic = record8(durableObjects.periodic) ? durableObjects.periodic : {};
11460
- const storage = record8(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
11461
- const d1 = record8(resources.d1) ? resources.d1 : {};
11462
- const d1Activity = record8(d1.activity) ? d1.activity : {};
11463
- const d1Storage = record8(d1.storage) ? d1.storage : {};
11464
- const d1Latency = record8(d1Activity.latency) ? d1Activity.latency : {};
11465
- const r2 = record8(resources.r2) ? resources.r2 : {};
11466
- const r2Operations = record8(r2.operations) ? r2.operations : {};
11467
- 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 : {};
11468
11881
  const status = String(
11469
11882
  read3.body.status ?? read3.body.error ?? "unavailable"
11470
11883
  );
@@ -11475,11 +11888,11 @@ function providerCapacityLines(read3) {
11475
11888
  ];
11476
11889
  }
11477
11890
  function liveSyncLine(read3) {
11478
- const performance = record8(read3.body.performance) ? read3.body.performance : {};
11479
- 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 : {};
11480
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`;
11481
11894
  }
11482
- function record8(value2) {
11895
+ function record9(value2) {
11483
11896
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
11484
11897
  }
11485
11898
  function numeric3(value2) {
@@ -11650,21 +12063,294 @@ function statusMinutes(value2) {
11650
12063
  }
11651
12064
  async function read2(url, headers, doFetch) {
11652
12065
  const response2 = await doFetch(url, { headers });
11653
- const text2 = await response2.text();
12066
+ const text3 = await response2.text();
11654
12067
  let body = {};
11655
- if (text2) {
12068
+ if (text3) {
11656
12069
  try {
11657
- const value2 = JSON.parse(text2);
12070
+ const value2 = JSON.parse(text3);
11658
12071
  body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
11659
12072
  } catch {
11660
- body = { message: text2.slice(0, 300) };
12073
+ body = { message: text3.slice(0, 300) };
11661
12074
  }
11662
12075
  }
11663
12076
  return { httpStatus: response2.status, body };
11664
12077
  }
11665
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
+
11666
12352
  // src/provision.ts
11667
- import { AppsError as AppsError2, createAppsClient as createAppsClient3, orderAppServices as orderAppServices3, tenantIdFor as tenantIdFor5 } from "@odla-ai/apps";
12353
+ import { AppsError as AppsError2, createAppsClient as createAppsClient3, orderAppServices as orderAppServices3, tenantIdFor as tenantIdFor6 } from "@odla-ai/apps";
11668
12354
  import { putSecret as putSecret2 } from "@odla-ai/ai";
11669
12355
  import process13 from "process";
11670
12356
 
@@ -11721,9 +12407,9 @@ async function responseText(res) {
11721
12407
  }
11722
12408
 
11723
12409
  // src/provision-credentials.ts
11724
- import { tenantIdFor as tenantIdFor4 } from "@odla-ai/apps";
12410
+ import { tenantIdFor as tenantIdFor5 } from "@odla-ai/apps";
11725
12411
  async function provisionEnvCredentials(opts) {
11726
- const tenantId = tenantIdFor4(opts.cfg.app.id, opts.env);
12412
+ const tenantId = tenantIdFor5(opts.cfg.app.id, opts.env);
11727
12413
  const prior = opts.credentials?.envs[opts.env];
11728
12414
  let credentials = opts.credentials;
11729
12415
  let dbKey = opts.cfg.services.includes("db") && !opts.rotateDb ? prior?.dbKey : void 0;
@@ -11821,8 +12507,8 @@ function runtimeUrl(cfg, suffix = "") {
11821
12507
  return `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials${suffix}`;
11822
12508
  }
11823
12509
  async function safeError(response2) {
11824
- const text2 = await response2.text();
11825
- return redactSecrets(text2.slice(0, 1e3));
12510
+ const text3 = await response2.text();
12511
+ return redactSecrets(text3.slice(0, 1e3));
11826
12512
  }
11827
12513
  async function finish(doFetch, cfg, token, sessionId, method) {
11828
12514
  return doFetch(runtimeUrl(cfg, `/${encodeURIComponent(sessionId)}`), {
@@ -12055,7 +12741,7 @@ async function provision(options) {
12055
12741
  }
12056
12742
  let devVarsCredentials = credentials;
12057
12743
  for (const env of cfg.envs) {
12058
- const tenantId = tenantIdFor5(cfg.app.id, env);
12744
+ const tenantId = tenantIdFor6(cfg.app.id, env);
12059
12745
  let dbKey;
12060
12746
  if (options.pushSecrets) {
12061
12747
  const delivered = await deliverRuntimeCredentials(cfg, {
@@ -12225,6 +12911,7 @@ var COMMAND_SURFACE = {
12225
12911
  doctor: {},
12226
12912
  help: {},
12227
12913
  init: {},
12914
+ monitor: { plan: {}, apply: {}, run: {}, status: {}, incidents: {}, report: {} },
12228
12915
  o11y: { status: {} },
12229
12916
  operations: { get: {}, wait: {} },
12230
12917
  platform: {
@@ -12257,7 +12944,7 @@ var COMMAND_SURFACE = {
12257
12944
  rm: {},
12258
12945
  lint: {}
12259
12946
  },
12260
- secrets: { push: {}, set: {}, "set-clerk-key": {} },
12947
+ secrets: { push: {}, status: {}, set: {}, "set-clerk-key": {} },
12261
12948
  security: {
12262
12949
  plan: {},
12263
12950
  sources: {},
@@ -12465,12 +13152,12 @@ async function runbookRemove(ctx, slug) {
12465
13152
  // src/runbook-import.ts
12466
13153
  import { readFileSync as readFileSync10, readdirSync as readdirSync2, statSync } from "fs";
12467
13154
  import { basename as basename2, join as join13 } from "path";
12468
- function parseRunbook(text2, slug) {
12469
- let rest = text2;
13155
+ function parseRunbook(text3, slug) {
13156
+ let rest = text3;
12470
13157
  const meta = {};
12471
- 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);
12472
13159
  if (fm) {
12473
- rest = text2.slice(fm[0].length);
13160
+ rest = text3.slice(fm[0].length);
12474
13161
  for (const line of fm[1].split(/\r?\n/)) {
12475
13162
  const pair = /^(\w+)\s*:\s*(.+)$/.exec(line.trim());
12476
13163
  if (!pair) continue;
@@ -13239,9 +13926,9 @@ function printHostedSecurityIntent(out, intent) {
13239
13926
  }
13240
13927
  function assertHostedSecurityPlanReady(plan) {
13241
13928
  const reasons = [];
13242
- for (const [label, route2] of Object.entries(plan.routes)) {
13243
- if (!route2.enabled) reasons.push(`${label} is disabled`);
13244
- 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`);
13245
13932
  }
13246
13933
  if (!plan.independent) reasons.push("discovery and validation are not independently routed");
13247
13934
  if (plan.ready && reasons.length === 0) return;
@@ -13295,20 +13982,20 @@ function enforceHostedReportGate(report4, parsed, out, emitSuccess) {
13295
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.`);
13296
13983
  }
13297
13984
  }
13298
- function printHostedSecurityPlanRoute(out, label, route2) {
13299
- const readiness = route2.enabled && route2.credentialReady ? "ready" : [
13300
- route2.enabled ? void 0 : "disabled",
13301
- 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"
13302
13989
  ].filter(Boolean).join(", ");
13303
- out.log(` ${label}: ${route2.provider}/${route2.model} \xB7 policy v${route2.policyVersion} \xB7 ${readiness}`);
13304
- 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`);
13305
13992
  }
13306
13993
  function printHostedCoverage(out, job) {
13307
13994
  const coverage = job.coverage;
13308
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}` : ""}`);
13309
13996
  }
13310
- function routeLabel(route2) {
13311
- 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}` : ""}`;
13312
13999
  }
13313
14000
  var HOSTED_SEVERITIES = ["informational", "low", "medium", "high", "critical"];
13314
14001
  function hostedSeverity(value2, flag) {
@@ -13402,11 +14089,11 @@ function selectEnv(requested, declared, configPath, rootDir) {
13402
14089
  }
13403
14090
  return env;
13404
14091
  }
13405
- async function injectedToken(options, request2) {
13406
- 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 }));
13407
14094
  if (typeof value2 !== "string" || value2.length < 8 || value2.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value2)) {
13408
14095
  throw new Error(
13409
- 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"
13410
14097
  );
13411
14098
  }
13412
14099
  return value2;
@@ -13719,11 +14406,11 @@ async function runLocalSecurityCommand(parsed, dependencies) {
13719
14406
  sourceDisclosureAck: parsed.options["ack-redacted-source"] === true ? "redacted" : void 0,
13720
14407
  fetch: doFetch,
13721
14408
  stdout: out,
13722
- getToken: async (request2) => {
13723
- if (request2.scope === "platform:security:self") {
14409
+ getToken: async (request3) => {
14410
+ if (request3.scope === "platform:security:self") {
13724
14411
  return getScopedPlatformToken({
13725
- platform: request2.platform,
13726
- scope: request2.scope,
14412
+ platform: request3.platform,
14413
+ scope: request3.scope,
13727
14414
  email: stringOpt(parsed.options.email),
13728
14415
  open,
13729
14416
  fetch: doFetch,
@@ -13732,7 +14419,7 @@ async function runLocalSecurityCommand(parsed, dependencies) {
13732
14419
  });
13733
14420
  }
13734
14421
  const cfg = await loadProjectConfig(configPath);
13735
- if (platformAudience(cfg.platformUrl) !== platformAudience(request2.platform)) {
14422
+ if (platformAudience(cfg.platformUrl) !== platformAudience(request3.platform)) {
13736
14423
  throw new Error("--platform cannot reuse a project developer token from another platform; update odla.config.mjs and authenticate there");
13737
14424
  }
13738
14425
  return getDeveloperToken(
@@ -13937,10 +14624,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
13937
14624
  }
13938
14625
  if (command === "bug") {
13939
14626
  const action2 = parsed.positionals[1] ?? "list";
13940
- const canonical = action2 === "report" || action2 === "create" ? "add" : action2;
14627
+ const canonical2 = action2 === "report" || action2 === "create" ? "add" : action2;
13941
14628
  await pmCommand({
13942
14629
  ...parsed,
13943
- positionals: ["pm", "bug", canonical, ...parsed.positionals.slice(2)]
14630
+ positionals: ["pm", "bug", canonical2, ...parsed.positionals.slice(2)]
13944
14631
  }, runtime);
13945
14632
  return;
13946
14633
  }
@@ -13952,6 +14639,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
13952
14639
  await o11yCommand(parsed, runtime);
13953
14640
  return;
13954
14641
  }
14642
+ if (command === "monitor") {
14643
+ await monitorCommand(parsed, runtime);
14644
+ return;
14645
+ }
13955
14646
  if (command === "platform") {
13956
14647
  await platformCommand(parsed, runtime);
13957
14648
  return;
@@ -14032,9 +14723,9 @@ export {
14032
14723
  getScopedPlatformToken,
14033
14724
  SYSTEM_AI_PURPOSES,
14034
14725
  adminAi,
14035
- GOOGLE_CALENDAR_EVENTS_SCOPE,
14036
14726
  calendarServiceConfig,
14037
14727
  calendarBookingPageUrl,
14728
+ GOOGLE_CALENDAR_EVENTS_SCOPE,
14038
14729
  calendarStatus,
14039
14730
  calendarCalendars,
14040
14731
  calendarConnect,
@@ -14066,6 +14757,8 @@ export {
14066
14757
  CODE_BUILD_RECIPES,
14067
14758
  codeConnect,
14068
14759
  runCodeRuntime,
14760
+ monitoringWireConfig,
14761
+ monitorCommand,
14069
14762
  provision,
14070
14763
  COMMAND_SURFACE,
14071
14764
  acceptedAfter,
@@ -14084,4 +14777,4 @@ export {
14084
14777
  isTerminalHostedSecurityStatus,
14085
14778
  runCli
14086
14779
  };
14087
- //# sourceMappingURL=chunk-L6YTOTWU.js.map
14780
+ //# sourceMappingURL=chunk-LGNNX6AP.js.map