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