@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
package/dist/index.cjs
CHANGED
|
@@ -72,6 +72,8 @@ __export(index_exports, {
|
|
|
72
72
|
isTerminalHostedSecurityStatus: () => isTerminalHostedSecurityStatus,
|
|
73
73
|
listGitHubSecuritySources: () => listGitHubSecuritySources,
|
|
74
74
|
listHostedSecurityJobs: () => listHostedSecurityJobs,
|
|
75
|
+
monitorCommand: () => monitorCommand,
|
|
76
|
+
monitoringWireConfig: () => monitoringWireConfig,
|
|
75
77
|
printCapabilities: () => printCapabilities,
|
|
76
78
|
provision: () => provision,
|
|
77
79
|
reconcileConfig: () => reconcileConfig,
|
|
@@ -342,10 +344,10 @@ function isManagedDevVar(line) {
|
|
|
342
344
|
const match = line.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/);
|
|
343
345
|
return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
|
|
344
346
|
}
|
|
345
|
-
function writePrivateText(path,
|
|
347
|
+
function writePrivateText(path, text3) {
|
|
346
348
|
(0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(path), { recursive: true });
|
|
347
349
|
const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
348
|
-
(0, import_node_fs2.writeFileSync)(temporary,
|
|
350
|
+
(0, import_node_fs2.writeFileSync)(temporary, text3, { mode: 384 });
|
|
349
351
|
(0, import_node_fs2.chmodSync)(temporary, 384);
|
|
350
352
|
(0, import_node_fs2.renameSync)(temporary, path);
|
|
351
353
|
}
|
|
@@ -460,7 +462,7 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
460
462
|
}
|
|
461
463
|
function cachedGrantCovers(cached, required) {
|
|
462
464
|
if (required.optionalProjectCapabilities.length === 0) return true;
|
|
463
|
-
return required.projectIds.every((
|
|
465
|
+
return required.projectIds.every((id2) => cached.projectIds?.includes(id2)) && required.optionalProjectCapabilities.every(
|
|
464
466
|
(capability) => cached.optionalProjectCapabilities?.includes(capability)
|
|
465
467
|
);
|
|
466
468
|
}
|
|
@@ -619,8 +621,8 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
619
621
|
// src/principal-presentation.ts
|
|
620
622
|
function unresolvedPrincipalLabel(credentialKind2, principalId) {
|
|
621
623
|
const kind = typeof credentialKind2 === "string" ? credentialKind2.trim() : "";
|
|
622
|
-
const
|
|
623
|
-
const audit = kind &&
|
|
624
|
+
const id2 = typeof principalId === "string" ? principalId.trim() : "";
|
|
625
|
+
const audit = kind && id2 ? `${kind}:${id2}` : kind || id2;
|
|
624
626
|
return `Unknown principal${audit ? ` [${audit}]` : ""}`;
|
|
625
627
|
}
|
|
626
628
|
|
|
@@ -632,38 +634,38 @@ function adminAiAuditQuery(filters) {
|
|
|
632
634
|
}
|
|
633
635
|
return `?limit=${filters.limit}`;
|
|
634
636
|
}
|
|
635
|
-
async function readAdminAiAudit(
|
|
636
|
-
const response2 = await
|
|
637
|
-
headers:
|
|
637
|
+
async function readAdminAiAudit(request3) {
|
|
638
|
+
const response2 = await request3.fetch(`${request3.platform}/registry/platform/ai-audit${request3.query}`, {
|
|
639
|
+
headers: request3.headers
|
|
638
640
|
});
|
|
639
641
|
const body = await responseBody(response2);
|
|
640
642
|
if (!response2.ok) throw new Error(apiError(response2.status, body));
|
|
641
|
-
if (
|
|
642
|
-
|
|
643
|
+
if (request3.json) {
|
|
644
|
+
request3.stdout.log(JSON.stringify(body, null, 2));
|
|
643
645
|
return;
|
|
644
646
|
}
|
|
645
647
|
const events = isRecord(body) && Array.isArray(body.events) ? body.events.filter(isRecord) : [];
|
|
646
|
-
|
|
648
|
+
request3.stdout.log("when change target before -> after actor");
|
|
647
649
|
for (const event of events) {
|
|
648
650
|
const before = isRecord(event.oldPolicy) ? event.oldPolicy : void 0;
|
|
649
651
|
const after = isRecord(event.newPolicy) ? event.newPolicy : void 0;
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
+
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";
|
|
653
|
+
request3.stdout.log([
|
|
652
654
|
timestamp(event.createdAt),
|
|
653
655
|
String(event.changeKind ?? ""),
|
|
654
656
|
String(event.purpose ?? event.provider ?? ""),
|
|
655
|
-
|
|
657
|
+
route3,
|
|
656
658
|
unresolvedPrincipalLabel(event.actorType, event.actorId)
|
|
657
659
|
].join(" "));
|
|
658
660
|
}
|
|
659
661
|
}
|
|
660
662
|
async function responseBody(response2) {
|
|
661
|
-
const
|
|
662
|
-
if (!
|
|
663
|
+
const text3 = await response2.text();
|
|
664
|
+
if (!text3) return {};
|
|
663
665
|
try {
|
|
664
|
-
return JSON.parse(
|
|
666
|
+
return JSON.parse(text3);
|
|
665
667
|
} catch {
|
|
666
|
-
return { message:
|
|
668
|
+
return { message: text3.slice(0, 300) };
|
|
667
669
|
}
|
|
668
670
|
}
|
|
669
671
|
function apiError(status, body) {
|
|
@@ -704,14 +706,14 @@ function adminAiUsageQuery(filters) {
|
|
|
704
706
|
const query = params.toString();
|
|
705
707
|
return query ? `?${query}` : "";
|
|
706
708
|
}
|
|
707
|
-
async function readAdminAiUsage(
|
|
708
|
-
const res = await
|
|
709
|
-
headers:
|
|
709
|
+
async function readAdminAiUsage(request3) {
|
|
710
|
+
const res = await request3.fetch(`${request3.platform}/registry/platform/ai-usage${request3.query}`, {
|
|
711
|
+
headers: request3.headers
|
|
710
712
|
});
|
|
711
713
|
const body = await responseBody2(res);
|
|
712
714
|
if (!res.ok) throw new Error(apiError2("read platform AI usage", res.status, body));
|
|
713
|
-
if (
|
|
714
|
-
else printUsage(body,
|
|
715
|
+
if (request3.json) request3.stdout.log(JSON.stringify(body, null, 2));
|
|
716
|
+
else printUsage(body, request3.stdout);
|
|
715
717
|
}
|
|
716
718
|
function usageLimit(value2) {
|
|
717
719
|
if (!Number.isSafeInteger(value2) || value2 < 1 || value2 > 500) {
|
|
@@ -765,12 +767,12 @@ function timestamp2(value2) {
|
|
|
765
767
|
return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
|
|
766
768
|
}
|
|
767
769
|
async function responseBody2(res) {
|
|
768
|
-
const
|
|
769
|
-
if (!
|
|
770
|
+
const text3 = await res.text();
|
|
771
|
+
if (!text3) return {};
|
|
770
772
|
try {
|
|
771
|
-
return JSON.parse(
|
|
773
|
+
return JSON.parse(text3);
|
|
772
774
|
} catch {
|
|
773
|
-
return { message:
|
|
775
|
+
return { message: text3.slice(0, 300) };
|
|
774
776
|
}
|
|
775
777
|
}
|
|
776
778
|
function apiError2(action2, status, body) {
|
|
@@ -946,12 +948,12 @@ function catalogModels(body) {
|
|
|
946
948
|
return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
|
|
947
949
|
}
|
|
948
950
|
async function responseBody3(res) {
|
|
949
|
-
const
|
|
950
|
-
if (!
|
|
951
|
+
const text3 = await res.text();
|
|
952
|
+
if (!text3) return {};
|
|
951
953
|
try {
|
|
952
|
-
return JSON.parse(
|
|
954
|
+
return JSON.parse(text3);
|
|
953
955
|
} catch {
|
|
954
|
-
return { message:
|
|
956
|
+
return { message: text3.slice(0, 300) };
|
|
955
957
|
}
|
|
956
958
|
}
|
|
957
959
|
function apiError3(action2, status, body) {
|
|
@@ -1083,7 +1085,7 @@ function calendarServiceConfig(cfg, env) {
|
|
|
1083
1085
|
if (!google) throw new Error("calendar.google is required when the calendar service is enabled");
|
|
1084
1086
|
const configured = google.availabilityCalendars?.[env] ?? google.calendars?.[env];
|
|
1085
1087
|
if (!configured?.length) throw new Error(`calendar.google.availabilityCalendars.${env} is required`);
|
|
1086
|
-
const availability = unique(configured.map((
|
|
1088
|
+
const availability = unique(configured.map((id2) => id2.trim()));
|
|
1087
1089
|
return {
|
|
1088
1090
|
provider: "google",
|
|
1089
1091
|
access: "book",
|
|
@@ -1132,7 +1134,7 @@ function validateCalendarConfig(cfg, envs, services, path) {
|
|
|
1132
1134
|
if (ids.length > 10) {
|
|
1133
1135
|
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must contain at most 10 calendar ids`);
|
|
1134
1136
|
}
|
|
1135
|
-
if (ids.some((
|
|
1137
|
+
if (ids.some((id2) => !safeText2(id2, 1024))) {
|
|
1136
1138
|
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} contains an invalid calendar id`);
|
|
1137
1139
|
}
|
|
1138
1140
|
}
|
|
@@ -1274,6 +1276,175 @@ function unique2(values) {
|
|
|
1274
1276
|
return [...new Set(values.filter(Boolean))];
|
|
1275
1277
|
}
|
|
1276
1278
|
|
|
1279
|
+
// src/monitoring-validation.ts
|
|
1280
|
+
var CADENCES = /* @__PURE__ */ new Set(["1m", "2m", "5m", "10m", "15m", "30m", "1h"]);
|
|
1281
|
+
var WINDOWS = /* @__PURE__ */ new Set(["7d", "28d", "30d"]);
|
|
1282
|
+
var SHORT = /* @__PURE__ */ new Set(["30m", "1h", "6h", "12h", "1d"]);
|
|
1283
|
+
var LONG = /* @__PURE__ */ new Set(["1d", "3d", "7d"]);
|
|
1284
|
+
var DAYS = /* @__PURE__ */ new Set(["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]);
|
|
1285
|
+
var O11Y_METRICS = /* @__PURE__ */ new Set(["error_rate", "latency_p95", "synthetic_success", "synthetic_publish_to_visible"]);
|
|
1286
|
+
var COMPARATORS = /* @__PURE__ */ new Set(["gt", "gte", "lt", "lte"]);
|
|
1287
|
+
function validateMonitoringConfig(cfg, envs, services, path) {
|
|
1288
|
+
if (!cfg.o11y) return;
|
|
1289
|
+
if (!record(cfg.o11y)) fail(path, "o11y must be an object");
|
|
1290
|
+
only(cfg.o11y, ["service", "endpoint", "version", "monitoring"], `${path}: o11y`);
|
|
1291
|
+
const monitoring = cfg.o11y.monitoring;
|
|
1292
|
+
if (!monitoring) return;
|
|
1293
|
+
if (!services.includes("o11y")) fail(path, 'o11y.monitoring requires "o11y" in services');
|
|
1294
|
+
if (!record(monitoring)) fail(path, "o11y.monitoring must be an object");
|
|
1295
|
+
only(monitoring, ["probes", "slos", "notifications"], `${path}: o11y.monitoring`);
|
|
1296
|
+
if (monitoring.probes !== void 0 && (!Array.isArray(monitoring.probes) || monitoring.probes.length > 50)) {
|
|
1297
|
+
fail(path, "o11y.monitoring.probes must contain at most 50 probes");
|
|
1298
|
+
}
|
|
1299
|
+
const probeIds = /* @__PURE__ */ new Set();
|
|
1300
|
+
(monitoring.probes ?? []).forEach((probe, index) => validateProbe(probe, index, envs, path, probeIds));
|
|
1301
|
+
if (!Array.isArray(monitoring.slos) || monitoring.slos.length < 1 || monitoring.slos.length > 50) {
|
|
1302
|
+
fail(path, "o11y.monitoring.slos must contain 1 through 50 SLOs");
|
|
1303
|
+
}
|
|
1304
|
+
const sloIds = /* @__PURE__ */ new Set();
|
|
1305
|
+
monitoring.slos.forEach((slo, index) => validateSlo(slo, index, path, probeIds, sloIds));
|
|
1306
|
+
if (monitoring.notifications !== void 0) validateNotifications(monitoring.notifications, envs, path);
|
|
1307
|
+
}
|
|
1308
|
+
function validateProbe(value2, index, envs, path, ids) {
|
|
1309
|
+
const label = `${path}: o11y.monitoring.probes[${index}]`;
|
|
1310
|
+
if (!record(value2)) fail(label, "must be an object");
|
|
1311
|
+
only(value2, ["id", "route", "envs", "every", "timeout", "ready", "expect", "enabled"], label);
|
|
1312
|
+
if (!id(value2.id)) fail(label, "id must be lowercase letters, numbers, and hyphens");
|
|
1313
|
+
if (ids.has(value2.id)) fail(label, `id duplicates ${value2.id}`);
|
|
1314
|
+
ids.add(value2.id);
|
|
1315
|
+
if (!route(value2.route)) fail(label, "route must be a relative absolute path without credentials or a fragment");
|
|
1316
|
+
if (!CADENCES.has(String(value2.every))) fail(label, `every must be one of ${[...CADENCES].join(", ")}`);
|
|
1317
|
+
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");
|
|
1318
|
+
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");
|
|
1319
|
+
if (value2.ready !== void 0) {
|
|
1320
|
+
if (!record(value2.ready) || !text(value2.ready.selector, 300)) fail(label, "ready.selector is required");
|
|
1321
|
+
only(value2.ready, ["selector"], `${label}.ready`);
|
|
1322
|
+
}
|
|
1323
|
+
if (!record(value2.expect)) fail(label, "expect must be an object");
|
|
1324
|
+
only(value2.expect, ["status", "titleIncludes", "textIncludes", "accessibility"], `${label}.expect`);
|
|
1325
|
+
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");
|
|
1326
|
+
if (value2.expect.titleIncludes !== void 0 && !text(value2.expect.titleIncludes, 300)) fail(label, "expect.titleIncludes is invalid");
|
|
1327
|
+
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");
|
|
1328
|
+
if (value2.expect.accessibility !== void 0) validateAccessibility(value2.expect.accessibility, label);
|
|
1329
|
+
}
|
|
1330
|
+
function validateAccessibility(value2, label) {
|
|
1331
|
+
if (!Array.isArray(value2) || value2.length > 10) fail(label, "expect.accessibility must contain at most 10 assertions");
|
|
1332
|
+
for (const item of value2) {
|
|
1333
|
+
if (!record(item) || !text(item.role, 80) || !text(item.name, 300)) fail(label, "expect.accessibility entries need role and name");
|
|
1334
|
+
only(item, ["role", "name"], `${label}.expect.accessibility`);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
function validateSlo(value2, index, path, probes, ids) {
|
|
1338
|
+
const label = `${path}: o11y.monitoring.slos[${index}]`;
|
|
1339
|
+
if (!record(value2)) fail(label, "must be an object");
|
|
1340
|
+
only(value2, ["id", "name", "indicator", "target", "window", "alerts", "enabled"], label);
|
|
1341
|
+
if (!id(value2.id) || ids.has(value2.id)) fail(label, "id must be unique lowercase letters, numbers, and hyphens");
|
|
1342
|
+
ids.add(value2.id);
|
|
1343
|
+
if (value2.name !== void 0 && !text(value2.name, 160)) fail(label, "name is invalid");
|
|
1344
|
+
validateIndicator(value2.indicator, label, probes);
|
|
1345
|
+
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");
|
|
1346
|
+
if (!WINDOWS.has(String(value2.window))) fail(label, `window must be one of ${[...WINDOWS].join(", ")}`);
|
|
1347
|
+
if (value2.alerts !== void 0) validateAlerts(value2.alerts, label);
|
|
1348
|
+
}
|
|
1349
|
+
function validateIndicator(value2, label, probes) {
|
|
1350
|
+
if (!record(value2)) fail(label, "indicator must be an object");
|
|
1351
|
+
if (value2.type === "probe-success") {
|
|
1352
|
+
only(value2, ["type", "probes"], `${label}.indicator`);
|
|
1353
|
+
if (!Array.isArray(value2.probes) || value2.probes.length < 1) fail(label, "probe-success must select at least one probe");
|
|
1354
|
+
if (value2.probes.some((probe) => typeof probe !== "string" || !probes.has(probe))) fail(label, "indicator references an unknown probe");
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
if (value2.type !== "o11y-metric") fail(label, "indicator.type must be probe-success or o11y-metric");
|
|
1358
|
+
only(value2, ["type", "metric", "comparator", "threshold", "every", "observationWindow", "route"], `${label}.indicator`);
|
|
1359
|
+
if (!O11Y_METRICS.has(String(value2.metric))) fail(label, "indicator.metric is unsupported");
|
|
1360
|
+
if (!COMPARATORS.has(String(value2.comparator))) fail(label, "indicator.comparator is unsupported");
|
|
1361
|
+
if (typeof value2.threshold !== "number" || !Number.isFinite(value2.threshold)) fail(label, "indicator.threshold must be finite");
|
|
1362
|
+
if (!CADENCES.has(String(value2.every)) || !CADENCES.has(String(value2.observationWindow))) fail(label, "indicator cadence and observationWindow must be supported durations");
|
|
1363
|
+
if (value2.route !== void 0 && !routePattern(value2.route)) fail(label, "indicator.route must be an exact route template or trailing-* prefix");
|
|
1364
|
+
if ((value2.metric === "synthetic_success" || value2.metric === "synthetic_publish_to_visible") && value2.route !== void 0) fail(label, "synthetic indicators cannot select a route");
|
|
1365
|
+
}
|
|
1366
|
+
function validateAlerts(value2, label) {
|
|
1367
|
+
if (!record(value2)) fail(label, "alerts must be an object");
|
|
1368
|
+
only(value2, ["spike", "trend"], `${label}.alerts`);
|
|
1369
|
+
if (value2.spike !== void 0) {
|
|
1370
|
+
if (!record(value2.spike)) fail(label, "alerts.spike must be an object");
|
|
1371
|
+
only(value2.spike, ["badChecks", "withinChecks", "recoverAfter"], `${label}.alerts.spike`);
|
|
1372
|
+
const bad = positive(value2.spike.badChecks, 2), within = positive(value2.spike.withinChecks, 3), recover = positive(value2.spike.recoverAfter, 2);
|
|
1373
|
+
if (bad > within || within > 20 || recover > 20) fail(label, "alerts.spike requires badChecks <= withinChecks <= 20 and recoverAfter <= 20");
|
|
1374
|
+
}
|
|
1375
|
+
if (value2.trend !== void 0) {
|
|
1376
|
+
if (!record(value2.trend)) fail(label, "alerts.trend must be an object");
|
|
1377
|
+
only(value2.trend, ["burnRate", "shortWindow", "longWindow", "minBadChecks"], `${label}.alerts.trend`);
|
|
1378
|
+
const burn = value2.trend.burnRate ?? 1;
|
|
1379
|
+
if (typeof burn !== "number" || !Number.isFinite(burn) || burn <= 0 || burn > 1e3) fail(label, "alerts.trend.burnRate must be greater than 0");
|
|
1380
|
+
if (value2.trend.shortWindow !== void 0 && !SHORT.has(String(value2.trend.shortWindow))) fail(label, "alerts.trend.shortWindow is unsupported");
|
|
1381
|
+
if (value2.trend.longWindow !== void 0 && !LONG.has(String(value2.trend.longWindow))) fail(label, "alerts.trend.longWindow is unsupported");
|
|
1382
|
+
if (positive(value2.trend.minBadChecks, 2) > 100) fail(label, "alerts.trend.minBadChecks must be at most 100");
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
function validateNotifications(value2, envs, path) {
|
|
1386
|
+
if (!record(value2)) fail(path, "o11y.monitoring.notifications must map environments to policies");
|
|
1387
|
+
for (const [env, policy] of Object.entries(value2)) {
|
|
1388
|
+
const label = `${path}: o11y.monitoring.notifications.${env}`;
|
|
1389
|
+
if (!envs.includes(env) && env !== "prod") fail(label, "is not a configured environment");
|
|
1390
|
+
if (!record(policy)) fail(label, "must be an object");
|
|
1391
|
+
only(policy, ["email", "timezone", "daily", "weekly"], label);
|
|
1392
|
+
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");
|
|
1393
|
+
if (!timezone(policy.timezone)) fail(label, "timezone must be an IANA timezone");
|
|
1394
|
+
if (policy.daily !== void 0 && policy.daily !== false && !clock(policy.daily)) fail(label, "daily must be HH:MM or false");
|
|
1395
|
+
if (policy.weekly !== void 0 && policy.weekly !== false) {
|
|
1396
|
+
if (!record(policy.weekly) || !DAYS.has(String(policy.weekly.day)) || !clock(policy.weekly.at)) fail(label, "weekly needs a weekday and HH:MM time");
|
|
1397
|
+
only(policy.weekly, ["day", "at"], `${label}.weekly`);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
function fail(label, message2) {
|
|
1402
|
+
throw new Error(`${label}: ${message2}`);
|
|
1403
|
+
}
|
|
1404
|
+
function record(value2) {
|
|
1405
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
1406
|
+
}
|
|
1407
|
+
function only(value2, keys, label) {
|
|
1408
|
+
const extra = Object.keys(value2).find((key) => !keys.includes(key));
|
|
1409
|
+
if (extra) fail(label, `${extra} is not supported`);
|
|
1410
|
+
}
|
|
1411
|
+
function id(value2) {
|
|
1412
|
+
return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
|
|
1413
|
+
}
|
|
1414
|
+
function text(value2, max) {
|
|
1415
|
+
return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
|
|
1416
|
+
}
|
|
1417
|
+
function positive(value2, fallback) {
|
|
1418
|
+
return value2 === void 0 ? fallback : Number.isSafeInteger(value2) && Number(value2) > 0 ? Number(value2) : Infinity;
|
|
1419
|
+
}
|
|
1420
|
+
function route(value2) {
|
|
1421
|
+
if (typeof value2 !== "string" || value2.length > 2048 || !value2.startsWith("/") || value2.startsWith("//")) return false;
|
|
1422
|
+
try {
|
|
1423
|
+
const url = new URL(value2, "https://probe.invalid");
|
|
1424
|
+
return url.origin === "https://probe.invalid" && !url.hash;
|
|
1425
|
+
} catch {
|
|
1426
|
+
return false;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
function routePattern(value2) {
|
|
1430
|
+
return typeof value2 === "string" && value2.length <= 160 && /^\/[A-Za-z0-9_./:-]+\*?$/.test(value2) && !value2.slice(0, -1).includes("*");
|
|
1431
|
+
}
|
|
1432
|
+
function emailAddress(value2) {
|
|
1433
|
+
return typeof value2 === "string" && value2.length <= 254 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value2);
|
|
1434
|
+
}
|
|
1435
|
+
function clock(value2) {
|
|
1436
|
+
return typeof value2 === "string" && /^(?:[01]\d|2[0-3]):[0-5]\d$/.test(value2);
|
|
1437
|
+
}
|
|
1438
|
+
function timezone(value2) {
|
|
1439
|
+
if (typeof value2 !== "string" || value2.length > 100) return false;
|
|
1440
|
+
try {
|
|
1441
|
+
new Intl.DateTimeFormat("en", { timeZone: value2 }).format(0);
|
|
1442
|
+
return true;
|
|
1443
|
+
} catch {
|
|
1444
|
+
return false;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1277
1448
|
// src/config.ts
|
|
1278
1449
|
var DEFAULT_PLATFORM = "https://odla.ai";
|
|
1279
1450
|
var DEFAULT_ENVS = ["dev"];
|
|
@@ -1294,6 +1465,7 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
|
|
|
1294
1465
|
const services = unique3(raw.services?.length ? raw.services : DEFAULT_SERVICES);
|
|
1295
1466
|
validateServices(services, resolved);
|
|
1296
1467
|
validateCalendarConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
|
|
1468
|
+
validateMonitoringConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
|
|
1297
1469
|
const local = {
|
|
1298
1470
|
tokenFile: (0, import_node_path4.resolve)(rootDir, raw.local?.tokenFile ?? ".odla/dev-token.json"),
|
|
1299
1471
|
credentialsFile: (0, import_node_path4.resolve)(rootDir, raw.local?.credentialsFile ?? ".odla/credentials.local.json"),
|
|
@@ -1695,7 +1867,7 @@ async function adminCommand(parsed, deps = {}) {
|
|
|
1695
1867
|
var import_node_process10 = __toESM(require("process"), 1);
|
|
1696
1868
|
|
|
1697
1869
|
// src/whoami-command.ts
|
|
1698
|
-
var
|
|
1870
|
+
var text2 = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
1699
1871
|
function principalKind(value2, machine) {
|
|
1700
1872
|
return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
|
|
1701
1873
|
}
|
|
@@ -1708,12 +1880,12 @@ function credentialKind(value2, machine, scopes) {
|
|
|
1708
1880
|
function managerOf(value2) {
|
|
1709
1881
|
if (!value2 || typeof value2 !== "object") return null;
|
|
1710
1882
|
const row = value2;
|
|
1711
|
-
const principalId =
|
|
1883
|
+
const principalId = text2(row.principalId);
|
|
1712
1884
|
if (!principalId) return null;
|
|
1713
1885
|
return {
|
|
1714
1886
|
principalId,
|
|
1715
|
-
displayName:
|
|
1716
|
-
handle:
|
|
1887
|
+
displayName: text2(row.displayName) ?? "Unnamed member",
|
|
1888
|
+
handle: text2(row.handle) ?? ""
|
|
1717
1889
|
};
|
|
1718
1890
|
}
|
|
1719
1891
|
function unnamedPrincipal(kind) {
|
|
@@ -1727,14 +1899,14 @@ async function fetchIdentity(platformUrl, token, doFetch) {
|
|
|
1727
1899
|
});
|
|
1728
1900
|
if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
|
|
1729
1901
|
const body = await res.json();
|
|
1730
|
-
const developerId =
|
|
1902
|
+
const developerId = text2(body.developerId) ?? "";
|
|
1731
1903
|
const machine = body.machine === true;
|
|
1732
1904
|
const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
|
|
1733
|
-
const principalId =
|
|
1734
|
-
const email =
|
|
1905
|
+
const principalId = text2(body.principalId) ?? developerId;
|
|
1906
|
+
const email = text2(body.email);
|
|
1735
1907
|
const kind = principalKind(body.principalKind, machine);
|
|
1736
|
-
const displayName =
|
|
1737
|
-
const handle =
|
|
1908
|
+
const displayName = text2(body.displayName) ?? email ?? unnamedPrincipal(kind);
|
|
1909
|
+
const handle = text2(body.handle) ?? "";
|
|
1738
1910
|
const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
|
|
1739
1911
|
return {
|
|
1740
1912
|
developerId,
|
|
@@ -1744,7 +1916,7 @@ async function fetchIdentity(platformUrl, token, doFetch) {
|
|
|
1744
1916
|
handle,
|
|
1745
1917
|
manager: managerOf(body.manager),
|
|
1746
1918
|
credential: {
|
|
1747
|
-
id:
|
|
1919
|
+
id: text2(credential2.id),
|
|
1748
1920
|
kind: credentialKind(credential2.kind, machine, scopes)
|
|
1749
1921
|
},
|
|
1750
1922
|
email,
|
|
@@ -1939,13 +2111,13 @@ async function agentCommand(parsed, deps = {}) {
|
|
|
1939
2111
|
const base = `${cfg.dbEndpoint}/app/${encodeURIComponent(tenant)}/admin/agent-jobs`;
|
|
1940
2112
|
const headers = { authorization: `Bearer ${credential2}` };
|
|
1941
2113
|
if (action2 === "retry") {
|
|
1942
|
-
const
|
|
1943
|
-
const res2 = await doFetch(`${base}/${encodeURIComponent(
|
|
2114
|
+
const id2 = parsed.positionals[2];
|
|
2115
|
+
const res2 = await doFetch(`${base}/${encodeURIComponent(id2)}/retry`, { method: "POST", headers });
|
|
1944
2116
|
const body2 = await readJson(res2);
|
|
1945
2117
|
if (!res2.ok) throw new Error(`agent retry failed (${res2.status}): ${errorMessage(body2)}`);
|
|
1946
2118
|
const result2 = { v: 1, appId: cfg.app.id, env, tenant, ...body2 };
|
|
1947
2119
|
if (parsed.options.json === true) out.log(JSON.stringify(result2, null, 2));
|
|
1948
|
-
else out.log(`${tenant}: requeued ${
|
|
2120
|
+
else out.log(`${tenant}: requeued ${id2}`);
|
|
1949
2121
|
return;
|
|
1950
2122
|
}
|
|
1951
2123
|
const state2 = stringOpt(parsed.options.state);
|
|
@@ -2025,8 +2197,8 @@ async function appImport(options) {
|
|
|
2025
2197
|
const out = options.stdout ?? console;
|
|
2026
2198
|
const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
|
|
2027
2199
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
2028
|
-
const
|
|
2029
|
-
const { format, sources } = (0, import_import.parseImport)(
|
|
2200
|
+
const text3 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs7.readFileSync)(0, "utf8")))() : (0, import_node_fs7.readFileSync)(options.file, "utf8");
|
|
2201
|
+
const { format, sources } = (0, import_import.parseImport)(text3, options.ns);
|
|
2030
2202
|
if (format === "namespace-map" && options.ns) {
|
|
2031
2203
|
throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
|
|
2032
2204
|
}
|
|
@@ -2237,7 +2409,7 @@ var EXTENSIONS = {
|
|
|
2237
2409
|
"text/html": "html",
|
|
2238
2410
|
"application/json": "json"
|
|
2239
2411
|
};
|
|
2240
|
-
var encode = (
|
|
2412
|
+
var encode = (text3) => new TextEncoder().encode(text3);
|
|
2241
2413
|
function assetFileName(uuid, mime) {
|
|
2242
2414
|
const ext = EXTENSIONS[mime.split(";")[0].trim().toLowerCase()] ?? "bin";
|
|
2243
2415
|
return `${uuid.replace(/[^a-zA-Z0-9._-]/g, "_")}.${ext}`;
|
|
@@ -2449,18 +2621,18 @@ async function readCalendarStatus(ctx) {
|
|
|
2449
2621
|
}
|
|
2450
2622
|
async function discoverGoogleCalendars(ctx) {
|
|
2451
2623
|
const raw = await calendarJson(ctx, "/calendars", {});
|
|
2452
|
-
const value2 =
|
|
2624
|
+
const value2 = record2(raw);
|
|
2453
2625
|
if (!value2 || !Array.isArray(value2.calendars)) throw new Error("calendar discovery returned an invalid response");
|
|
2454
2626
|
return value2.calendars.map((item, index) => {
|
|
2455
|
-
const calendar =
|
|
2456
|
-
const
|
|
2457
|
-
if (!calendar || !
|
|
2627
|
+
const calendar = record2(item);
|
|
2628
|
+
const id2 = textField(calendar?.id, 1024);
|
|
2629
|
+
if (!calendar || !id2) throw new Error(`calendar discovery returned an invalid calendar at index ${index}`);
|
|
2458
2630
|
const role = calendar.accessRole;
|
|
2459
2631
|
if (role !== void 0 && role !== "freeBusyReader" && role !== "reader" && role !== "writer" && role !== "owner") {
|
|
2460
2632
|
throw new Error(`calendar discovery returned an invalid access role at index ${index}`);
|
|
2461
2633
|
}
|
|
2462
2634
|
return {
|
|
2463
|
-
id,
|
|
2635
|
+
id: id2,
|
|
2464
2636
|
...optionalText("summary", calendar.summary, 500),
|
|
2465
2637
|
...typeof calendar.primary === "boolean" ? { primary: calendar.primary } : {},
|
|
2466
2638
|
...typeof calendar.selected === "boolean" ? { selected: calendar.selected } : {},
|
|
@@ -2488,10 +2660,10 @@ async function pollCalendarConnection(ctx, attemptId) {
|
|
|
2488
2660
|
}
|
|
2489
2661
|
function parseCalendarStatus(raw, env) {
|
|
2490
2662
|
const outer = wrapped(raw, "calendar");
|
|
2491
|
-
const value2 =
|
|
2492
|
-
const connection =
|
|
2493
|
-
const config =
|
|
2494
|
-
const googleConfig =
|
|
2663
|
+
const value2 = record2(outer.attempt) ?? record2(outer.status) ?? outer;
|
|
2664
|
+
const connection = record2(value2.connection) ?? {};
|
|
2665
|
+
const config = record2(value2.config) ?? record2(outer.config) ?? {};
|
|
2666
|
+
const googleConfig = record2(config.google) ?? config;
|
|
2495
2667
|
const stateValue = calendarState(value2.status ?? value2.state ?? connection.status ?? connection.state);
|
|
2496
2668
|
if (!stateValue) {
|
|
2497
2669
|
throw new Error("calendar status returned an invalid connection state");
|
|
@@ -2504,7 +2676,7 @@ function parseCalendarStatus(raw, env) {
|
|
|
2504
2676
|
if (accessValue !== void 0 && accessValue !== "book" && accessValue !== "read") {
|
|
2505
2677
|
throw new Error("calendar status returned unsupported access");
|
|
2506
2678
|
}
|
|
2507
|
-
const errorValue =
|
|
2679
|
+
const errorValue = record2(value2.error) ?? record2(connection.error);
|
|
2508
2680
|
const errorCode2 = textField(value2.lastErrorCode, 128);
|
|
2509
2681
|
const bookingPageValue = Object.hasOwn(value2, "bookingPageUrl") ? value2.bookingPageUrl : Object.hasOwn(config, "bookingPageUrl") ? config.bookingPageUrl : googleConfig.bookingPageUrl;
|
|
2510
2682
|
const connected = typeof (value2.connected ?? connection.connected) === "boolean" ? Boolean(value2.connected ?? connection.connected) : ["healthy", "degraded"].includes(stateValue);
|
|
@@ -2574,11 +2746,11 @@ async function calendarJson(ctx, suffix, init) {
|
|
|
2574
2746
|
return body;
|
|
2575
2747
|
}
|
|
2576
2748
|
function wrapped(raw, key) {
|
|
2577
|
-
const outer =
|
|
2749
|
+
const outer = record2(raw);
|
|
2578
2750
|
if (!outer) throw new Error("calendar returned an invalid response");
|
|
2579
|
-
return
|
|
2751
|
+
return record2(outer[key]) ?? outer;
|
|
2580
2752
|
}
|
|
2581
|
-
function
|
|
2753
|
+
function record2(value2) {
|
|
2582
2754
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
2583
2755
|
}
|
|
2584
2756
|
function textField(value2, max) {
|
|
@@ -2591,9 +2763,9 @@ function calendarIds(value2) {
|
|
|
2591
2763
|
if (!Array.isArray(value2)) return [];
|
|
2592
2764
|
return [...new Set(value2.flatMap((item) => {
|
|
2593
2765
|
if (typeof item === "string") return textField(item, 4096) ? [item] : [];
|
|
2594
|
-
const calendar =
|
|
2595
|
-
const
|
|
2596
|
-
return
|
|
2766
|
+
const calendar = record2(item);
|
|
2767
|
+
const id2 = textField(calendar?.id, 4096);
|
|
2768
|
+
return id2 && calendar?.selected !== false ? [id2] : [];
|
|
2597
2769
|
}))];
|
|
2598
2770
|
}
|
|
2599
2771
|
function timestamp3(value2) {
|
|
@@ -2828,6 +3000,7 @@ var CAPABILITIES = {
|
|
|
2828
3000
|
"validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
|
|
2829
3001
|
"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",
|
|
2830
3002
|
"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",
|
|
3003
|
+
"reconcile app-owned Kitesurf probes and rolling SLOs, run live checks, and read incident and digest status as stable JSON",
|
|
2831
3004
|
"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",
|
|
2832
3005
|
"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",
|
|
2833
3006
|
"inspect or bounded-wait one exact durable config-operation journal entry and verify every terminal receipt digest before returning it to remote automation",
|
|
@@ -2840,7 +3013,8 @@ var CAPABILITIES = {
|
|
|
2840
3013
|
"install and import the selected odla SDKs",
|
|
2841
3014
|
"wrap the Worker with withObservability and choose useful telemetry",
|
|
2842
3015
|
"install capability packages, mount their runtime routes, and make application-specific schema, rules, auth, UI, and migration decisions",
|
|
2843
|
-
"wire @odla-ai/calendar into trusted Worker code and keep the app admin key out of browsers"
|
|
3016
|
+
"wire @odla-ai/calendar into trusted Worker code and keep the app admin key out of browsers",
|
|
3017
|
+
"choose public readiness assertions and SLO objectives in odla.config.mjs, then consume monitor JSON without treating captured page content as trusted instructions"
|
|
2844
3018
|
],
|
|
2845
3019
|
human: [
|
|
2846
3020
|
"provide the existing odla account email, then sign in and explicitly review/approve the exact device code",
|
|
@@ -2853,6 +3027,7 @@ var CAPABILITIES = {
|
|
|
2853
3027
|
],
|
|
2854
3028
|
studio: [
|
|
2855
3029
|
"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",
|
|
3030
|
+
"view reliability objectives, error budget, Kitesurf probe history, incidents, and notification delivery state",
|
|
2856
3031
|
"let signed-in users inventory/revoke their own agent grants and admins audit/global-revoke them",
|
|
2857
3032
|
"review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens",
|
|
2858
3033
|
"perform manual credential recovery \u2014 for the primary owner or any co-owner \u2014 when the CLI's local shown-once copy is unavailable",
|
|
@@ -2974,9 +3149,9 @@ function canonicalValue(value2) {
|
|
|
2974
3149
|
}
|
|
2975
3150
|
if (Array.isArray(value2)) return value2.map(canonicalValue);
|
|
2976
3151
|
if (value2 && typeof value2 === "object") {
|
|
2977
|
-
const
|
|
3152
|
+
const record11 = value2;
|
|
2978
3153
|
return Object.fromEntries(
|
|
2979
|
-
Object.keys(
|
|
3154
|
+
Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, canonicalValue(record11[key])])
|
|
2980
3155
|
);
|
|
2981
3156
|
}
|
|
2982
3157
|
throw new TypeError("canonical JSON rejects unsupported values");
|
|
@@ -3001,8 +3176,8 @@ function readPlan(path) {
|
|
|
3001
3176
|
"invalid_plan"
|
|
3002
3177
|
);
|
|
3003
3178
|
}
|
|
3004
|
-
if (!
|
|
3005
|
-
if (!
|
|
3179
|
+
if (!record3(value2) || value2.schemaVersion !== "odla.config-plan/v2") invalidPlan("unsupported plan schema");
|
|
3180
|
+
if (!record3(value2.scope) || typeof value2.scope.appId !== "string" || typeof value2.scope.platformUrl !== "string") {
|
|
3006
3181
|
invalidPlan("plan scope is invalid");
|
|
3007
3182
|
}
|
|
3008
3183
|
if (!DIGEST.test(String(value2.desiredRevision)) || !DIGEST.test(String(value2.observedRevision))) {
|
|
@@ -3052,10 +3227,10 @@ function assertOperationId(value2) {
|
|
|
3052
3227
|
function assertActions(actions) {
|
|
3053
3228
|
const ids = /* @__PURE__ */ new Set();
|
|
3054
3229
|
for (const action2 of actions) {
|
|
3055
|
-
if (!
|
|
3056
|
-
const
|
|
3057
|
-
if (!ACTION_ID.test(
|
|
3058
|
-
ids.add(
|
|
3230
|
+
if (!record3(action2)) invalidPlan("every plan action must be an object");
|
|
3231
|
+
const id2 = String(action2.id ?? "");
|
|
3232
|
+
if (!ACTION_ID.test(id2) || ids.has(id2)) invalidPlan("plan action ids must be unique frozen ids");
|
|
3233
|
+
ids.add(id2);
|
|
3059
3234
|
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))) {
|
|
3060
3235
|
invalidPlan("plan action metadata is invalid");
|
|
3061
3236
|
}
|
|
@@ -3081,14 +3256,14 @@ function assertConditionalAction(action2) {
|
|
|
3081
3256
|
if (action2.path !== (action2.kind === "configure_service" ? `${base}.config` : base)) {
|
|
3082
3257
|
invalidPlan("service action path is invalid");
|
|
3083
3258
|
}
|
|
3084
|
-
if (action2.applySupport !== "provision" || !
|
|
3259
|
+
if (action2.applySupport !== "provision" || !record3(action2.after)) {
|
|
3085
3260
|
invalidPlan("service action payload is invalid");
|
|
3086
3261
|
}
|
|
3087
3262
|
if (action2.kind === "enable_service") {
|
|
3088
|
-
if (action2.after.enabled !== true || action2.before !== null && !
|
|
3263
|
+
if (action2.after.enabled !== true || action2.before !== null && !record3(action2.before)) {
|
|
3089
3264
|
invalidPlan("service enable action is invalid");
|
|
3090
3265
|
}
|
|
3091
|
-
} else if (!
|
|
3266
|
+
} else if (!record3(action2.before)) {
|
|
3092
3267
|
invalidPlan("service configure action is invalid");
|
|
3093
3268
|
}
|
|
3094
3269
|
}
|
|
@@ -3105,7 +3280,7 @@ function linkState(value2) {
|
|
|
3105
3280
|
function invalidPlan(message2) {
|
|
3106
3281
|
throw new ConfigOperationCommandError(message2, "invalid_plan");
|
|
3107
3282
|
}
|
|
3108
|
-
function
|
|
3283
|
+
function record3(value2) {
|
|
3109
3284
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
3110
3285
|
}
|
|
3111
3286
|
|
|
@@ -3144,9 +3319,9 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
3144
3319
|
}
|
|
3145
3320
|
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
|
|
3146
3321
|
}
|
|
3147
|
-
function errorCode(
|
|
3322
|
+
function errorCode(text3) {
|
|
3148
3323
|
try {
|
|
3149
|
-
const body = JSON.parse(
|
|
3324
|
+
const body = JSON.parse(text3);
|
|
3150
3325
|
return typeof body.error?.code === "string" ? body.error.code : null;
|
|
3151
3326
|
} catch {
|
|
3152
3327
|
return null;
|
|
@@ -3289,7 +3464,7 @@ async function configApply(options) {
|
|
|
3289
3464
|
throw new ConfigOperationCommandError("--idempotency-key is not a safe 1-120 character key", "invalid_plan");
|
|
3290
3465
|
}
|
|
3291
3466
|
const client = await operationClient(cfg, options, "apply");
|
|
3292
|
-
const
|
|
3467
|
+
const request3 = {
|
|
3293
3468
|
schemaVersion: "odla.config-operation-request/v1",
|
|
3294
3469
|
expectedRevision: plan.registryRevision,
|
|
3295
3470
|
desiredRevision: plan.desiredRevision,
|
|
@@ -3301,7 +3476,7 @@ async function configApply(options) {
|
|
|
3301
3476
|
};
|
|
3302
3477
|
let receipt;
|
|
3303
3478
|
try {
|
|
3304
|
-
receipt = await client.applyConfigOperation(cfg.app.id,
|
|
3479
|
+
receipt = await client.applyConfigOperation(cfg.app.id, request3);
|
|
3305
3480
|
} catch (error) {
|
|
3306
3481
|
const retained = retainedReceipt(error);
|
|
3307
3482
|
if (retained) {
|
|
@@ -3400,8 +3575,8 @@ function failureForReceipt(receipt) {
|
|
|
3400
3575
|
return new ConfigOperationCommandError(receipt.error?.message ?? `config operation ${receipt.state}`, "config_operation_failed");
|
|
3401
3576
|
}
|
|
3402
3577
|
function retainedReceipt(error) {
|
|
3403
|
-
if (!(error instanceof import_apps6.AppsError) || !
|
|
3404
|
-
return
|
|
3578
|
+
if (!(error instanceof import_apps6.AppsError) || !record4(error.details)) return null;
|
|
3579
|
+
return record4(error.details.operation) ? error.details.operation : null;
|
|
3405
3580
|
}
|
|
3406
3581
|
function normalizeRequestError(error) {
|
|
3407
3582
|
if (!(error instanceof import_apps6.AppsError)) return error instanceof Error ? error : new Error(String(error));
|
|
@@ -3414,7 +3589,7 @@ function normalizeRequestError(error) {
|
|
|
3414
3589
|
}
|
|
3415
3590
|
return new ConfigOperationCommandError(error.message, error.code || "config_operation_failed");
|
|
3416
3591
|
}
|
|
3417
|
-
function
|
|
3592
|
+
function record4(value2) {
|
|
3418
3593
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
3419
3594
|
}
|
|
3420
3595
|
|
|
@@ -3881,15 +4056,15 @@ function readWranglerConfig(path) {
|
|
|
3881
4056
|
return null;
|
|
3882
4057
|
}
|
|
3883
4058
|
}
|
|
3884
|
-
function stripJsonComments(
|
|
4059
|
+
function stripJsonComments(text3) {
|
|
3885
4060
|
let result = "";
|
|
3886
4061
|
let inString = false;
|
|
3887
|
-
for (let i = 0; i <
|
|
3888
|
-
const ch =
|
|
4062
|
+
for (let i = 0; i < text3.length; i++) {
|
|
4063
|
+
const ch = text3[i];
|
|
3889
4064
|
if (inString) {
|
|
3890
4065
|
result += ch;
|
|
3891
4066
|
if (ch === "\\") {
|
|
3892
|
-
result +=
|
|
4067
|
+
result += text3[i + 1] ?? "";
|
|
3893
4068
|
i++;
|
|
3894
4069
|
} else if (ch === '"') {
|
|
3895
4070
|
inString = false;
|
|
@@ -3901,14 +4076,14 @@ function stripJsonComments(text2) {
|
|
|
3901
4076
|
result += ch;
|
|
3902
4077
|
continue;
|
|
3903
4078
|
}
|
|
3904
|
-
if (ch === "/" &&
|
|
3905
|
-
while (i <
|
|
4079
|
+
if (ch === "/" && text3[i + 1] === "/") {
|
|
4080
|
+
while (i < text3.length && text3[i] !== "\n") i++;
|
|
3906
4081
|
result += "\n";
|
|
3907
4082
|
continue;
|
|
3908
4083
|
}
|
|
3909
|
-
if (ch === "/" &&
|
|
4084
|
+
if (ch === "/" && text3[i + 1] === "*") {
|
|
3910
4085
|
i += 2;
|
|
3911
|
-
while (i <
|
|
4086
|
+
while (i < text3.length && !(text3[i] === "*" && text3[i + 1] === "/")) i++;
|
|
3912
4087
|
i++;
|
|
3913
4088
|
continue;
|
|
3914
4089
|
}
|
|
@@ -3947,7 +4122,7 @@ async function wranglerRuntimeTarget(run, opts) {
|
|
|
3947
4122
|
throw new Error(`wrangler is not logged in \u2014 run "wrangler login" (a browser step for the human)`);
|
|
3948
4123
|
}
|
|
3949
4124
|
const discovered = [...new Set(`${whoami.stdout}
|
|
3950
|
-
${whoami.stderr}`.match(/\b[a-f0-9]{32}\b/gi)?.map((
|
|
4125
|
+
${whoami.stderr}`.match(/\b[a-f0-9]{32}\b/gi)?.map((id2) => id2.toLowerCase()) ?? [])];
|
|
3951
4126
|
const accountId = configuredAccount.toLowerCase() || (discovered.length === 1 ? discovered[0] : "");
|
|
3952
4127
|
if (!/^[a-f0-9]{32}$/.test(accountId) || configuredAccount && discovered.length > 0 && !discovered.includes(accountId)) {
|
|
3953
4128
|
throw new Error("Wrangler account is ambiguous or does not match account_id in the config");
|
|
@@ -4422,9 +4597,9 @@ function initProject(options) {
|
|
|
4422
4597
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
4423
4598
|
out.log("updated .gitignore for local odla credentials");
|
|
4424
4599
|
}
|
|
4425
|
-
function writeIfMissing(path,
|
|
4600
|
+
function writeIfMissing(path, text3) {
|
|
4426
4601
|
if ((0, import_node_fs12.existsSync)(path)) return;
|
|
4427
|
-
(0, import_node_fs12.writeFileSync)(path,
|
|
4602
|
+
(0, import_node_fs12.writeFileSync)(path, text3);
|
|
4428
4603
|
}
|
|
4429
4604
|
function configTemplate(input) {
|
|
4430
4605
|
const calendar = input.services.includes("calendar") ? ` calendar: {
|
|
@@ -4634,13 +4809,13 @@ async function secretsSetClerkKey(options) {
|
|
|
4634
4809
|
body: JSON.stringify({ value: value2 })
|
|
4635
4810
|
});
|
|
4636
4811
|
if (!res.ok) {
|
|
4637
|
-
const
|
|
4638
|
-
throw new Error(`store Clerk secret key failed (${res.status}): ${
|
|
4812
|
+
const text3 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
|
|
4813
|
+
throw new Error(`store Clerk secret key failed (${res.status}): ${text3 || "request failed"}`);
|
|
4639
4814
|
}
|
|
4640
4815
|
out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
|
|
4641
4816
|
}
|
|
4642
|
-
function scrubValue(
|
|
4643
|
-
return redactSecrets(
|
|
4817
|
+
function scrubValue(text3, value2) {
|
|
4818
|
+
return redactSecrets(text3).split(value2).join("[value redacted]");
|
|
4644
4819
|
}
|
|
4645
4820
|
async function resolveVaultWrite(options) {
|
|
4646
4821
|
const out = options.stdout ?? console;
|
|
@@ -4774,8 +4949,8 @@ alwaysApply: false
|
|
|
4774
4949
|
|
|
4775
4950
|
${PROJECT_INSTRUCTIONS}
|
|
4776
4951
|
`;
|
|
4777
|
-
function claudeAdapter(skill,
|
|
4778
|
-
const match =
|
|
4952
|
+
function claudeAdapter(skill, canonical2) {
|
|
4953
|
+
const match = canonical2.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
4779
4954
|
if (!match) throw new Error(`bundled skill ${skill} has no YAML frontmatter`);
|
|
4780
4955
|
const lines = match[1].split(/\r?\n/);
|
|
4781
4956
|
const frontmatter = [];
|
|
@@ -4845,8 +5020,8 @@ function installSkill(options = {}) {
|
|
|
4845
5020
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
4846
5021
|
if (harnesses.includes("claude")) {
|
|
4847
5022
|
for (const skill of skillNames(files)) {
|
|
4848
|
-
const
|
|
4849
|
-
plan((0, import_node_path13.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill,
|
|
5023
|
+
const canonical2 = (0, import_node_fs13.readFileSync)((0, import_node_path13.join)(sourceDir, skill, "SKILL.md"), "utf8");
|
|
5024
|
+
plan((0, import_node_path13.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical2));
|
|
4850
5025
|
}
|
|
4851
5026
|
rememberTarget("claude", claudeRoot);
|
|
4852
5027
|
}
|
|
@@ -5122,7 +5297,7 @@ function assertCalendarHealthy(status, expected) {
|
|
|
5122
5297
|
if (!status.writable) throw new Error('calendar grant does not cover booking writes; run "odla-ai calendar connect" to re-consent');
|
|
5123
5298
|
const hasEventsScope = status.grantedScopes.some((scope) => scope === GOOGLE_CALENDAR_EVENTS_SCOPE);
|
|
5124
5299
|
if (!hasEventsScope) throw new Error("calendar connection is missing calendar.events consent");
|
|
5125
|
-
const missing = expected.availabilityCalendars.filter((
|
|
5300
|
+
const missing = expected.availabilityCalendars.filter((id2) => !status.calendars.includes(id2));
|
|
5126
5301
|
if (missing.length) throw new Error(`calendar connection is missing configured calendars: ${missing.join(", ")}`);
|
|
5127
5302
|
}
|
|
5128
5303
|
async function getJson(doFetch, url, bearer) {
|
|
@@ -5522,8 +5697,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
5522
5697
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
5523
5698
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
5524
5699
|
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
5525
|
-
const entries = inventory.flatMap((
|
|
5526
|
-
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(
|
|
5700
|
+
const entries = inventory.flatMap((record11) => {
|
|
5701
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record11);
|
|
5527
5702
|
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
5528
5703
|
});
|
|
5529
5704
|
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
@@ -5771,8 +5946,8 @@ function normalize(value2) {
|
|
|
5771
5946
|
if (Array.isArray(value2)) return value2.map(normalize);
|
|
5772
5947
|
if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
|
|
5773
5948
|
if (typeof value2 === "object") {
|
|
5774
|
-
const
|
|
5775
|
-
return Object.fromEntries(Object.keys(
|
|
5949
|
+
const record11 = value2;
|
|
5950
|
+
return Object.fromEntries(Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, normalize(record11[key])]));
|
|
5776
5951
|
}
|
|
5777
5952
|
throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
|
|
5778
5953
|
}
|
|
@@ -5846,7 +6021,7 @@ function copyRef(ref) {
|
|
|
5846
6021
|
function normalizeReaders(readers) {
|
|
5847
6022
|
if (readers.kind === "public") return Object.freeze({ kind: "public" });
|
|
5848
6023
|
const principalIds = [...new Set(readers.principalIds)].sort();
|
|
5849
|
-
if (principalIds.some((
|
|
6024
|
+
if (principalIds.some((id2) => !id2)) throw new CamelError("reader_mismatch", "Reader principal IDs must be non-empty.");
|
|
5850
6025
|
return Object.freeze({ kind: "principals", principalIds: Object.freeze(principalIds) });
|
|
5851
6026
|
}
|
|
5852
6027
|
|
|
@@ -5856,7 +6031,7 @@ var SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
|
5856
6031
|
var ID = /^[A-Za-z0-9._:-]{1,160}$/;
|
|
5857
6032
|
async function digestCodeVerificationReceipt(fields) {
|
|
5858
6033
|
validate(fields);
|
|
5859
|
-
const
|
|
6034
|
+
const canonical2 = {
|
|
5860
6035
|
schemaVersion: fields.schemaVersion,
|
|
5861
6036
|
verificationId: fields.verificationId,
|
|
5862
6037
|
trustedBaseCommitSha: fields.trustedBaseCommitSha,
|
|
@@ -5883,7 +6058,7 @@ async function digestCodeVerificationReceipt(fields) {
|
|
|
5883
6058
|
changedTestsRequireReview: fields.changedTestsRequireReview,
|
|
5884
6059
|
outcome: fields.outcome
|
|
5885
6060
|
};
|
|
5886
|
-
return `sha256:${await sha256Hex(canonicalJson2(
|
|
6061
|
+
return `sha256:${await sha256Hex(canonicalJson2(canonical2))}`;
|
|
5887
6062
|
}
|
|
5888
6063
|
function validate(fields) {
|
|
5889
6064
|
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) {
|
|
@@ -6098,7 +6273,7 @@ async function createConversionRegistry(config) {
|
|
|
6098
6273
|
if (await conversionPolicyDigest(definition) !== policy.digest) throw new CamelError("state_conflict", "Conversion policy digest mismatch.");
|
|
6099
6274
|
if (policy.output.kind === "registered_id") {
|
|
6100
6275
|
const registry = config.registeredIds?.[policy.output.registryId];
|
|
6101
|
-
const validValues = registry && Object.entries(registry.values).every(([candidate,
|
|
6276
|
+
const validValues = registry && Object.entries(registry.values).every(([candidate, id2]) => candidate.length > 0 && typeof id2 === "string" && id2.length > 0);
|
|
6102
6277
|
if (!registry || !validValues || registry.digest !== policy.output.registryDigest || await registeredIdRegistryDigest(registry.values) !== registry.digest) {
|
|
6103
6278
|
throw new CamelError("state_conflict", "Registered-ID registry digest mismatch.");
|
|
6104
6279
|
}
|
|
@@ -6106,63 +6281,63 @@ async function createConversionRegistry(config) {
|
|
|
6106
6281
|
policies.set(policy.conversionId, Object.freeze(policy));
|
|
6107
6282
|
}
|
|
6108
6283
|
const outputCounts = /* @__PURE__ */ new Map();
|
|
6109
|
-
const get = (
|
|
6110
|
-
const policy = policies.get(
|
|
6284
|
+
const get = (id2, kind) => {
|
|
6285
|
+
const policy = policies.get(id2);
|
|
6111
6286
|
if (!policy || policy.output.kind !== kind) throw new CamelError("conversion_rejected", "Conversion policy is missing or has the wrong output kind.");
|
|
6112
6287
|
return policy;
|
|
6113
6288
|
};
|
|
6114
|
-
const checked = (source,
|
|
6115
|
-
const policy = get(
|
|
6289
|
+
const checked = (source, id2, kind) => {
|
|
6290
|
+
const policy = get(id2, kind);
|
|
6116
6291
|
if (utf8Length(source.value) > policy.maximumSourceBytes) throw new CamelError("limit_exceeded", "Unsafe conversion input exceeds its byte bound.");
|
|
6117
6292
|
return policy;
|
|
6118
6293
|
};
|
|
6119
|
-
const
|
|
6294
|
+
const emit4 = (source, policy, value2) => convert(source, policy, value2, outputCounts);
|
|
6120
6295
|
const operations = Object.freeze({
|
|
6121
|
-
boolean: async (value2,
|
|
6122
|
-
const policy = checked(value2,
|
|
6123
|
-
return
|
|
6296
|
+
boolean: async (value2, id2) => {
|
|
6297
|
+
const policy = checked(value2, id2, "boolean");
|
|
6298
|
+
return emit4(value2, policy, requireBoolean(value2.value));
|
|
6124
6299
|
},
|
|
6125
|
-
integer: async (value2,
|
|
6126
|
-
const policy = checked(value2,
|
|
6127
|
-
return
|
|
6300
|
+
integer: async (value2, id2) => {
|
|
6301
|
+
const policy = checked(value2, id2, "integer");
|
|
6302
|
+
return emit4(value2, policy, boundedInteger(value2.value, policy.output));
|
|
6128
6303
|
},
|
|
6129
|
-
finiteNumber: async (value2,
|
|
6130
|
-
const policy = checked(value2,
|
|
6131
|
-
return
|
|
6304
|
+
finiteNumber: async (value2, id2) => {
|
|
6305
|
+
const policy = checked(value2, id2, "finite_number");
|
|
6306
|
+
return emit4(value2, policy, boundedNumber(value2.value, policy.output));
|
|
6132
6307
|
},
|
|
6133
|
-
enum: async (value2,
|
|
6134
|
-
const policy = checked(value2,
|
|
6135
|
-
return
|
|
6308
|
+
enum: async (value2, id2) => {
|
|
6309
|
+
const policy = checked(value2, id2, "enum");
|
|
6310
|
+
return emit4(value2, policy, enumMember(value2.value, policy.output));
|
|
6136
6311
|
},
|
|
6137
|
-
date: async (value2,
|
|
6138
|
-
const policy = checked(value2,
|
|
6139
|
-
return
|
|
6312
|
+
date: async (value2, id2) => {
|
|
6313
|
+
const policy = checked(value2, id2, "date");
|
|
6314
|
+
return emit4(value2, policy, canonicalDate(value2.value, policy.output));
|
|
6140
6315
|
},
|
|
6141
|
-
registeredId: async (value2,
|
|
6142
|
-
const policy = checked(value2,
|
|
6316
|
+
registeredId: async (value2, id2) => {
|
|
6317
|
+
const policy = checked(value2, id2, "registered_id");
|
|
6143
6318
|
const registry = config.registeredIds?.[policy.output.registryId];
|
|
6144
6319
|
const output = typeof value2.value === "string" ? registry?.values[value2.value] : void 0;
|
|
6145
6320
|
if (!output) throw new CamelError("conversion_rejected", "Registered-ID conversion rejected the candidate.");
|
|
6146
|
-
return
|
|
6321
|
+
return emit4(value2, policy, output);
|
|
6147
6322
|
},
|
|
6148
|
-
digest: async (value2,
|
|
6149
|
-
const policy = checked(value2,
|
|
6323
|
+
digest: async (value2, id2) => {
|
|
6324
|
+
const policy = checked(value2, id2, "digest");
|
|
6150
6325
|
if (!(value2.value instanceof Uint8Array)) throw new CamelError("conversion_rejected", "Digest conversion requires bytes.");
|
|
6151
|
-
return
|
|
6326
|
+
return emit4(value2, policy, await sha256Hex(value2.value));
|
|
6152
6327
|
},
|
|
6153
|
-
measure: async (value2, metric,
|
|
6154
|
-
const policy = checked(value2,
|
|
6328
|
+
measure: async (value2, metric, id2) => {
|
|
6329
|
+
const policy = checked(value2, id2, "integer");
|
|
6155
6330
|
const measured = measure(value2.value, metric);
|
|
6156
|
-
return
|
|
6331
|
+
return emit4(value2, policy, boundedInteger(measured, policy.output));
|
|
6157
6332
|
},
|
|
6158
|
-
test: async (value2, predicateId,
|
|
6159
|
-
const policy = checked(value2,
|
|
6333
|
+
test: async (value2, predicateId, id2) => {
|
|
6334
|
+
const policy = checked(value2, id2, "boolean");
|
|
6160
6335
|
const predicate = config.predicates?.[predicateId];
|
|
6161
6336
|
if (!predicate) throw new CamelError("conversion_rejected", "Predicate is not registered.");
|
|
6162
|
-
return
|
|
6337
|
+
return emit4(value2, policy, evaluatePredicate(value2.value, predicate, config.registeredIds));
|
|
6163
6338
|
}
|
|
6164
6339
|
});
|
|
6165
|
-
return Object.freeze({ operations, policy: (
|
|
6340
|
+
return Object.freeze({ operations, policy: (id2) => policies.get(id2) ?? missingPolicy() });
|
|
6166
6341
|
}
|
|
6167
6342
|
async function convert(source, policy, value2, counts) {
|
|
6168
6343
|
const sourceKey = sourceIdentity(source);
|
|
@@ -6205,8 +6380,8 @@ function boundedInteger(value2, spec) {
|
|
|
6205
6380
|
}
|
|
6206
6381
|
function boundedNumber(value2, spec) {
|
|
6207
6382
|
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.");
|
|
6208
|
-
const
|
|
6209
|
-
if (/e/i.test(
|
|
6383
|
+
const text3 = String(value2);
|
|
6384
|
+
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.");
|
|
6210
6385
|
return value2;
|
|
6211
6386
|
}
|
|
6212
6387
|
function enumMember(value2, spec) {
|
|
@@ -6257,10 +6432,10 @@ function createCamelIngress(constants2 = []) {
|
|
|
6257
6432
|
const ingress = {
|
|
6258
6433
|
userInstruction: (value2, input) => createSafeInternal(value2, "user_instruction", metadata("user_instruction", input.id, input.readers)),
|
|
6259
6434
|
systemPolicy: (value2, input) => createSafeInternal(value2, "system_policy", metadata("system_policy", input.id, input.readers)),
|
|
6260
|
-
control: (
|
|
6261
|
-
const item = byId.get(
|
|
6435
|
+
control: (id2) => {
|
|
6436
|
+
const item = byId.get(id2);
|
|
6262
6437
|
if (!item) throw new CamelError("permission_denied", "Unknown control constant.");
|
|
6263
|
-
return createSafeInternal(item.value, "harness_constant", metadata("harness",
|
|
6438
|
+
return createSafeInternal(item.value, "harness_constant", metadata("harness", id2, item.readers));
|
|
6264
6439
|
},
|
|
6265
6440
|
external: (value2, label) => createUnsafeInternal(value2, label),
|
|
6266
6441
|
quarantinedOutput: (value2, input) => {
|
|
@@ -6284,9 +6459,9 @@ function assertNoUnsafeConstant(value2, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
6284
6459
|
}
|
|
6285
6460
|
for (const child of Object.values(value2)) assertNoUnsafeConstant(child, seen);
|
|
6286
6461
|
}
|
|
6287
|
-
function metadata(kind,
|
|
6288
|
-
if (!
|
|
6289
|
-
return { readers, provenance: [{ kind, id }] };
|
|
6462
|
+
function metadata(kind, id2, readers) {
|
|
6463
|
+
if (!id2) throw new CamelError("state_conflict", "Provenance IDs must be non-empty.");
|
|
6464
|
+
return { readers, provenance: [{ kind, id: id2 }] };
|
|
6290
6465
|
}
|
|
6291
6466
|
|
|
6292
6467
|
// ../camel/dist/policy.js
|
|
@@ -6350,7 +6525,7 @@ function isControlOwned(value2) {
|
|
|
6350
6525
|
return value2.label.safeBasis === "system_policy" || value2.label.safeBasis === "harness_constant";
|
|
6351
6526
|
}
|
|
6352
6527
|
function copyRegistries(registries) {
|
|
6353
|
-
return Object.freeze(Object.fromEntries(Object.entries(registries).map(([
|
|
6528
|
+
return Object.freeze(Object.fromEntries(Object.entries(registries).map(([id2, registry]) => [id2, Object.freeze({ digest: registry.digest, values: Object.freeze([...registry.values]) })])));
|
|
6354
6529
|
}
|
|
6355
6530
|
function validateUnsafeSelector(path, value2, tool) {
|
|
6356
6531
|
const policy = tool.unsafeSelectorPolicy;
|
|
@@ -6362,8 +6537,8 @@ function validateUnsafeSelector(path, value2, tool) {
|
|
|
6362
6537
|
return void 0;
|
|
6363
6538
|
}
|
|
6364
6539
|
function looksLikeDestination(value2) {
|
|
6365
|
-
const
|
|
6366
|
-
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(
|
|
6540
|
+
const text3 = value2.trim();
|
|
6541
|
+
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text3) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text3);
|
|
6367
6542
|
}
|
|
6368
6543
|
|
|
6369
6544
|
// ../harness/dist/chunk-ANNX7VGK.js
|
|
@@ -6373,9 +6548,9 @@ var import_path10 = require("path");
|
|
|
6373
6548
|
|
|
6374
6549
|
// ../graph/dist/chunk-PS2SO4UP.js
|
|
6375
6550
|
var nodeId = (kind, name) => `${kind}:${name}`;
|
|
6376
|
-
function parseNodeId(
|
|
6377
|
-
const at =
|
|
6378
|
-
return at < 0 ? { kind: "", name:
|
|
6551
|
+
function parseNodeId(id2) {
|
|
6552
|
+
const at = id2.indexOf(":");
|
|
6553
|
+
return at < 0 ? { kind: "", name: id2 } : { kind: id2.slice(0, at), name: id2.slice(at + 1) };
|
|
6379
6554
|
}
|
|
6380
6555
|
var GraphBuilder = class {
|
|
6381
6556
|
byId = /* @__PURE__ */ new Map();
|
|
@@ -6383,14 +6558,14 @@ var GraphBuilder = class {
|
|
|
6383
6558
|
seen = /* @__PURE__ */ new Set();
|
|
6384
6559
|
/** Add or enrich a node. Later attributes win; the kind never changes. */
|
|
6385
6560
|
node(kind, name, attrs) {
|
|
6386
|
-
const
|
|
6387
|
-
const existing = this.byId.get(
|
|
6561
|
+
const id2 = nodeId(kind, name);
|
|
6562
|
+
const existing = this.byId.get(id2);
|
|
6388
6563
|
if (existing) {
|
|
6389
|
-
if (attrs) this.byId.set(
|
|
6390
|
-
return
|
|
6564
|
+
if (attrs) this.byId.set(id2, { ...existing, attrs: { ...existing.attrs, ...attrs } });
|
|
6565
|
+
return id2;
|
|
6391
6566
|
}
|
|
6392
|
-
this.byId.set(
|
|
6393
|
-
return
|
|
6567
|
+
this.byId.set(id2, { id: id2, kind, name, ...attrs ? { attrs } : {} });
|
|
6568
|
+
return id2;
|
|
6394
6569
|
}
|
|
6395
6570
|
/**
|
|
6396
6571
|
* Add a directed edge, minting either endpoint if it is not known yet.
|
|
@@ -6400,10 +6575,10 @@ var GraphBuilder = class {
|
|
|
6400
6575
|
* by how often someone repeated an import.
|
|
6401
6576
|
*/
|
|
6402
6577
|
edge(from, kind, to, attrs) {
|
|
6403
|
-
for (const
|
|
6404
|
-
if (!this.byId.has(
|
|
6405
|
-
const parsed = parseNodeId(
|
|
6406
|
-
this.byId.set(
|
|
6578
|
+
for (const id2 of [from, to]) {
|
|
6579
|
+
if (!this.byId.has(id2)) {
|
|
6580
|
+
const parsed = parseNodeId(id2);
|
|
6581
|
+
this.byId.set(id2, { id: id2, kind: parsed.kind, name: parsed.name });
|
|
6407
6582
|
}
|
|
6408
6583
|
}
|
|
6409
6584
|
const key = `${from} ${kind} ${to}`;
|
|
@@ -6436,18 +6611,18 @@ function nodesOfKind(graph, kind) {
|
|
|
6436
6611
|
|
|
6437
6612
|
// ../graph/dist/index.js
|
|
6438
6613
|
var follows = (kinds, edge) => !kinds || kinds.includes(edge.kind);
|
|
6439
|
-
function incident(graph,
|
|
6614
|
+
function incident(graph, id2, traversal = {}) {
|
|
6440
6615
|
const direction = traversal.direction ?? "out";
|
|
6441
|
-
const forward = direction === "out" || direction === "both" ? graph.out.get(
|
|
6442
|
-
const backward = direction === "in" || direction === "both" ? graph.in.get(
|
|
6616
|
+
const forward = direction === "out" || direction === "both" ? graph.out.get(id2) ?? [] : [];
|
|
6617
|
+
const backward = direction === "in" || direction === "both" ? graph.in.get(id2) ?? [] : [];
|
|
6443
6618
|
return [...forward, ...backward].filter((edge) => follows(traversal.kinds, edge));
|
|
6444
6619
|
}
|
|
6445
6620
|
var otherEnd = (edge, from) => edge.from === from ? edge.to : edge.from;
|
|
6446
|
-
function neighbors(graph,
|
|
6621
|
+
function neighbors(graph, id2, traversal = {}) {
|
|
6447
6622
|
const seen = /* @__PURE__ */ new Set();
|
|
6448
|
-
for (const edge of incident(graph,
|
|
6449
|
-
const other = otherEnd(edge,
|
|
6450
|
-
if (other !==
|
|
6623
|
+
for (const edge of incident(graph, id2, traversal)) {
|
|
6624
|
+
const other = otherEnd(edge, id2);
|
|
6625
|
+
if (other !== id2) seen.add(other);
|
|
6451
6626
|
}
|
|
6452
6627
|
return [...seen];
|
|
6453
6628
|
}
|
|
@@ -6531,9 +6706,9 @@ async function extractImports(builder, input) {
|
|
|
6531
6706
|
const sources = input.paths.filter(isSourcePath);
|
|
6532
6707
|
const known = new Set(sources);
|
|
6533
6708
|
for (const path of sources) {
|
|
6534
|
-
let
|
|
6709
|
+
let text3;
|
|
6535
6710
|
try {
|
|
6536
|
-
|
|
6711
|
+
text3 = await input.read(path);
|
|
6537
6712
|
} catch {
|
|
6538
6713
|
continue;
|
|
6539
6714
|
}
|
|
@@ -6541,13 +6716,13 @@ async function extractImports(builder, input) {
|
|
|
6541
6716
|
const file = builder.node(FILE, path, pkg ? { pkg } : void 0);
|
|
6542
6717
|
if (pkg) builder.edge(builder.node(PACKAGE, pkg), CONTAINS, file);
|
|
6543
6718
|
const specifiers = /* @__PURE__ */ new Set();
|
|
6544
|
-
for (const match of
|
|
6545
|
-
for (const match of
|
|
6719
|
+
for (const match of text3.matchAll(IMPORT_FROM)) specifiers.add(match[1]);
|
|
6720
|
+
for (const match of text3.matchAll(BARE_IMPORT)) specifiers.add(match[1]);
|
|
6546
6721
|
for (const specifier of specifiers) {
|
|
6547
6722
|
const resolved = resolveImport(path, specifier, known);
|
|
6548
6723
|
if (resolved) builder.edge(file, IMPORTS, nodeId(FILE, resolved));
|
|
6549
6724
|
}
|
|
6550
|
-
for (const name of exportedNames(
|
|
6725
|
+
for (const name of exportedNames(text3)) {
|
|
6551
6726
|
builder.edge(file, EXPORTS, builder.node(SYMBOL, name));
|
|
6552
6727
|
}
|
|
6553
6728
|
}
|
|
@@ -6588,16 +6763,16 @@ async function extractData(builder, input) {
|
|
|
6588
6763
|
};
|
|
6589
6764
|
for (const path of input.paths) {
|
|
6590
6765
|
if (!SOURCE_FILE.test(path) || input.ignore?.(path)) continue;
|
|
6591
|
-
let
|
|
6766
|
+
let text3;
|
|
6592
6767
|
try {
|
|
6593
|
-
|
|
6768
|
+
text3 = await input.read(path);
|
|
6594
6769
|
} catch {
|
|
6595
6770
|
continue;
|
|
6596
6771
|
}
|
|
6597
|
-
for (const statement of
|
|
6772
|
+
for (const statement of text3.matchAll(STATEMENT)) {
|
|
6598
6773
|
const verb = statement[1].toUpperCase().replace(/\s+/g, " ");
|
|
6599
6774
|
const start = statement.index ?? 0;
|
|
6600
|
-
const rest =
|
|
6775
|
+
const rest = text3.slice(start + statement[0].length, start + STATEMENT_WINDOW);
|
|
6601
6776
|
if (verb === "SELECT") {
|
|
6602
6777
|
for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
|
|
6603
6778
|
continue;
|
|
@@ -6613,16 +6788,16 @@ async function extractData(builder, input) {
|
|
|
6613
6788
|
for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
|
|
6614
6789
|
}
|
|
6615
6790
|
}
|
|
6616
|
-
for (const match of
|
|
6617
|
-
touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(
|
|
6791
|
+
for (const match of text3.matchAll(NS_CONST)) {
|
|
6792
|
+
touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(text3, match.index ?? 0));
|
|
6618
6793
|
}
|
|
6619
|
-
for (const match of
|
|
6620
|
-
touch(path, match[1], NAMESPACE, accessFor(
|
|
6794
|
+
for (const match of text3.matchAll(NS_LITERAL)) {
|
|
6795
|
+
touch(path, match[1], NAMESPACE, accessFor(text3, match.index ?? 0));
|
|
6621
6796
|
}
|
|
6622
6797
|
}
|
|
6623
6798
|
}
|
|
6624
|
-
function accessFor(
|
|
6625
|
-
const window =
|
|
6799
|
+
function accessFor(text3, index) {
|
|
6800
|
+
const window = text3.slice(Math.max(0, index - 160), index + 40);
|
|
6626
6801
|
return /\b(?:transact|update|delete|create|insert|Ops)\b/.test(window) ? WRITES : READS;
|
|
6627
6802
|
}
|
|
6628
6803
|
async function buildCodeGraph(input) {
|
|
@@ -6753,13 +6928,13 @@ function createCodeRuntimeControlClient(options) {
|
|
|
6753
6928
|
if (!Number.isSafeInteger(modelRequestTimeoutMs) || modelRequestTimeoutMs < 3e4 || modelRequestTimeoutMs > 30 * 6e4) {
|
|
6754
6929
|
throw new TypeError("modelRequestTimeoutMs must be an integer from 30000 to 1800000");
|
|
6755
6930
|
}
|
|
6756
|
-
const
|
|
6931
|
+
const request3 = options.fetch ?? fetch;
|
|
6757
6932
|
const call2 = async (path, body, timeoutMs = requestTimeoutMs) => {
|
|
6758
6933
|
const timeout = AbortSignal.timeout(timeoutMs);
|
|
6759
6934
|
const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
|
|
6760
6935
|
let response2;
|
|
6761
6936
|
try {
|
|
6762
|
-
response2 = await
|
|
6937
|
+
response2 = await request3(`${endpoint}${path}`, {
|
|
6763
6938
|
method: "POST",
|
|
6764
6939
|
headers: { authorization: `Bearer ${options.token}`, "content-type": "application/json" },
|
|
6765
6940
|
body: JSON.stringify(body),
|
|
@@ -6772,7 +6947,7 @@ function createCodeRuntimeControlClient(options) {
|
|
|
6772
6947
|
}
|
|
6773
6948
|
const value2 = await response2.json().catch(() => null);
|
|
6774
6949
|
if (!response2.ok) {
|
|
6775
|
-
const problem =
|
|
6950
|
+
const problem = record5(record5(value2)?.error);
|
|
6776
6951
|
throw new CodeRuntimeControlError(
|
|
6777
6952
|
typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
|
|
6778
6953
|
response2.status,
|
|
@@ -6794,12 +6969,12 @@ function createCodeRuntimeControlClient(options) {
|
|
|
6794
6969
|
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
|
|
6795
6970
|
),
|
|
6796
6971
|
infer: async (sessionId, inference) => {
|
|
6797
|
-
const value2 =
|
|
6972
|
+
const value2 = record5(await call2(
|
|
6798
6973
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
|
|
6799
6974
|
inference,
|
|
6800
6975
|
modelRequestTimeoutMs
|
|
6801
6976
|
));
|
|
6802
|
-
if (!value2 || value2.requestId !== inference.requestId || !
|
|
6977
|
+
if (!value2 || value2.requestId !== inference.requestId || !record5(value2.response) || !record5(value2.receipt)) {
|
|
6803
6978
|
throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
|
|
6804
6979
|
}
|
|
6805
6980
|
return value2;
|
|
@@ -6867,12 +7042,12 @@ function validateHeartbeat(version, capabilities) {
|
|
|
6867
7042
|
}
|
|
6868
7043
|
}
|
|
6869
7044
|
function parseSnapshot(value2) {
|
|
6870
|
-
const root =
|
|
6871
|
-
const host =
|
|
7045
|
+
const root = record5(value2);
|
|
7046
|
+
const host = record5(root?.host);
|
|
6872
7047
|
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");
|
|
6873
7048
|
const bindingIds = /* @__PURE__ */ new Set();
|
|
6874
7049
|
const bindings = root.bindings.map((item) => {
|
|
6875
|
-
const binding =
|
|
7050
|
+
const binding = record5(item);
|
|
6876
7051
|
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)) {
|
|
6877
7052
|
throw invalid("binding");
|
|
6878
7053
|
}
|
|
@@ -6882,10 +7057,10 @@ function parseSnapshot(value2) {
|
|
|
6882
7057
|
const commandIds = /* @__PURE__ */ new Set();
|
|
6883
7058
|
const commandSequences = /* @__PURE__ */ new Set();
|
|
6884
7059
|
const commands = root.commands.map((item) => {
|
|
6885
|
-
const command =
|
|
7060
|
+
const command = record5(item);
|
|
6886
7061
|
const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
|
|
6887
7062
|
const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
|
|
6888
|
-
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)) || !
|
|
7063
|
+
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");
|
|
6889
7064
|
commandIds.add(command.commandId);
|
|
6890
7065
|
commandSequences.add(sequenceKey);
|
|
6891
7066
|
return command;
|
|
@@ -6893,10 +7068,10 @@ function parseSnapshot(value2) {
|
|
|
6893
7068
|
return { host, bindings, commands };
|
|
6894
7069
|
}
|
|
6895
7070
|
async function parseSource(value2) {
|
|
6896
|
-
const snapshot =
|
|
7071
|
+
const snapshot = record5(record5(value2)?.snapshot);
|
|
6897
7072
|
if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
|
|
6898
7073
|
const files = snapshot.files.map((value22) => {
|
|
6899
|
-
const file =
|
|
7074
|
+
const file = record5(value22);
|
|
6900
7075
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
|
|
6901
7076
|
return { path: file.path, content: file.content };
|
|
6902
7077
|
});
|
|
@@ -6905,11 +7080,11 @@ async function parseSource(value2) {
|
|
|
6905
7080
|
const aliases = /* @__PURE__ */ new Set();
|
|
6906
7081
|
const references = [];
|
|
6907
7082
|
for (const item of referencesValue) {
|
|
6908
|
-
const reference =
|
|
7083
|
+
const reference = record5(item);
|
|
6909
7084
|
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");
|
|
6910
7085
|
aliases.add(reference.alias);
|
|
6911
7086
|
const referenceFiles = reference.files.map((entry) => {
|
|
6912
|
-
const file =
|
|
7087
|
+
const file = record5(entry);
|
|
6913
7088
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
|
|
6914
7089
|
return { path: file.path, content: file.content };
|
|
6915
7090
|
});
|
|
@@ -6924,18 +7099,18 @@ async function parseSource(value2) {
|
|
|
6924
7099
|
return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
|
|
6925
7100
|
}
|
|
6926
7101
|
function parseReview(value2) {
|
|
6927
|
-
const review =
|
|
7102
|
+
const review = record5(record5(value2)?.review);
|
|
6928
7103
|
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");
|
|
6929
7104
|
return review;
|
|
6930
7105
|
}
|
|
6931
7106
|
function parseCandidate(value2) {
|
|
6932
|
-
const candidate =
|
|
7107
|
+
const candidate = record5(record5(value2)?.candidate);
|
|
6933
7108
|
if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
|
|
6934
7109
|
throw invalid("candidate");
|
|
6935
7110
|
}
|
|
6936
7111
|
return { candidateId: candidate.candidateId, status: candidate.status };
|
|
6937
7112
|
}
|
|
6938
|
-
var
|
|
7113
|
+
var record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
6939
7114
|
var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
|
|
6940
7115
|
var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
6941
7116
|
var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
@@ -7031,8 +7206,8 @@ function gitApply(cwd, patch2, check) {
|
|
|
7031
7206
|
});
|
|
7032
7207
|
let stderr = "";
|
|
7033
7208
|
child.stderr.setEncoding("utf8");
|
|
7034
|
-
child.stderr.on("data", (
|
|
7035
|
-
if (stderr.length < 4e3) stderr +=
|
|
7209
|
+
child.stderr.on("data", (text3) => {
|
|
7210
|
+
if (stderr.length < 4e3) stderr += text3.slice(0, 4e3);
|
|
7036
7211
|
});
|
|
7037
7212
|
child.once("error", reject);
|
|
7038
7213
|
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr.trim().slice(0, 500)))));
|
|
@@ -7900,7 +8075,7 @@ async function runCodeAgentAttempt(options) {
|
|
|
7900
8075
|
}
|
|
7901
8076
|
}
|
|
7902
8077
|
async function handleCodeRuntimeInference(input) {
|
|
7903
|
-
const { command, metadata: metadata2, request:
|
|
8078
|
+
const { command, metadata: metadata2, request: request3, state: state2 } = input;
|
|
7904
8079
|
if (state2.tokens >= metadata2.maxTokensPerInteraction) {
|
|
7905
8080
|
if (!state2.noticeEmitted) {
|
|
7906
8081
|
state2.noticeEmitted = true;
|
|
@@ -7913,7 +8088,7 @@ async function handleCodeRuntimeInference(input) {
|
|
|
7913
8088
|
return {
|
|
7914
8089
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
7915
8090
|
type: "inference.response",
|
|
7916
|
-
requestId:
|
|
8091
|
+
requestId: request3.requestId,
|
|
7917
8092
|
response: {
|
|
7918
8093
|
id: `budget:${command.commandId}`,
|
|
7919
8094
|
provider: "openai",
|
|
@@ -7927,9 +8102,9 @@ async function handleCodeRuntimeInference(input) {
|
|
|
7927
8102
|
}
|
|
7928
8103
|
const startedAt = Date.now();
|
|
7929
8104
|
const response2 = await input.control.infer(command.sessionId, {
|
|
7930
|
-
requestId:
|
|
8105
|
+
requestId: request3.requestId,
|
|
7931
8106
|
interactionId: command.commandId,
|
|
7932
|
-
call:
|
|
8107
|
+
call: request3.call
|
|
7933
8108
|
});
|
|
7934
8109
|
state2.tokens += response2.receipt.inputTokens + response2.receipt.outputTokens;
|
|
7935
8110
|
await input.event({
|
|
@@ -7946,14 +8121,14 @@ async function handleCodeRuntimeInference(input) {
|
|
|
7946
8121
|
return {
|
|
7947
8122
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
7948
8123
|
type: "inference.response",
|
|
7949
|
-
requestId:
|
|
8124
|
+
requestId: request3.requestId,
|
|
7950
8125
|
response: response2.response
|
|
7951
8126
|
};
|
|
7952
8127
|
}
|
|
7953
8128
|
function createCodeRuntimeInference(options) {
|
|
7954
8129
|
let seq = 0;
|
|
7955
8130
|
return {
|
|
7956
|
-
chat: async (
|
|
8131
|
+
chat: async (request3) => {
|
|
7957
8132
|
const requestId = `${options.command.commandId}:${++seq}`;
|
|
7958
8133
|
const answer = await handleCodeRuntimeInference({
|
|
7959
8134
|
command: options.command,
|
|
@@ -7965,7 +8140,7 @@ function createCodeRuntimeInference(options) {
|
|
|
7965
8140
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
7966
8141
|
type: "inference.request",
|
|
7967
8142
|
requestId,
|
|
7968
|
-
call:
|
|
8143
|
+
call: request3
|
|
7969
8144
|
}
|
|
7970
8145
|
});
|
|
7971
8146
|
if (answer.type !== "inference.response") throw new TypeError("brokered inference returned the wrong frame");
|
|
@@ -8171,9 +8346,9 @@ async function safePrefix(base, paths, prefix) {
|
|
|
8171
8346
|
function descriptor(name, effect, argumentRoles) {
|
|
8172
8347
|
return { name, version: 1, effect, inputSchema: { type: "object" }, argumentRoles, policyId: `odla.code.${name}.v1` };
|
|
8173
8348
|
}
|
|
8174
|
-
async function conversionPolicy(
|
|
8349
|
+
async function conversionPolicy(id2, output) {
|
|
8175
8350
|
const definition = {
|
|
8176
|
-
conversionId:
|
|
8351
|
+
conversionId: id2,
|
|
8177
8352
|
version: 1,
|
|
8178
8353
|
output,
|
|
8179
8354
|
maximumSourceBytes: 1e6,
|
|
@@ -8182,18 +8357,18 @@ async function conversionPolicy(id, output) {
|
|
|
8182
8357
|
};
|
|
8183
8358
|
return { ...definition, digest: await conversionPolicyDigest(definition) };
|
|
8184
8359
|
}
|
|
8185
|
-
async function registeredPolicy(
|
|
8360
|
+
async function registeredPolicy(id2, registryId, values) {
|
|
8186
8361
|
const mapping = Object.fromEntries(values.map((value2) => [value2, value2]));
|
|
8187
|
-
return conversionPolicy(
|
|
8362
|
+
return conversionPolicy(id2, {
|
|
8188
8363
|
kind: "registered_id",
|
|
8189
8364
|
registryId,
|
|
8190
8365
|
registryDigest: await registeredIdRegistryDigest(mapping)
|
|
8191
8366
|
});
|
|
8192
8367
|
}
|
|
8193
8368
|
async function conversionRegistry(policies, values) {
|
|
8194
|
-
const registeredIds = Object.fromEntries(await Promise.all(Object.entries(values).map(async ([
|
|
8369
|
+
const registeredIds = Object.fromEntries(await Promise.all(Object.entries(values).map(async ([id2, entries]) => {
|
|
8195
8370
|
const mapping = Object.fromEntries(entries.map((value2) => [value2, value2]));
|
|
8196
|
-
return [
|
|
8371
|
+
return [id2, { values: mapping, digest: await registeredIdRegistryDigest(mapping) }];
|
|
8197
8372
|
})));
|
|
8198
8373
|
return createConversionRegistry({ policies, registeredIds });
|
|
8199
8374
|
}
|
|
@@ -8247,10 +8422,10 @@ function decision(input, policy, approvalConsumed, tool, actionDigest) {
|
|
|
8247
8422
|
actionDigest: actionDigest ?? (policy.outcome === "require_approval" ? policy.actionDigest : "")
|
|
8248
8423
|
};
|
|
8249
8424
|
}
|
|
8250
|
-
function policyContext(context,
|
|
8425
|
+
function policyContext(context, request3, options, extra) {
|
|
8251
8426
|
return {
|
|
8252
8427
|
lease: context.lease,
|
|
8253
|
-
request:
|
|
8428
|
+
request: request3,
|
|
8254
8429
|
workspaceId: `workspace:${context.lease.task.attemptId}`,
|
|
8255
8430
|
readers: { kind: "principals", principalIds: [options.readerId] },
|
|
8256
8431
|
...extra
|
|
@@ -8269,8 +8444,8 @@ function optionalInteger(value2) {
|
|
|
8269
8444
|
if (!Number.isSafeInteger(value2) || value2 < 1) throw new TypeError("line bounds must be positive integers");
|
|
8270
8445
|
return value2;
|
|
8271
8446
|
}
|
|
8272
|
-
function response(
|
|
8273
|
-
return { requestId:
|
|
8447
|
+
function response(request3, ok, content2, details) {
|
|
8448
|
+
return { requestId: request3.requestId, ok, content: content2, ...details ? { details } : {} };
|
|
8274
8449
|
}
|
|
8275
8450
|
var cache = /* @__PURE__ */ new Map();
|
|
8276
8451
|
function workspaceGraphs(workspaceDir, paths) {
|
|
@@ -8286,7 +8461,7 @@ function workspaceGraphs(workspaceDir, paths) {
|
|
|
8286
8461
|
cache.set(workspaceDir, built);
|
|
8287
8462
|
return built;
|
|
8288
8463
|
}
|
|
8289
|
-
var shortId = (
|
|
8464
|
+
var shortId = (id2) => id2.slice(id2.indexOf(":") + 1);
|
|
8290
8465
|
function renderOverview(graphs, prefix) {
|
|
8291
8466
|
const rows = rollup(graphs.graph, FILE, prefix === void 0 ? {} : { prefix });
|
|
8292
8467
|
if (rows.length === 0) return prefix ? `No source under "${prefix}".` : "No source files.";
|
|
@@ -8295,19 +8470,19 @@ function renderOverview(graphs, prefix) {
|
|
|
8295
8470
|
return [`${total} source files. Directories, largest first \u2014 read one with sandbox.list --prefix.`, ...lines].join("\n");
|
|
8296
8471
|
}
|
|
8297
8472
|
function renderWhereIs(graphs, symbol) {
|
|
8298
|
-
const sites = neighbors(graphs.graph, nodeId(SYMBOL, symbol), { direction: "in", kinds: ["exports"] }).map((
|
|
8299
|
-
path: shortId(
|
|
8300
|
-
pkg: neighbors(graphs.graph,
|
|
8301
|
-
dependents: incident(graphs.graph,
|
|
8473
|
+
const sites = neighbors(graphs.graph, nodeId(SYMBOL, symbol), { direction: "in", kinds: ["exports"] }).map((id2) => ({
|
|
8474
|
+
path: shortId(id2),
|
|
8475
|
+
pkg: neighbors(graphs.graph, id2, { direction: "in", kinds: ["contains"] })[0],
|
|
8476
|
+
dependents: incident(graphs.graph, id2, { direction: "in", kinds: [IMPORTS] }).length
|
|
8302
8477
|
})).sort((left, right) => right.dependents - left.dependents || left.path.localeCompare(right.path));
|
|
8303
8478
|
if (sites.length === 0) return `No exported symbol named "${symbol}". Try sandbox.search for a textual match.`;
|
|
8304
8479
|
return sites.slice(0, 20).map((site) => `${site.path}${site.pkg ? ` [${shortId(site.pkg)}]` : ""} ${site.dependents} dependents`).join("\n");
|
|
8305
8480
|
}
|
|
8306
8481
|
function renderWhoImports(graphs, path) {
|
|
8307
|
-
const
|
|
8308
|
-
const importers = neighbors(graphs.graph,
|
|
8482
|
+
const id2 = nodeId(FILE, path);
|
|
8483
|
+
const importers = neighbors(graphs.graph, id2, { direction: "in", kinds: [IMPORTS] });
|
|
8309
8484
|
if (importers.length === 0) {
|
|
8310
|
-
return graphs.graph.nodes.has(
|
|
8485
|
+
return graphs.graph.nodes.has(id2) ? `Nothing imports ${path}. It is a leaf.` : `${path} is not a source file in this workspace.`;
|
|
8311
8486
|
}
|
|
8312
8487
|
return importers.slice(0, 40).map(shortId).sort().join("\n");
|
|
8313
8488
|
}
|
|
@@ -8330,11 +8505,11 @@ var GRAPH_TOOLS = /* @__PURE__ */ new Set([
|
|
|
8330
8505
|
"sandbox.who_imports",
|
|
8331
8506
|
"sandbox.who_touches"
|
|
8332
8507
|
]);
|
|
8333
|
-
async function read(context,
|
|
8334
|
-
exactKeys(
|
|
8335
|
-
const path = stringField(
|
|
8336
|
-
const startLine = optionalInteger(
|
|
8337
|
-
const endLine = optionalInteger(
|
|
8508
|
+
async function read(context, request3, options, policy) {
|
|
8509
|
+
exactKeys(request3.input, ["path", "startLine", "endLine"]);
|
|
8510
|
+
const path = stringField(request3.input, "path");
|
|
8511
|
+
const startLine = optionalInteger(request3.input.startLine) ?? 1;
|
|
8512
|
+
const endLine = optionalInteger(request3.input.endLine) ?? startLine + (options.maxReadLines ?? 2e3) - 1;
|
|
8338
8513
|
if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
|
|
8339
8514
|
throw new TypeError("requested line range exceeds its bound");
|
|
8340
8515
|
}
|
|
@@ -8342,8 +8517,8 @@ async function read(context, request2, options, policy) {
|
|
|
8342
8517
|
if (!paths.includes(path)) {
|
|
8343
8518
|
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.`);
|
|
8344
8519
|
}
|
|
8345
|
-
const allowed = await policy.read(policyContext(context,
|
|
8346
|
-
if (!allowed) return response(
|
|
8520
|
+
const allowed = await policy.read(policyContext(context, request3, options, { paths, path, startLine, endLine }));
|
|
8521
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8347
8522
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
8348
8523
|
const info = await (0, import_promises10.stat)(target);
|
|
8349
8524
|
if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
|
|
@@ -8356,74 +8531,74 @@ async function read(context, request2, options, policy) {
|
|
|
8356
8531
|
if (Buffer.byteLength(content2) > (options.maxReadBytes ?? 128 * 1024)) {
|
|
8357
8532
|
throw new TypeError("read result exceeds its byte bound");
|
|
8358
8533
|
}
|
|
8359
|
-
return response(
|
|
8534
|
+
return response(request3, true, content2, { path, startLine, endLine: Math.min(endLine, lines.length) });
|
|
8360
8535
|
}
|
|
8361
|
-
async function list(context,
|
|
8362
|
-
exactKeys(
|
|
8363
|
-
const raw =
|
|
8536
|
+
async function list(context, request3, options, policy) {
|
|
8537
|
+
exactKeys(request3.input, ["prefix", "maxEntries"]);
|
|
8538
|
+
const raw = request3.input.prefix;
|
|
8364
8539
|
const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
8365
|
-
const maxEntries = optionalInteger(
|
|
8540
|
+
const maxEntries = optionalInteger(request3.input.maxEntries) ?? 1e3;
|
|
8366
8541
|
if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
|
|
8367
8542
|
const paths = await registeredFiles(context.workspaceDir, 2e4);
|
|
8368
|
-
const allowed = await policy.list(policyContext(context,
|
|
8369
|
-
if (!allowed) return response(
|
|
8543
|
+
const allowed = await policy.list(policyContext(context, request3, options, { paths, ...prefix ? { prefix } : {} }));
|
|
8544
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8370
8545
|
const entries = listWorkspace(paths, { ...prefix ? { prefix } : {}, maxEntries });
|
|
8371
8546
|
if (!entries.length) {
|
|
8372
|
-
return response(
|
|
8547
|
+
return response(request3, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
|
|
8373
8548
|
}
|
|
8374
8549
|
const truncated = entries.length < paths.length && entries.length === maxEntries;
|
|
8375
8550
|
const hint = !prefix && paths.length > 500 ? `
|
|
8376
8551
|
\u2026 ${paths.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
|
|
8377
8552
|
return response(
|
|
8378
|
-
|
|
8553
|
+
request3,
|
|
8379
8554
|
true,
|
|
8380
8555
|
`${entries.join("\n")}${truncated ? `
|
|
8381
8556
|
\u2026 truncated at ${maxEntries} entries` : ""}${hint}`,
|
|
8382
8557
|
{ count: entries.length, truncated }
|
|
8383
8558
|
);
|
|
8384
8559
|
}
|
|
8385
|
-
async function search(context,
|
|
8386
|
-
exactKeys(
|
|
8387
|
-
const query = stringField(
|
|
8560
|
+
async function search(context, request3, options, policy) {
|
|
8561
|
+
exactKeys(request3.input, ["query", "prefix", "maxResults", "caseSensitive"]);
|
|
8562
|
+
const query = stringField(request3.input, "query");
|
|
8388
8563
|
if (query.length > 512) throw new TypeError("search query exceeds its bound");
|
|
8389
|
-
const raw =
|
|
8564
|
+
const raw = request3.input.prefix;
|
|
8390
8565
|
const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
8391
|
-
const maxResults = optionalInteger(
|
|
8566
|
+
const maxResults = optionalInteger(request3.input.maxResults) ?? 100;
|
|
8392
8567
|
if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
|
|
8393
|
-
const caseSensitive =
|
|
8568
|
+
const caseSensitive = request3.input.caseSensitive === void 0 ? true : request3.input.caseSensitive === true;
|
|
8394
8569
|
const paths = await registeredFiles(context.workspaceDir, 2e4);
|
|
8395
|
-
const allowed = await policy.search(policyContext(context,
|
|
8396
|
-
if (!allowed) return response(
|
|
8570
|
+
const allowed = await policy.search(policyContext(context, request3, options, { paths, query, ...prefix ? { prefix } : {} }));
|
|
8571
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8397
8572
|
const matches = await searchWorkspace(context.workspaceDir, paths, {
|
|
8398
8573
|
query,
|
|
8399
8574
|
maxResults,
|
|
8400
8575
|
caseSensitive,
|
|
8401
8576
|
...prefix ? { prefix } : {}
|
|
8402
8577
|
});
|
|
8403
|
-
if (!matches.length) return response(
|
|
8404
|
-
return response(
|
|
8578
|
+
if (!matches.length) return response(request3, true, `No match for "${query}".`, { count: 0 });
|
|
8579
|
+
return response(request3, true, matches.map((match) => `${match.path}:${match.line}: ${match.text}`).join("\n"), {
|
|
8405
8580
|
count: matches.length
|
|
8406
8581
|
});
|
|
8407
8582
|
}
|
|
8408
|
-
async function graphQuery(context,
|
|
8409
|
-
exactKeys(
|
|
8410
|
-
const raw =
|
|
8583
|
+
async function graphQuery(context, request3, options, policy) {
|
|
8584
|
+
exactKeys(request3.input, ["query"]);
|
|
8585
|
+
const raw = request3.input.query;
|
|
8411
8586
|
const query = typeof raw === "string" ? raw : "";
|
|
8412
8587
|
if (query.length > 512) throw new TypeError("query exceeds its bound");
|
|
8413
|
-
const allowed = await policy.graph(policyContext(context,
|
|
8414
|
-
tool:
|
|
8588
|
+
const allowed = await policy.graph(policyContext(context, request3, options, {
|
|
8589
|
+
tool: request3.tool,
|
|
8415
8590
|
selector: query
|
|
8416
8591
|
}));
|
|
8417
|
-
if (!allowed) return response(
|
|
8592
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8418
8593
|
const paths = await registeredFiles(context.workspaceDir, 2e4);
|
|
8419
8594
|
const graphs = await workspaceGraphs(context.workspaceDir, paths);
|
|
8420
|
-
if (
|
|
8421
|
-
return response(
|
|
8595
|
+
if (request3.tool === "sandbox.overview") {
|
|
8596
|
+
return response(request3, true, renderOverview(graphs, query || void 0));
|
|
8422
8597
|
}
|
|
8423
|
-
if (!query) throw new TypeError(`${
|
|
8424
|
-
if (
|
|
8425
|
-
if (
|
|
8426
|
-
return response(
|
|
8598
|
+
if (!query) throw new TypeError(`${request3.tool} requires a query`);
|
|
8599
|
+
if (request3.tool === "sandbox.where_is") return response(request3, true, renderWhereIs(graphs, query));
|
|
8600
|
+
if (request3.tool === "sandbox.who_imports") return response(request3, true, renderWhoImports(graphs, query));
|
|
8601
|
+
return response(request3, true, renderWhoTouches(graphs, query));
|
|
8427
8602
|
}
|
|
8428
8603
|
function createCodeToolBroker(options) {
|
|
8429
8604
|
validateOptions(options);
|
|
@@ -8431,24 +8606,24 @@ function createCodeToolBroker(options) {
|
|
|
8431
8606
|
const policy = createCodePolicyGate(options);
|
|
8432
8607
|
let tail = Promise.resolve();
|
|
8433
8608
|
return {
|
|
8434
|
-
execute(context,
|
|
8435
|
-
const result = tail.then(() =>
|
|
8609
|
+
execute(context, request3) {
|
|
8610
|
+
const result = tail.then(() => route2(context, request3, options, recipes, policy));
|
|
8436
8611
|
tail = result.then(() => void 0, () => void 0);
|
|
8437
8612
|
return result;
|
|
8438
8613
|
}
|
|
8439
8614
|
};
|
|
8440
8615
|
}
|
|
8441
|
-
async function
|
|
8616
|
+
async function route2(context, request3, options, recipes, policy) {
|
|
8442
8617
|
try {
|
|
8443
8618
|
if (context.signal?.aborted) throw new TypeError("tool request was cancelled");
|
|
8444
|
-
if (
|
|
8445
|
-
if (
|
|
8446
|
-
if (
|
|
8447
|
-
if (GRAPH_TOOLS.has(
|
|
8448
|
-
if (
|
|
8449
|
-
return await recipe(context,
|
|
8619
|
+
if (request3.tool === "sandbox.read") return await read(context, request3, options, policy);
|
|
8620
|
+
if (request3.tool === "sandbox.list") return await list(context, request3, options, policy);
|
|
8621
|
+
if (request3.tool === "sandbox.search") return await search(context, request3, options, policy);
|
|
8622
|
+
if (GRAPH_TOOLS.has(request3.tool)) return await graphQuery(context, request3, options, policy);
|
|
8623
|
+
if (request3.tool === "sandbox.apply_patch") return await patch(context, request3, options, policy);
|
|
8624
|
+
return await recipe(context, request3, options, recipes, policy);
|
|
8450
8625
|
} catch (reason) {
|
|
8451
|
-
return response(
|
|
8626
|
+
return response(request3, false, toolFailureMessage(reason));
|
|
8452
8627
|
}
|
|
8453
8628
|
}
|
|
8454
8629
|
function toolFailureMessage(reason) {
|
|
@@ -8460,34 +8635,34 @@ function toolFailureMessage(reason) {
|
|
|
8460
8635
|
if (code === "EACCES" || code === "EPERM") return "that path is not readable through this tool";
|
|
8461
8636
|
return "tool failed closed";
|
|
8462
8637
|
}
|
|
8463
|
-
async function patch(context,
|
|
8464
|
-
exactKeys(
|
|
8465
|
-
const value2 = stringField(
|
|
8638
|
+
async function patch(context, request3, options, policy) {
|
|
8639
|
+
exactKeys(request3.input, ["patch"]);
|
|
8640
|
+
const value2 = stringField(request3.input, "patch");
|
|
8466
8641
|
const paths = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
|
|
8467
8642
|
if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
|
|
8468
8643
|
throw new TypeError("patch targets a read-only reference source");
|
|
8469
8644
|
}
|
|
8470
|
-
const allowed = await policy.patch(policyContext(context,
|
|
8471
|
-
if (!allowed) return response(
|
|
8645
|
+
const allowed = await policy.patch(policyContext(context, request3, options, { patch: value2 }));
|
|
8646
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8472
8647
|
await applyCodePatch(context.workspaceDir, value2, paths);
|
|
8473
|
-
return response(
|
|
8648
|
+
return response(request3, true, `Applied patch to ${paths.length} file(s).`, { paths });
|
|
8474
8649
|
}
|
|
8475
|
-
async function recipe(context,
|
|
8476
|
-
exactKeys(
|
|
8477
|
-
const recipeId = stringField(
|
|
8650
|
+
async function recipe(context, request3, options, recipes, policy) {
|
|
8651
|
+
exactKeys(request3.input, ["recipeId"]);
|
|
8652
|
+
const recipeId = stringField(request3.input, "recipeId");
|
|
8478
8653
|
const digestLimits = {
|
|
8479
8654
|
maxFiles: options.maxRecipeWorkspaceFiles ?? 2e4,
|
|
8480
8655
|
maxBytes: options.maxRecipeWorkspaceBytes ?? 512 * 1024 * 1024
|
|
8481
8656
|
};
|
|
8482
8657
|
const sourceDigest = await digestStagedWorkspace(context.workspaceDir, digestLimits);
|
|
8483
|
-
const allowed = await policy.recipe(policyContext(context,
|
|
8658
|
+
const allowed = await policy.recipe(policyContext(context, request3, options, {
|
|
8484
8659
|
recipeIds: [...recipes.keys()].sort(),
|
|
8485
8660
|
recipeId,
|
|
8486
8661
|
sourceDigest
|
|
8487
8662
|
}));
|
|
8488
|
-
if (!allowed) return response(
|
|
8663
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8489
8664
|
const selected = recipes.get(recipeId);
|
|
8490
|
-
if (!selected) return response(
|
|
8665
|
+
if (!selected) return response(request3, false, "build recipe is not registered");
|
|
8491
8666
|
const staged = await stageWorkspace(context.workspaceDir, {
|
|
8492
8667
|
maxFiles: digestLimits.maxFiles,
|
|
8493
8668
|
maxBytes: digestLimits.maxBytes
|
|
@@ -8504,7 +8679,7 @@ async function recipe(context, request2, options, recipes, policy) {
|
|
|
8504
8679
|
const output = [result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
8505
8680
|
const ok = result.exitCode === 0 && !result.outputLimitExceeded && !result.timedOut;
|
|
8506
8681
|
const status = result.timedOut ? "timed out" : result.outputLimitExceeded ? "exceeded output limit" : ok ? "passed" : `failed with exit ${result.exitCode}`;
|
|
8507
|
-
return response(
|
|
8682
|
+
return response(request3, ok, `Recipe ${recipeId} ${status}.${output ? `
|
|
8508
8683
|
${output}` : ""}`, {
|
|
8509
8684
|
recipeId,
|
|
8510
8685
|
exitCode: result.exitCode,
|
|
@@ -8554,7 +8729,7 @@ async function runGoal(spec, attempt) {
|
|
|
8554
8729
|
const startedAt = now();
|
|
8555
8730
|
const attempts = [];
|
|
8556
8731
|
const boardErrors = [];
|
|
8557
|
-
const
|
|
8732
|
+
const emit4 = async (event) => {
|
|
8558
8733
|
if (!spec.onEvent) return;
|
|
8559
8734
|
try {
|
|
8560
8735
|
await spec.onEvent(event);
|
|
@@ -8567,7 +8742,7 @@ async function runGoal(spec, attempt) {
|
|
|
8567
8742
|
let costKnown = false;
|
|
8568
8743
|
const finish2 = async (stoppedReason) => {
|
|
8569
8744
|
const met = stoppedReason === "proof_passed";
|
|
8570
|
-
await
|
|
8745
|
+
await emit4(met ? { type: "goal_met", attempts: attempts.length, tokens, ...costKnown ? { costUsd } : {} } : {
|
|
8571
8746
|
type: "goal_abandoned",
|
|
8572
8747
|
reason: stoppedReason,
|
|
8573
8748
|
attempts: attempts.length,
|
|
@@ -8588,7 +8763,7 @@ async function runGoal(spec, attempt) {
|
|
|
8588
8763
|
if (spec.signal?.aborted) return finish2("cancelled");
|
|
8589
8764
|
if (spec.budget.deadline !== void 0 && now() >= spec.budget.deadline) return finish2("deadline");
|
|
8590
8765
|
const prompt = index === 1 ? openingPrompt(spec) : retryPrompt(spec, attempts.at(-1));
|
|
8591
|
-
await
|
|
8766
|
+
await emit4({ type: "attempt_started", attempt: index, prompt });
|
|
8592
8767
|
const outcome = await attempt({
|
|
8593
8768
|
attempt: index,
|
|
8594
8769
|
prompt,
|
|
@@ -8608,7 +8783,7 @@ async function runGoal(spec, attempt) {
|
|
|
8608
8783
|
...outcome.error === void 0 ? {} : { error: outcome.error }
|
|
8609
8784
|
});
|
|
8610
8785
|
if (outcome.gatePassed) return finish2("proof_passed");
|
|
8611
|
-
await
|
|
8786
|
+
await emit4({
|
|
8612
8787
|
type: "attempt_failed",
|
|
8613
8788
|
attempt: index,
|
|
8614
8789
|
feedback: outcome.feedback,
|
|
@@ -8657,7 +8832,7 @@ function createCodeRuntimeToolBroker(input, lease, role) {
|
|
|
8657
8832
|
readerId: `code-session:${lease.task.taskId}`,
|
|
8658
8833
|
readOnlyPrefixes: [".odla-references"]
|
|
8659
8834
|
});
|
|
8660
|
-
return role === "coding" ? broker : { execute: (context,
|
|
8835
|
+
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" }) };
|
|
8661
8836
|
}
|
|
8662
8837
|
var POSITIVE = (value2) => Number.isFinite(value2) && Number(value2) > 0 ? Number(value2) : void 0;
|
|
8663
8838
|
function codeGoalSpec(payload) {
|
|
@@ -9045,18 +9220,18 @@ var CodePiRuntimeEngine = class {
|
|
|
9045
9220
|
/** Report every brokered effect as it starts and finishes. */
|
|
9046
9221
|
#observed(command, active, broker) {
|
|
9047
9222
|
return {
|
|
9048
|
-
execute: async (context,
|
|
9223
|
+
execute: async (context, request3) => {
|
|
9049
9224
|
const startedAt = Date.now();
|
|
9050
9225
|
await this.#event(
|
|
9051
9226
|
command,
|
|
9052
|
-
{ type: "tool", phase: "started", tool:
|
|
9227
|
+
{ type: "tool", phase: "started", tool: request3.tool },
|
|
9053
9228
|
active.conversationRefs
|
|
9054
9229
|
).catch(() => void 0);
|
|
9055
|
-
const response2 = await broker.execute(context,
|
|
9230
|
+
const response2 = await broker.execute(context, request3);
|
|
9056
9231
|
await this.#event(command, {
|
|
9057
9232
|
type: "tool",
|
|
9058
9233
|
phase: "completed",
|
|
9059
|
-
tool:
|
|
9234
|
+
tool: request3.tool,
|
|
9060
9235
|
ok: response2.ok,
|
|
9061
9236
|
durationMs: Date.now() - startedAt
|
|
9062
9237
|
}, active.conversationRefs).catch(() => void 0);
|
|
@@ -9196,7 +9371,7 @@ async function waitForHostedPoll(milliseconds, signal) {
|
|
|
9196
9371
|
}
|
|
9197
9372
|
function isValidHostedSecurityPlan(value2, env) {
|
|
9198
9373
|
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;
|
|
9199
|
-
const validRoute = (
|
|
9374
|
+
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;
|
|
9200
9375
|
return validRoute(value2.routes?.discovery, "security.discovery") && validRoute(value2.routes?.validation, "security.validation");
|
|
9201
9376
|
}
|
|
9202
9377
|
function hostedSecurityCredential(value2) {
|
|
@@ -9545,20 +9720,20 @@ async function runCodeRuntime(input) {
|
|
|
9545
9720
|
}
|
|
9546
9721
|
}
|
|
9547
9722
|
function parseConnection(value2, appId, appEnv) {
|
|
9548
|
-
const root =
|
|
9549
|
-
const host =
|
|
9550
|
-
const offer =
|
|
9551
|
-
const binding =
|
|
9723
|
+
const root = record6(value2);
|
|
9724
|
+
const host = record6(root?.host);
|
|
9725
|
+
const offer = record6(root?.offer);
|
|
9726
|
+
const binding = record6(root?.binding);
|
|
9552
9727
|
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)) {
|
|
9553
9728
|
throw new Error("connect Code host returned an invalid response");
|
|
9554
9729
|
}
|
|
9555
9730
|
return root;
|
|
9556
9731
|
}
|
|
9557
9732
|
function apiFailure(action2, status, value2) {
|
|
9558
|
-
const message2 =
|
|
9733
|
+
const message2 = record6(record6(value2)?.error)?.message;
|
|
9559
9734
|
return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
|
|
9560
9735
|
}
|
|
9561
|
-
function
|
|
9736
|
+
function record6(value2) {
|
|
9562
9737
|
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
9563
9738
|
}
|
|
9564
9739
|
|
|
@@ -9777,9 +9952,9 @@ async function credentialCommand(parsed, deps = {}) {
|
|
|
9777
9952
|
}, doFetch, out, { optionalProjectCapabilities: ["app.manage"] });
|
|
9778
9953
|
const base = `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials`;
|
|
9779
9954
|
if (action2 === "revoke") {
|
|
9780
|
-
const
|
|
9781
|
-
if (!
|
|
9782
|
-
const response3 = await doFetch(`${base}/${encodeURIComponent(
|
|
9955
|
+
const id2 = parsed.positionals[2];
|
|
9956
|
+
if (!id2) throw new Error("credentials revoke requires the exact receipt id from credentials list");
|
|
9957
|
+
const response3 = await doFetch(`${base}/${encodeURIComponent(id2)}`, {
|
|
9783
9958
|
method: "DELETE",
|
|
9784
9959
|
headers: { authorization: `Bearer ${token}` }
|
|
9785
9960
|
});
|
|
@@ -9887,6 +10062,12 @@ Usage:
|
|
|
9887
10062
|
odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
|
|
9888
10063
|
odla-ai context remove <name> --yes [--json]
|
|
9889
10064
|
odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
|
|
10065
|
+
odla-ai monitor plan [--config odla.config.mjs] [--env prod] [--json]
|
|
10066
|
+
odla-ai monitor apply [--config odla.config.mjs] [--env prod] [--json] [--yes]
|
|
10067
|
+
odla-ai monitor run <probe-id> [--app <id>] [--env prod] [--json]
|
|
10068
|
+
odla-ai monitor status [--app <id>] [--context <name>] [--env prod] [--json]
|
|
10069
|
+
odla-ai monitor incidents [--app <id>] [--env prod] [--limit 100] [--runs] [--json]
|
|
10070
|
+
odla-ai monitor report [--app <id>] [--env prod] [--period daily|weekly] [--json]
|
|
9890
10071
|
odla-ai platform status [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
|
|
9891
10072
|
odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
|
|
9892
10073
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
@@ -10024,6 +10205,9 @@ Commands:
|
|
|
10024
10205
|
canary, collector ingest/scheduler trust, Cloudflare-owned
|
|
10025
10206
|
runtime metrics, and a machine verdict.
|
|
10026
10207
|
--json keeps auth progress on stderr for unattended agents.
|
|
10208
|
+
monitor Reconcile checked-in Kitesurf routes, rolling SLOs, spike/trend
|
|
10209
|
+
policies, and email digests; run probes manually and expose
|
|
10210
|
+
stable status, incident, and report JSON to agents and CI.
|
|
10027
10211
|
platform Read canonical fleet health, releases, provider load/freshness,
|
|
10028
10212
|
explicit unknowns, and next actions through a read-only grant.
|
|
10029
10213
|
provision Register services, compose integrations, persist credentials, optionally push secrets.
|
|
@@ -10227,7 +10411,7 @@ async function discussList(ctx, parsed) {
|
|
|
10227
10411
|
}
|
|
10228
10412
|
});
|
|
10229
10413
|
}
|
|
10230
|
-
async function discussRead(ctx,
|
|
10414
|
+
async function discussRead(ctx, id2, parsed) {
|
|
10231
10415
|
const requestedLimit = stringOpt(parsed.options.limit);
|
|
10232
10416
|
const requestedOffset = stringOpt(parsed.options.offset);
|
|
10233
10417
|
if (requestedLimit !== void 0 || requestedOffset !== void 0) {
|
|
@@ -10235,7 +10419,7 @@ async function discussRead(ctx, id, parsed) {
|
|
|
10235
10419
|
limit: requestedLimit ?? "200",
|
|
10236
10420
|
offset: requestedOffset ?? "0"
|
|
10237
10421
|
});
|
|
10238
|
-
const page2 = await request(ctx, "GET", `/topics/${encodeURIComponent(
|
|
10422
|
+
const page2 = await request(ctx, "GET", `/topics/${encodeURIComponent(id2)}?${query}`);
|
|
10239
10423
|
emit(
|
|
10240
10424
|
ctx,
|
|
10241
10425
|
page2,
|
|
@@ -10255,7 +10439,7 @@ async function discussRead(ctx, id, parsed) {
|
|
|
10255
10439
|
const page2 = await request(
|
|
10256
10440
|
ctx,
|
|
10257
10441
|
"GET",
|
|
10258
|
-
`/topics/${encodeURIComponent(
|
|
10442
|
+
`/topics/${encodeURIComponent(id2)}?limit=200&offset=${offset}`
|
|
10259
10443
|
);
|
|
10260
10444
|
topic = page2.topic;
|
|
10261
10445
|
for (const post of page2.posts) posts.set(post.id, post);
|
|
@@ -10297,20 +10481,20 @@ async function discussPost(ctx, parsed) {
|
|
|
10297
10481
|
});
|
|
10298
10482
|
emit(ctx, created, () => ctx.out.log(`opened topic ${created.id}`));
|
|
10299
10483
|
}
|
|
10300
|
-
async function discussReply(ctx,
|
|
10484
|
+
async function discussReply(ctx, id2, parsed) {
|
|
10301
10485
|
const created = await request(
|
|
10302
10486
|
ctx,
|
|
10303
10487
|
"POST",
|
|
10304
|
-
`/topics/${encodeURIComponent(
|
|
10488
|
+
`/topics/${encodeURIComponent(id2)}/replies`,
|
|
10305
10489
|
{ ...content(parsed), mutationId: writeMutationId(parsed) }
|
|
10306
10490
|
);
|
|
10307
10491
|
emit(ctx, created, () => ctx.out.log(`replied ${created.id}`));
|
|
10308
10492
|
}
|
|
10309
|
-
async function discussResolve(ctx,
|
|
10493
|
+
async function discussResolve(ctx, id2, resolved, parsed) {
|
|
10310
10494
|
const result = await request(
|
|
10311
10495
|
ctx,
|
|
10312
10496
|
"PATCH",
|
|
10313
|
-
`/topics/${encodeURIComponent(
|
|
10497
|
+
`/topics/${encodeURIComponent(id2)}`,
|
|
10314
10498
|
{ resolved, mutationId: writeMutationId(parsed) }
|
|
10315
10499
|
);
|
|
10316
10500
|
emit(ctx, result, () => ctx.out.log(`${resolved ? "resolved" : "reopened"} ${result.id}`));
|
|
@@ -10584,9 +10768,9 @@ var ALLOWED = [
|
|
|
10584
10768
|
"context",
|
|
10585
10769
|
"open"
|
|
10586
10770
|
];
|
|
10587
|
-
function requireId(
|
|
10588
|
-
if (!
|
|
10589
|
-
return
|
|
10771
|
+
function requireId(id2, action2) {
|
|
10772
|
+
if (!id2) throw new Error(`"discuss ${action2}" needs a topic id`);
|
|
10773
|
+
return id2;
|
|
10590
10774
|
}
|
|
10591
10775
|
async function buildContext(parsed, deps) {
|
|
10592
10776
|
const context = await resolveOperatorContext(parsed, {
|
|
@@ -10621,7 +10805,7 @@ async function buildContext(parsed, deps) {
|
|
|
10621
10805
|
async function discussCommand(parsed, deps = {}) {
|
|
10622
10806
|
assertArgs(parsed, ALLOWED, 3);
|
|
10623
10807
|
const action2 = parsed.positionals[1];
|
|
10624
|
-
const
|
|
10808
|
+
const id2 = parsed.positionals[2];
|
|
10625
10809
|
if (!action2) throw new Error('"discuss" needs an action. Run "odla-ai help".');
|
|
10626
10810
|
const ctx = await buildContext(parsed, deps);
|
|
10627
10811
|
switch (action2) {
|
|
@@ -10631,17 +10815,17 @@ async function discussCommand(parsed, deps = {}) {
|
|
|
10631
10815
|
case "topics":
|
|
10632
10816
|
return discussList(ctx, parsed);
|
|
10633
10817
|
case "read":
|
|
10634
|
-
return discussRead(ctx, requireId(
|
|
10818
|
+
return discussRead(ctx, requireId(id2, "read"), parsed);
|
|
10635
10819
|
case "post":
|
|
10636
10820
|
return discussPost(ctx, parsed);
|
|
10637
10821
|
case "reply":
|
|
10638
|
-
return discussReply(ctx, requireId(
|
|
10822
|
+
return discussReply(ctx, requireId(id2, "reply"), parsed);
|
|
10639
10823
|
case "resolve":
|
|
10640
|
-
return discussResolve(ctx, requireId(
|
|
10824
|
+
return discussResolve(ctx, requireId(id2, "resolve"), parsed.options.reopen !== true, parsed);
|
|
10641
10825
|
case "who":
|
|
10642
10826
|
return discussWho(ctx, parsed);
|
|
10643
10827
|
case "watch": {
|
|
10644
|
-
const result = await discussWatch(ctx,
|
|
10828
|
+
const result = await discussWatch(ctx, id2, parsed);
|
|
10645
10829
|
if (!result.found) throw new WatchTimeoutError(result.cursor);
|
|
10646
10830
|
return;
|
|
10647
10831
|
}
|
|
@@ -10712,8 +10896,8 @@ function collectFields(parsed, allowClear) {
|
|
|
10712
10896
|
if (allowClear) out[spec.key] = null;
|
|
10713
10897
|
continue;
|
|
10714
10898
|
}
|
|
10715
|
-
const
|
|
10716
|
-
out[spec.key] = spec.num ? Number(
|
|
10899
|
+
const text3 = stringOpt(value2);
|
|
10900
|
+
out[spec.key] = spec.num ? Number(text3) : text3;
|
|
10717
10901
|
}
|
|
10718
10902
|
return out;
|
|
10719
10903
|
}
|
|
@@ -10726,17 +10910,17 @@ function collectEntityFields(entity, parsed, allowClear) {
|
|
|
10726
10910
|
if (entity === "task" && fields.column === "ready") fields.column = "todo";
|
|
10727
10911
|
return fields;
|
|
10728
10912
|
}
|
|
10729
|
-
function statusCol(entity,
|
|
10730
|
-
if (entity === "bug") return `${
|
|
10913
|
+
function statusCol(entity, record11) {
|
|
10914
|
+
if (entity === "bug") return `${record11.status ?? ""}/${record11.severity ?? ""}`;
|
|
10731
10915
|
if (entity === "task") {
|
|
10732
|
-
const state2 =
|
|
10733
|
-
return
|
|
10916
|
+
const state2 = record11.column === "todo" ? "ready" : String(record11.column ?? "");
|
|
10917
|
+
return record11.revision ? `${state2}; r${record11.revision}` : state2;
|
|
10734
10918
|
}
|
|
10735
|
-
return String(
|
|
10919
|
+
return String(record11.status ?? "");
|
|
10736
10920
|
}
|
|
10737
|
-
function referenceMarkup(entity,
|
|
10738
|
-
const label = (
|
|
10739
|
-
return `@[${label}](pm:${entity}/${
|
|
10921
|
+
function referenceMarkup(entity, record11) {
|
|
10922
|
+
const label = (record11.title?.trim() || `${entity} ${record11.id}`).replaceAll("]", ")");
|
|
10923
|
+
return `@[${label}](pm:${entity}/${record11.id})`;
|
|
10740
10924
|
}
|
|
10741
10925
|
var STUDIO_SECTION = {
|
|
10742
10926
|
goal: "goals",
|
|
@@ -10744,19 +10928,19 @@ var STUDIO_SECTION = {
|
|
|
10744
10928
|
decision: "decisions",
|
|
10745
10929
|
bug: "bugs"
|
|
10746
10930
|
};
|
|
10747
|
-
function studioRecordUrl(ctx, entity,
|
|
10931
|
+
function studioRecordUrl(ctx, entity, id2) {
|
|
10748
10932
|
return new URL(
|
|
10749
|
-
`/studio/pm/${STUDIO_SECTION[entity]}/${encodeURIComponent(
|
|
10933
|
+
`/studio/pm/${STUDIO_SECTION[entity]}/${encodeURIComponent(id2)}`,
|
|
10750
10934
|
ctx.platformUrl
|
|
10751
10935
|
).href;
|
|
10752
10936
|
}
|
|
10753
|
-
function studioRecordLink(ctx, entity,
|
|
10754
|
-
const label = (
|
|
10755
|
-
return `[${label}](${studioRecordUrl(ctx, entity,
|
|
10937
|
+
function studioRecordLink(ctx, entity, record11) {
|
|
10938
|
+
const label = (record11.title?.trim() || `${entity} ${record11.id}`).replaceAll("]", ")");
|
|
10939
|
+
return `[${label}](${studioRecordUrl(ctx, entity, record11.id)})`;
|
|
10756
10940
|
}
|
|
10757
|
-
function printRecord(ctx, entity,
|
|
10941
|
+
function printRecord(ctx, entity, record11) {
|
|
10758
10942
|
ctx.out.log(
|
|
10759
|
-
`${
|
|
10943
|
+
`${record11.id} [${statusCol(entity, record11)}] ${record11.appId} ${studioRecordLink(ctx, entity, record11)}`
|
|
10760
10944
|
);
|
|
10761
10945
|
}
|
|
10762
10946
|
function emit2(ctx, value2, human) {
|
|
@@ -10810,52 +10994,52 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
10810
10994
|
input,
|
|
10811
10995
|
mutationId: writeMutationId2(parsed)
|
|
10812
10996
|
});
|
|
10813
|
-
const
|
|
10814
|
-
emit2(ctx, res, () => ctx.out.log(`created ${entity}: ${studioRecordLink(ctx, entity,
|
|
10997
|
+
const record11 = { id: res.id, appId, title: String(input.title) };
|
|
10998
|
+
emit2(ctx, res, () => ctx.out.log(`created ${entity}: ${studioRecordLink(ctx, entity, record11)}`));
|
|
10815
10999
|
}
|
|
10816
|
-
async function pmGet(ctx, entity,
|
|
10817
|
-
const { record:
|
|
10818
|
-
emit2(ctx,
|
|
11000
|
+
async function pmGet(ctx, entity, id2) {
|
|
11001
|
+
const { record: record11 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id2)}`);
|
|
11002
|
+
emit2(ctx, record11, () => printRecord(ctx, entity, record11));
|
|
10819
11003
|
}
|
|
10820
|
-
async function pmReference(ctx, entity,
|
|
10821
|
-
const { record:
|
|
11004
|
+
async function pmReference(ctx, entity, id2) {
|
|
11005
|
+
const { record: record11 } = await pmRequest(
|
|
10822
11006
|
ctx,
|
|
10823
11007
|
"GET",
|
|
10824
|
-
`/${entity}/${encodeURIComponent(
|
|
11008
|
+
`/${entity}/${encodeURIComponent(id2)}`
|
|
10825
11009
|
);
|
|
10826
|
-
const markup = referenceMarkup(entity,
|
|
10827
|
-
emit2(ctx, { kind: `pm:${entity}`, id:
|
|
11010
|
+
const markup = referenceMarkup(entity, record11);
|
|
11011
|
+
emit2(ctx, { kind: `pm:${entity}`, id: record11.id, label: record11.title ?? "", markup }, () => {
|
|
10828
11012
|
ctx.out.log(markup);
|
|
10829
11013
|
});
|
|
10830
11014
|
}
|
|
10831
|
-
async function pmSet(ctx, entity,
|
|
11015
|
+
async function pmSet(ctx, entity, id2, parsed) {
|
|
10832
11016
|
const patch2 = collectEntityFields(entity, parsed, true);
|
|
10833
11017
|
if (Object.keys(patch2).length === 0)
|
|
10834
11018
|
throw new Error("pm set needs at least one field flag (e.g. --status doing, --assignee me, --no-assignee)");
|
|
10835
|
-
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(
|
|
11019
|
+
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id2)}`, {
|
|
10836
11020
|
patch: patch2,
|
|
10837
11021
|
mutationId: writeMutationId2(parsed)
|
|
10838
11022
|
});
|
|
10839
11023
|
emit2(ctx, res, () => {
|
|
10840
|
-
if (!res.record) return ctx.out.log(`updated ${entity} ${
|
|
11024
|
+
if (!res.record) return ctx.out.log(`updated ${entity} ${id2}`);
|
|
10841
11025
|
ctx.out.log(`${entity}: ${studioRecordLink(ctx, entity, res.record)} \u2192 ${statusCol(entity, res.record)}`);
|
|
10842
11026
|
});
|
|
10843
11027
|
}
|
|
10844
|
-
async function pmDone(ctx, entity,
|
|
11028
|
+
async function pmDone(ctx, entity, id2, parsed) {
|
|
10845
11029
|
const decisionId = stringOpt(parsed.options.decision);
|
|
10846
11030
|
if (decisionId && entity !== "bug") throw new Error("--decision is only valid when completing a bug");
|
|
10847
11031
|
const patch2 = { ...DONE[entity], ...decisionId ? { decisionId } : {} };
|
|
10848
|
-
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(
|
|
11032
|
+
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id2)}`, {
|
|
10849
11033
|
patch: patch2,
|
|
10850
11034
|
mutationId: writeMutationId2(parsed)
|
|
10851
11035
|
});
|
|
10852
11036
|
emit2(ctx, res, () => {
|
|
10853
|
-
const label = res.record ? studioRecordLink(ctx, entity, res.record) :
|
|
11037
|
+
const label = res.record ? studioRecordLink(ctx, entity, res.record) : id2;
|
|
10854
11038
|
const state2 = res.record ? statusCol(entity, res.record) : "done";
|
|
10855
11039
|
ctx.out.log(`${entity}: ${label} \u2192 ${state2}`);
|
|
10856
11040
|
});
|
|
10857
11041
|
}
|
|
10858
|
-
async function pmTaskLifecycle(ctx,
|
|
11042
|
+
async function pmTaskLifecycle(ctx, id2, action2, parsed) {
|
|
10859
11043
|
const rawRevision = stringOpt(parsed.options["expected-revision"]);
|
|
10860
11044
|
const expectedRevision = Number(rawRevision);
|
|
10861
11045
|
if (!rawRevision || !Number.isSafeInteger(expectedRevision) || expectedRevision < 1) {
|
|
@@ -10865,7 +11049,7 @@ async function pmTaskLifecycle(ctx, id, action2, parsed) {
|
|
|
10865
11049
|
const res = action2 === "ready" ? await pmRequest(
|
|
10866
11050
|
ctx,
|
|
10867
11051
|
"PATCH",
|
|
10868
|
-
`/task/${encodeURIComponent(
|
|
11052
|
+
`/task/${encodeURIComponent(id2)}`,
|
|
10869
11053
|
{
|
|
10870
11054
|
patch: {
|
|
10871
11055
|
...collectEntityFields("task", parsed, true),
|
|
@@ -10877,12 +11061,12 @@ async function pmTaskLifecycle(ctx, id, action2, parsed) {
|
|
|
10877
11061
|
) : await pmRequest(
|
|
10878
11062
|
ctx,
|
|
10879
11063
|
"POST",
|
|
10880
|
-
`/task/${encodeURIComponent(
|
|
11064
|
+
`/task/${encodeURIComponent(id2)}/${action2}`,
|
|
10881
11065
|
{ expectedRevision, mutationId }
|
|
10882
11066
|
);
|
|
10883
11067
|
emit2(ctx, res, () => {
|
|
10884
11068
|
const state2 = res.record ? statusCol("task", res.record) : action2;
|
|
10885
|
-
const label = res.record ? studioRecordLink(ctx, "task", res.record) :
|
|
11069
|
+
const label = res.record ? studioRecordLink(ctx, "task", res.record) : id2;
|
|
10886
11070
|
ctx.out.log(`task: ${label} \u2192 ${state2}`);
|
|
10887
11071
|
});
|
|
10888
11072
|
}
|
|
@@ -10911,9 +11095,9 @@ async function pmNext(ctx, parsed) {
|
|
|
10911
11095
|
const result = {
|
|
10912
11096
|
appId,
|
|
10913
11097
|
projectId,
|
|
10914
|
-
openGoals: goals.filter((
|
|
10915
|
-
doing: tasks.filter((
|
|
10916
|
-
ready: tasks.filter((
|
|
11098
|
+
openGoals: goals.filter((record11) => record11.status === "open"),
|
|
11099
|
+
doing: tasks.filter((record11) => record11.column === "doing"),
|
|
11100
|
+
ready: tasks.filter((record11) => record11.column === "todo")
|
|
10917
11101
|
};
|
|
10918
11102
|
emit2(ctx, result, () => {
|
|
10919
11103
|
ctx.out.log(`${appId}: goal-aligned work intake (read only)`);
|
|
@@ -10924,10 +11108,10 @@ async function pmNext(ctx, parsed) {
|
|
|
10924
11108
|
]) {
|
|
10925
11109
|
ctx.out.log(`${label}:`);
|
|
10926
11110
|
if (!records.length) ctx.out.log("- (none)");
|
|
10927
|
-
else for (const
|
|
11111
|
+
else for (const record11 of records) printRecord(
|
|
10928
11112
|
ctx,
|
|
10929
11113
|
label === "open goals" ? "goal" : "task",
|
|
10930
|
-
|
|
11114
|
+
record11
|
|
10931
11115
|
);
|
|
10932
11116
|
}
|
|
10933
11117
|
if (!result.openGoals.length) {
|
|
@@ -10951,9 +11135,9 @@ async function pmHandoff(ctx, parsed) {
|
|
|
10951
11135
|
const handoff = {
|
|
10952
11136
|
appId,
|
|
10953
11137
|
projectId,
|
|
10954
|
-
unmetGoals: goals.filter((
|
|
10955
|
-
activeTasks: tasks.filter((
|
|
10956
|
-
openBugs: bugs.filter((
|
|
11138
|
+
unmetGoals: goals.filter((record11) => record11.status !== "met"),
|
|
11139
|
+
activeTasks: tasks.filter((record11) => record11.column !== "done"),
|
|
11140
|
+
openBugs: bugs.filter((record11) => record11.status !== "fixed" && record11.status !== "wontfix")
|
|
10957
11141
|
};
|
|
10958
11142
|
const result = {
|
|
10959
11143
|
...handoff,
|
|
@@ -10972,45 +11156,45 @@ async function pmHandoff(ctx, parsed) {
|
|
|
10972
11156
|
]) {
|
|
10973
11157
|
ctx.out.log(`${label}:`);
|
|
10974
11158
|
if (!records.length) ctx.out.log("- (none)");
|
|
10975
|
-
else for (const
|
|
11159
|
+
else for (const record11 of records) printRecord(
|
|
10976
11160
|
ctx,
|
|
10977
11161
|
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
10978
|
-
|
|
11162
|
+
record11
|
|
10979
11163
|
);
|
|
10980
11164
|
}
|
|
10981
11165
|
});
|
|
10982
11166
|
}
|
|
10983
|
-
async function pmRemove(ctx, entity,
|
|
10984
|
-
await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(
|
|
10985
|
-
ctx.out.log(`deleted ${entity} ${
|
|
11167
|
+
async function pmRemove(ctx, entity, id2) {
|
|
11168
|
+
await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id2)}`);
|
|
11169
|
+
ctx.out.log(`deleted ${entity} ${id2}`);
|
|
10986
11170
|
}
|
|
10987
11171
|
|
|
10988
11172
|
// src/pm-links.ts
|
|
10989
|
-
async function pmLink(ctx, entity,
|
|
10990
|
-
const { record:
|
|
11173
|
+
async function pmLink(ctx, entity, id2) {
|
|
11174
|
+
const { record: record11 } = await pmRequest(
|
|
10991
11175
|
ctx,
|
|
10992
11176
|
"GET",
|
|
10993
|
-
`/${entity}/${encodeURIComponent(
|
|
11177
|
+
`/${entity}/${encodeURIComponent(id2)}`
|
|
10994
11178
|
);
|
|
10995
|
-
const url = studioRecordUrl(ctx, entity,
|
|
10996
|
-
const markdown = studioRecordLink(ctx, entity,
|
|
10997
|
-
emit2(ctx, { kind: entity, id:
|
|
11179
|
+
const url = studioRecordUrl(ctx, entity, record11.id);
|
|
11180
|
+
const markdown = studioRecordLink(ctx, entity, record11);
|
|
11181
|
+
emit2(ctx, { kind: entity, id: record11.id, label: record11.title ?? "", url, markdown }, () => {
|
|
10998
11182
|
ctx.out.log(markdown);
|
|
10999
11183
|
});
|
|
11000
11184
|
}
|
|
11001
11185
|
|
|
11002
11186
|
// src/pm-comments.ts
|
|
11003
|
-
async function pmComment(ctx, entity,
|
|
11187
|
+
async function pmComment(ctx, entity, id2, parsed) {
|
|
11004
11188
|
const body = stringOpt(parsed.options.body);
|
|
11005
11189
|
if (!body) throw new Error('pm comment needs --body "..."');
|
|
11006
|
-
await pmRequest(ctx, "POST", `/${entity}/${encodeURIComponent(
|
|
11190
|
+
await pmRequest(ctx, "POST", `/${entity}/${encodeURIComponent(id2)}/comments`, {
|
|
11007
11191
|
body,
|
|
11008
11192
|
mutationId: writeMutationId2(parsed)
|
|
11009
11193
|
});
|
|
11010
|
-
ctx.out.log(`commented on ${entity} ${
|
|
11194
|
+
ctx.out.log(`commented on ${entity} ${id2}`);
|
|
11011
11195
|
}
|
|
11012
|
-
async function pmComments(ctx, entity,
|
|
11013
|
-
const { messages } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(
|
|
11196
|
+
async function pmComments(ctx, entity, id2) {
|
|
11197
|
+
const { messages } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id2)}/comments`);
|
|
11014
11198
|
emit2(ctx, messages, () => {
|
|
11015
11199
|
if (messages.length === 0) ctx.out.log("(no comments)");
|
|
11016
11200
|
else for (const message2 of messages) {
|
|
@@ -11028,12 +11212,12 @@ function fieldLine(change) {
|
|
|
11028
11212
|
const before = change.before.length > 60 ? `${change.before.slice(0, 60)}\u2026` : change.before;
|
|
11029
11213
|
return `${change.field} (was: ${before.replace(/\s+/g, " ")})`;
|
|
11030
11214
|
}
|
|
11031
|
-
async function pmHistory(ctx, entity,
|
|
11215
|
+
async function pmHistory(ctx, entity, id2, parsed) {
|
|
11032
11216
|
const limit = numberOpt(parsed.options.limit, "--limit");
|
|
11033
11217
|
const page2 = await pmRequest(
|
|
11034
11218
|
ctx,
|
|
11035
11219
|
"GET",
|
|
11036
|
-
`/${entity}/${encodeURIComponent(
|
|
11220
|
+
`/${entity}/${encodeURIComponent(id2)}/history${limit === void 0 ? "" : `?limit=${limit}`}`
|
|
11037
11221
|
);
|
|
11038
11222
|
emit2(ctx, page2, () => {
|
|
11039
11223
|
if (!page2.entries.length) {
|
|
@@ -11121,16 +11305,16 @@ async function page(ctx, appId, cursor) {
|
|
|
11121
11305
|
}
|
|
11122
11306
|
return data;
|
|
11123
11307
|
}
|
|
11124
|
-
function recordState(
|
|
11125
|
-
if (
|
|
11126
|
-
return String(
|
|
11308
|
+
function recordState(record11) {
|
|
11309
|
+
if (record11.column) return record11.column === "todo" ? "ready" : record11.column;
|
|
11310
|
+
return String(record11.status ?? "");
|
|
11127
11311
|
}
|
|
11128
11312
|
function eventRecord(event) {
|
|
11129
11313
|
return event.payload.payload;
|
|
11130
11314
|
}
|
|
11131
11315
|
function eventLabel(event) {
|
|
11132
|
-
const
|
|
11133
|
-
if (
|
|
11316
|
+
const record11 = eventRecord(event);
|
|
11317
|
+
if (record11) return String(record11.title ?? event.payload.entityId);
|
|
11134
11318
|
const body = event.payload.message?.body?.replace(/\s+/g, " ").trim();
|
|
11135
11319
|
return body || event.payload.entityId;
|
|
11136
11320
|
}
|
|
@@ -11138,10 +11322,10 @@ function report2(ctx, parsed, result) {
|
|
|
11138
11322
|
if (ctx.json) ctx.out.log(JSON.stringify(result, null, 2));
|
|
11139
11323
|
else if (parsed.options.jsonl !== true && result.found) {
|
|
11140
11324
|
for (const event of result.events ?? []) {
|
|
11141
|
-
const
|
|
11142
|
-
const state2 =
|
|
11325
|
+
const record11 = eventRecord(event);
|
|
11326
|
+
const state2 = record11 ? recordState(record11) : "comment";
|
|
11143
11327
|
ctx.out.log(
|
|
11144
|
-
`${event.id} ${event.type} ${state2}${
|
|
11328
|
+
`${event.id} ${event.type} ${state2}${record11?.revision ? `; r${record11.revision}` : ""} ${eventLabel(event)}`
|
|
11145
11329
|
);
|
|
11146
11330
|
}
|
|
11147
11331
|
}
|
|
@@ -11215,8 +11399,8 @@ async function pmWatch(ctx, parsed) {
|
|
|
11215
11399
|
}
|
|
11216
11400
|
firstSuccess = false;
|
|
11217
11401
|
const matching = current.events.filter((event) => {
|
|
11218
|
-
const
|
|
11219
|
-
const state2 =
|
|
11402
|
+
const record11 = eventRecord(event);
|
|
11403
|
+
const state2 = record11 ? recordState(record11).toLowerCase() : "";
|
|
11220
11404
|
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);
|
|
11221
11405
|
});
|
|
11222
11406
|
for (const event of matching) {
|
|
@@ -11304,9 +11488,9 @@ async function pmProjectAdd(ctx, parsed) {
|
|
|
11304
11488
|
});
|
|
11305
11489
|
emit2(ctx, result, () => ctx.out.log(`created project: ${result.project.name} (${result.project.id})`));
|
|
11306
11490
|
}
|
|
11307
|
-
async function pmProjectUse(ctx,
|
|
11491
|
+
async function pmProjectUse(ctx, id2) {
|
|
11308
11492
|
if (!ctx.rootDir) throw new Error("pm project use needs a local project directory");
|
|
11309
|
-
const { project } = await pmRequest(ctx, "GET", `/project/${encodeURIComponent(
|
|
11493
|
+
const { project } = await pmRequest(ctx, "GET", `/project/${encodeURIComponent(id2)}`);
|
|
11310
11494
|
if (project.status !== "active") throw new Error(`project ${project.name} is ${project.status}, not active`);
|
|
11311
11495
|
writePmProjectContext(ctx.rootDir, { appId: project.appId, projectId: project.id });
|
|
11312
11496
|
emit2(ctx, project, () => ctx.out.log(`using ${project.appId} / ${project.name} (${project.id}) in this worktree`));
|
|
@@ -11374,9 +11558,9 @@ function allowedOptions(entity, action2) {
|
|
|
11374
11558
|
const entityOptions = action2 === "list" || action2 === "add" || action2 === "set" || action2 === "done" ? ENTITY_OPTIONS[entity][action2] : [];
|
|
11375
11559
|
return [...COMMON_OPTIONS, ...ACTION_OPTIONS[action2], ...entityOptions];
|
|
11376
11560
|
}
|
|
11377
|
-
function requireId2(
|
|
11378
|
-
if (!
|
|
11379
|
-
return
|
|
11561
|
+
function requireId2(id2, action2) {
|
|
11562
|
+
if (!id2) throw new Error(`"pm ... ${action2}" needs an item id`);
|
|
11563
|
+
return id2;
|
|
11380
11564
|
}
|
|
11381
11565
|
async function buildContext2(parsed, deps) {
|
|
11382
11566
|
const context = await resolveOperatorContext(parsed, {
|
|
@@ -11467,34 +11651,34 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
11467
11651
|
throw new Error(`pm ${action2} is only valid for tasks`);
|
|
11468
11652
|
}
|
|
11469
11653
|
const ctx = await buildContext2(parsed, deps);
|
|
11470
|
-
const
|
|
11654
|
+
const id2 = parsed.positionals[3];
|
|
11471
11655
|
switch (action2) {
|
|
11472
11656
|
case "list":
|
|
11473
11657
|
return pmList(ctx, entity, parsed);
|
|
11474
11658
|
case "add":
|
|
11475
11659
|
return pmAdd(ctx, entity, parsed);
|
|
11476
11660
|
case "get":
|
|
11477
|
-
return pmGet(ctx, entity, requireId2(
|
|
11661
|
+
return pmGet(ctx, entity, requireId2(id2, action2));
|
|
11478
11662
|
case "set":
|
|
11479
|
-
return pmSet(ctx, entity, requireId2(
|
|
11663
|
+
return pmSet(ctx, entity, requireId2(id2, action2), parsed);
|
|
11480
11664
|
case "done":
|
|
11481
|
-
return pmDone(ctx, entity, requireId2(
|
|
11665
|
+
return pmDone(ctx, entity, requireId2(id2, action2), parsed);
|
|
11482
11666
|
case "comment":
|
|
11483
|
-
return pmComment(ctx, entity, requireId2(
|
|
11667
|
+
return pmComment(ctx, entity, requireId2(id2, action2), parsed);
|
|
11484
11668
|
case "comments":
|
|
11485
|
-
return pmComments(ctx, entity, requireId2(
|
|
11669
|
+
return pmComments(ctx, entity, requireId2(id2, action2));
|
|
11486
11670
|
case "history":
|
|
11487
|
-
return pmHistory(ctx, entity, requireId2(
|
|
11671
|
+
return pmHistory(ctx, entity, requireId2(id2, action2), parsed);
|
|
11488
11672
|
case "rm":
|
|
11489
|
-
return pmRemove(ctx, entity, requireId2(
|
|
11673
|
+
return pmRemove(ctx, entity, requireId2(id2, action2));
|
|
11490
11674
|
case "link":
|
|
11491
|
-
return pmLink(ctx, entity, requireId2(
|
|
11675
|
+
return pmLink(ctx, entity, requireId2(id2, action2));
|
|
11492
11676
|
case "ref":
|
|
11493
|
-
return pmReference(ctx, entity, requireId2(
|
|
11677
|
+
return pmReference(ctx, entity, requireId2(id2, action2));
|
|
11494
11678
|
case "ready":
|
|
11495
11679
|
case "claim":
|
|
11496
11680
|
case "release":
|
|
11497
|
-
return pmTaskLifecycle(ctx, requireId2(
|
|
11681
|
+
return pmTaskLifecycle(ctx, requireId2(id2, action2), action2, parsed);
|
|
11498
11682
|
}
|
|
11499
11683
|
}
|
|
11500
11684
|
|
|
@@ -11597,17 +11781,17 @@ async function platformStatus(parsed, deps) {
|
|
|
11597
11781
|
}
|
|
11598
11782
|
}
|
|
11599
11783
|
function isPlatformStatus(value2) {
|
|
11600
|
-
if (!
|
|
11601
|
-
if (!
|
|
11602
|
-
if (!
|
|
11784
|
+
if (!record7(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
|
|
11785
|
+
if (!record7(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
|
|
11786
|
+
if (!record7(value2.catalog) || !record7(value2.summary)) return false;
|
|
11603
11787
|
return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
|
|
11604
11788
|
}
|
|
11605
11789
|
function apiMessage(value2) {
|
|
11606
|
-
if (!
|
|
11607
|
-
const error =
|
|
11790
|
+
if (!record7(value2)) return "request failed";
|
|
11791
|
+
const error = record7(value2.error) ? value2.error : value2;
|
|
11608
11792
|
return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
|
|
11609
11793
|
}
|
|
11610
|
-
function
|
|
11794
|
+
function record7(value2) {
|
|
11611
11795
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
11612
11796
|
}
|
|
11613
11797
|
|
|
@@ -11648,7 +11832,7 @@ function statusVerdict(reads) {
|
|
|
11648
11832
|
severity: "degraded"
|
|
11649
11833
|
});
|
|
11650
11834
|
}
|
|
11651
|
-
const performance =
|
|
11835
|
+
const performance = record8(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
|
|
11652
11836
|
if (performance?.status === "unavailable") {
|
|
11653
11837
|
reasons.push({
|
|
11654
11838
|
source: "liveSync",
|
|
@@ -11729,7 +11913,7 @@ function statusVerdict(reads) {
|
|
|
11729
11913
|
reasons
|
|
11730
11914
|
};
|
|
11731
11915
|
}
|
|
11732
|
-
function
|
|
11916
|
+
function record8(value2) {
|
|
11733
11917
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
11734
11918
|
}
|
|
11735
11919
|
function numeric2(value2) {
|
|
@@ -11757,7 +11941,7 @@ function printO11yStatus(status, out) {
|
|
|
11757
11941
|
out.log(
|
|
11758
11942
|
`o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
|
|
11759
11943
|
);
|
|
11760
|
-
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(
|
|
11944
|
+
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record9) : [];
|
|
11761
11945
|
const requests = routes.reduce(
|
|
11762
11946
|
(total, row) => total + numeric3(row.requests),
|
|
11763
11947
|
0
|
|
@@ -11769,39 +11953,39 @@ function printO11yStatus(status, out) {
|
|
|
11769
11953
|
out.log(
|
|
11770
11954
|
`application ${status.application.httpStatus} ${requests} requests ${errors} errors`
|
|
11771
11955
|
);
|
|
11772
|
-
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(
|
|
11956
|
+
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record9) : [];
|
|
11773
11957
|
out.log(
|
|
11774
11958
|
`application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
|
|
11775
11959
|
(row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
|
|
11776
11960
|
).join(", ") : "none observed"}`
|
|
11777
11961
|
);
|
|
11778
11962
|
out.log(liveSyncLine(status.liveSync));
|
|
11779
|
-
const canaryDurations =
|
|
11963
|
+
const canaryDurations = record9(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
|
|
11780
11964
|
out.log(
|
|
11781
11965
|
`canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
|
|
11782
11966
|
);
|
|
11783
|
-
const collectorIngest =
|
|
11784
|
-
const collectorStorage =
|
|
11967
|
+
const collectorIngest = record9(status.collector.body.ingest) ? status.collector.body.ingest : {};
|
|
11968
|
+
const collectorStorage = record9(collectorIngest.storage) ? collectorIngest.storage : {};
|
|
11785
11969
|
out.log(
|
|
11786
11970
|
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
11787
11971
|
);
|
|
11788
|
-
const providerMetrics =
|
|
11789
|
-
const providerCapacity =
|
|
11790
|
-
const workerMemory =
|
|
11972
|
+
const providerMetrics = record9(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
11973
|
+
const providerCapacity = record9(status.provider.body.capacity) ? status.provider.body.capacity : {};
|
|
11974
|
+
const workerMemory = record9(providerCapacity.memory) ? providerCapacity.memory : {};
|
|
11791
11975
|
out.log(
|
|
11792
11976
|
`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`
|
|
11793
11977
|
);
|
|
11794
11978
|
for (const line of providerCapacityLines(status.providerCapacity)) {
|
|
11795
11979
|
out.log(line);
|
|
11796
11980
|
}
|
|
11797
|
-
const coverage =
|
|
11798
|
-
const coverageCounts =
|
|
11799
|
-
const coverageBudget =
|
|
11981
|
+
const coverage = record9(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
|
|
11982
|
+
const coverageCounts = record9(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
|
|
11983
|
+
const coverageBudget = record9(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
|
|
11800
11984
|
out.log(
|
|
11801
11985
|
`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`
|
|
11802
11986
|
);
|
|
11803
11987
|
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
11804
|
-
const providerFreshness =
|
|
11988
|
+
const providerFreshness = record9(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
11805
11989
|
out.log(
|
|
11806
11990
|
`cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
|
|
11807
11991
|
);
|
|
@@ -11810,17 +11994,17 @@ function printO11yStatus(status, out) {
|
|
|
11810
11994
|
);
|
|
11811
11995
|
}
|
|
11812
11996
|
function providerCapacityLines(read3) {
|
|
11813
|
-
const resources =
|
|
11814
|
-
const durableObjects =
|
|
11815
|
-
const periodic =
|
|
11816
|
-
const storage =
|
|
11817
|
-
const d1 =
|
|
11818
|
-
const d1Activity =
|
|
11819
|
-
const d1Storage =
|
|
11820
|
-
const d1Latency =
|
|
11821
|
-
const r2 =
|
|
11822
|
-
const r2Operations =
|
|
11823
|
-
const r2Storage =
|
|
11997
|
+
const resources = record9(read3.body.resources) ? read3.body.resources : {};
|
|
11998
|
+
const durableObjects = record9(resources.durableObjects) ? resources.durableObjects : {};
|
|
11999
|
+
const periodic = record9(durableObjects.periodic) ? durableObjects.periodic : {};
|
|
12000
|
+
const storage = record9(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
|
|
12001
|
+
const d1 = record9(resources.d1) ? resources.d1 : {};
|
|
12002
|
+
const d1Activity = record9(d1.activity) ? d1.activity : {};
|
|
12003
|
+
const d1Storage = record9(d1.storage) ? d1.storage : {};
|
|
12004
|
+
const d1Latency = record9(d1Activity.latency) ? d1Activity.latency : {};
|
|
12005
|
+
const r2 = record9(resources.r2) ? resources.r2 : {};
|
|
12006
|
+
const r2Operations = record9(r2.operations) ? r2.operations : {};
|
|
12007
|
+
const r2Storage = record9(r2.storage) ? r2.storage : {};
|
|
11824
12008
|
const status = String(
|
|
11825
12009
|
read3.body.status ?? read3.body.error ?? "unavailable"
|
|
11826
12010
|
);
|
|
@@ -11831,11 +12015,11 @@ function providerCapacityLines(read3) {
|
|
|
11831
12015
|
];
|
|
11832
12016
|
}
|
|
11833
12017
|
function liveSyncLine(read3) {
|
|
11834
|
-
const performance =
|
|
11835
|
-
const commitToSend =
|
|
12018
|
+
const performance = record9(read3.body.performance) ? read3.body.performance : {};
|
|
12019
|
+
const commitToSend = record9(performance.commitToSend) ? performance.commitToSend : {};
|
|
11836
12020
|
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`;
|
|
11837
12021
|
}
|
|
11838
|
-
function
|
|
12022
|
+
function record9(value2) {
|
|
11839
12023
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
11840
12024
|
}
|
|
11841
12025
|
function numeric3(value2) {
|
|
@@ -12006,19 +12190,292 @@ function statusMinutes(value2) {
|
|
|
12006
12190
|
}
|
|
12007
12191
|
async function read2(url, headers, doFetch) {
|
|
12008
12192
|
const response2 = await doFetch(url, { headers });
|
|
12009
|
-
const
|
|
12193
|
+
const text3 = await response2.text();
|
|
12010
12194
|
let body = {};
|
|
12011
|
-
if (
|
|
12195
|
+
if (text3) {
|
|
12012
12196
|
try {
|
|
12013
|
-
const value2 = JSON.parse(
|
|
12197
|
+
const value2 = JSON.parse(text3);
|
|
12014
12198
|
body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
|
|
12015
12199
|
} catch {
|
|
12016
|
-
body = { message:
|
|
12200
|
+
body = { message: text3.slice(0, 300) };
|
|
12017
12201
|
}
|
|
12018
12202
|
}
|
|
12019
12203
|
return { httpStatus: response2.status, body };
|
|
12020
12204
|
}
|
|
12021
12205
|
|
|
12206
|
+
// src/monitoring-config.ts
|
|
12207
|
+
var import_node_crypto4 = require("crypto");
|
|
12208
|
+
function monitoringWireConfig(cfg, env) {
|
|
12209
|
+
const monitoring = cfg.o11y?.monitoring;
|
|
12210
|
+
if (!monitoring) throw new Error("o11y.monitoring is not configured");
|
|
12211
|
+
if (!cfg.services.includes("o11y")) throw new Error('o11y.monitoring requires "o11y" in services');
|
|
12212
|
+
const authoredLink = cfg.links?.[env];
|
|
12213
|
+
if (!authoredLink) throw new Error(`links.${env} is required for live monitoring`);
|
|
12214
|
+
const baseUrl = new URL(authoredLink).toString();
|
|
12215
|
+
const selectedProbes = (monitoring.probes ?? []).filter((probe) => !probe.envs || probe.envs.includes(env));
|
|
12216
|
+
const probeIds = new Set(selectedProbes.map((probe) => probe.id));
|
|
12217
|
+
const selectedSlos = monitoring.slos.filter(
|
|
12218
|
+
(slo) => slo.indicator.type === "o11y-metric" || slo.indicator.probes.some((id2) => probeIds.has(id2))
|
|
12219
|
+
);
|
|
12220
|
+
if (selectedSlos.length === 0) throw new Error(`o11y.monitoring has no SLOs for env "${env}"`);
|
|
12221
|
+
for (const slo of selectedSlos) {
|
|
12222
|
+
if (slo.indicator.type !== "probe-success") continue;
|
|
12223
|
+
const unavailable = slo.indicator.probes.filter((id2) => !probeIds.has(id2));
|
|
12224
|
+
if (unavailable.length) throw new Error(`SLO "${slo.id}" mixes probes unavailable in env "${env}": ${unavailable.join(", ")}`);
|
|
12225
|
+
}
|
|
12226
|
+
const payload = {
|
|
12227
|
+
environment: env,
|
|
12228
|
+
baseUrl,
|
|
12229
|
+
probes: selectedProbes.map(normalizeProbe),
|
|
12230
|
+
slos: selectedSlos.map(normalizeSlo),
|
|
12231
|
+
...notification(cfg.o11y.monitoring.notifications?.[env])
|
|
12232
|
+
};
|
|
12233
|
+
const revision = `sha256:${(0, import_node_crypto4.createHash)("sha256").update(canonical(payload)).digest("hex")}`;
|
|
12234
|
+
return { revision, ...payload };
|
|
12235
|
+
}
|
|
12236
|
+
function normalizeProbe(probe) {
|
|
12237
|
+
return {
|
|
12238
|
+
id: probe.id,
|
|
12239
|
+
route: probe.route,
|
|
12240
|
+
cadenceMinutes: durationMinutes(probe.every),
|
|
12241
|
+
timeoutMs: probe.timeout ?? 2e4,
|
|
12242
|
+
...probe.ready?.selector ? { readySelector: probe.ready.selector } : {},
|
|
12243
|
+
expect: {
|
|
12244
|
+
status: probe.expect.status,
|
|
12245
|
+
...probe.expect.titleIncludes ? { titleIncludes: probe.expect.titleIncludes } : {},
|
|
12246
|
+
textIncludes: probe.expect.textIncludes ?? [],
|
|
12247
|
+
accessibility: probe.expect.accessibility ?? []
|
|
12248
|
+
},
|
|
12249
|
+
enabled: probe.enabled !== false
|
|
12250
|
+
};
|
|
12251
|
+
}
|
|
12252
|
+
function normalizeSlo(slo) {
|
|
12253
|
+
return {
|
|
12254
|
+
id: slo.id,
|
|
12255
|
+
name: slo.name ?? slo.id,
|
|
12256
|
+
indicator: normalizeIndicator(slo.indicator),
|
|
12257
|
+
target: slo.target,
|
|
12258
|
+
windowMinutes: durationMinutes(slo.window),
|
|
12259
|
+
spike: {
|
|
12260
|
+
badChecks: slo.alerts?.spike?.badChecks ?? 2,
|
|
12261
|
+
withinChecks: slo.alerts?.spike?.withinChecks ?? 3,
|
|
12262
|
+
recoverAfter: slo.alerts?.spike?.recoverAfter ?? 2
|
|
12263
|
+
},
|
|
12264
|
+
trend: {
|
|
12265
|
+
burnRate: slo.alerts?.trend?.burnRate ?? 1,
|
|
12266
|
+
shortMinutes: durationMinutes(slo.alerts?.trend?.shortWindow ?? "6h"),
|
|
12267
|
+
longMinutes: durationMinutes(slo.alerts?.trend?.longWindow ?? "3d"),
|
|
12268
|
+
minBadChecks: slo.alerts?.trend?.minBadChecks ?? 2
|
|
12269
|
+
},
|
|
12270
|
+
enabled: slo.enabled !== false
|
|
12271
|
+
};
|
|
12272
|
+
}
|
|
12273
|
+
function normalizeIndicator(indicator) {
|
|
12274
|
+
if (indicator.type === "probe-success") {
|
|
12275
|
+
return { type: "probe-success", probes: [...new Set(indicator.probes)] };
|
|
12276
|
+
}
|
|
12277
|
+
return {
|
|
12278
|
+
type: "o11y-metric",
|
|
12279
|
+
metric: indicator.metric,
|
|
12280
|
+
comparator: indicator.comparator,
|
|
12281
|
+
threshold: indicator.threshold,
|
|
12282
|
+
cadenceMinutes: durationMinutes(indicator.every),
|
|
12283
|
+
observationWindowMinutes: durationMinutes(indicator.observationWindow),
|
|
12284
|
+
...indicator.route ? { route: indicator.route } : {}
|
|
12285
|
+
};
|
|
12286
|
+
}
|
|
12287
|
+
function notification(policy) {
|
|
12288
|
+
if (!policy) return {};
|
|
12289
|
+
return {
|
|
12290
|
+
notifications: {
|
|
12291
|
+
email: [...new Set(policy.email.map((email) => email.trim().toLowerCase()))],
|
|
12292
|
+
timezone: policy.timezone,
|
|
12293
|
+
daily: policy.daily === void 0 ? "08:00" : policy.daily,
|
|
12294
|
+
weekly: policy.weekly === void 0 ? { day: "monday", at: "08:00" } : policy.weekly
|
|
12295
|
+
}
|
|
12296
|
+
};
|
|
12297
|
+
}
|
|
12298
|
+
function durationMinutes(value2) {
|
|
12299
|
+
const match = /^(\d+)(m|h|d)$/.exec(value2);
|
|
12300
|
+
if (!match) throw new Error(`unsupported duration ${value2}`);
|
|
12301
|
+
const amount = Number(match[1]);
|
|
12302
|
+
return amount * (match[2] === "d" ? 1440 : match[2] === "h" ? 60 : 1);
|
|
12303
|
+
}
|
|
12304
|
+
function canonical(value2) {
|
|
12305
|
+
if (Array.isArray(value2)) return `[${value2.map(canonical).join(",")}]`;
|
|
12306
|
+
if (value2 && typeof value2 === "object") {
|
|
12307
|
+
return `{${Object.entries(value2).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`).join(",")}}`;
|
|
12308
|
+
}
|
|
12309
|
+
return JSON.stringify(value2);
|
|
12310
|
+
}
|
|
12311
|
+
|
|
12312
|
+
// src/monitor-command.ts
|
|
12313
|
+
var OPTIONS = [
|
|
12314
|
+
"config",
|
|
12315
|
+
"context",
|
|
12316
|
+
"platform",
|
|
12317
|
+
"token",
|
|
12318
|
+
"email",
|
|
12319
|
+
"json",
|
|
12320
|
+
"app",
|
|
12321
|
+
"env",
|
|
12322
|
+
"open",
|
|
12323
|
+
"yes",
|
|
12324
|
+
"period",
|
|
12325
|
+
"limit",
|
|
12326
|
+
"runs"
|
|
12327
|
+
];
|
|
12328
|
+
async function monitorCommand(parsed, deps = {}) {
|
|
12329
|
+
assertArgs(parsed, OPTIONS, 3);
|
|
12330
|
+
const action2 = parsed.positionals[1] ?? "status";
|
|
12331
|
+
if (!["plan", "apply", "run", "status", "incidents", "report"].includes(action2)) {
|
|
12332
|
+
throw new Error(`unknown monitor action "${action2}". Try "odla-ai monitor status --json".`);
|
|
12333
|
+
}
|
|
12334
|
+
const context = await resolveOperatorContext(parsed, {
|
|
12335
|
+
allowMissingConfig: action2 !== "plan" && action2 !== "apply",
|
|
12336
|
+
requireApp: true
|
|
12337
|
+
});
|
|
12338
|
+
if ((action2 === "plan" || action2 === "apply") && context.config.status !== "loaded") {
|
|
12339
|
+
throw new Error(`monitor ${action2} requires odla.config.mjs`);
|
|
12340
|
+
}
|
|
12341
|
+
const env = context.environment.value ?? context.cfg.envs[0] ?? "prod";
|
|
12342
|
+
const appId = context.app.value;
|
|
12343
|
+
const doFetch = deps.fetch ?? fetch;
|
|
12344
|
+
const out = deps.stdout ?? console;
|
|
12345
|
+
const token = await getDeveloperToken(
|
|
12346
|
+
context.cfg,
|
|
12347
|
+
{
|
|
12348
|
+
configPath: context.cfg.configPath,
|
|
12349
|
+
token: stringOpt(parsed.options.token),
|
|
12350
|
+
email: stringOpt(parsed.options.email),
|
|
12351
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
12352
|
+
openApprovalUrl: deps.openUrl
|
|
12353
|
+
},
|
|
12354
|
+
doFetch,
|
|
12355
|
+
out,
|
|
12356
|
+
action2 === "apply" || action2 === "run" ? { optionalProjectCapabilities: ["app.manage"] } : {}
|
|
12357
|
+
);
|
|
12358
|
+
const base = `${context.cfg.platformUrl}/o11y/${encodeURIComponent(appId)}/monitoring`;
|
|
12359
|
+
const headers = { authorization: `Bearer ${token}`, "content-type": "application/json" };
|
|
12360
|
+
const jsonOutput = parsed.options.json === true;
|
|
12361
|
+
if (action2 === "plan" || action2 === "apply") {
|
|
12362
|
+
const desired = monitoringWireConfig(context.cfg, env);
|
|
12363
|
+
const live = await request2(`${base}?env=${encodeURIComponent(env)}`, { headers }, doFetch);
|
|
12364
|
+
const currentRevision = record10(live.config) ? string(live.config.revision) : null;
|
|
12365
|
+
const changed = currentRevision !== desired.revision;
|
|
12366
|
+
const plan = {
|
|
12367
|
+
schemaVersion: 1,
|
|
12368
|
+
appId,
|
|
12369
|
+
env,
|
|
12370
|
+
currentRevision,
|
|
12371
|
+
desiredRevision: desired.revision,
|
|
12372
|
+
changed,
|
|
12373
|
+
probes: desired.probes.map((probe) => ({ id: probe.id, route: probe.route, cadenceMinutes: probe.cadenceMinutes })),
|
|
12374
|
+
slos: desired.slos.map((slo) => ({ id: slo.id, indicator: slo.indicator, target: slo.target, windowMinutes: slo.windowMinutes })),
|
|
12375
|
+
notifications: desired.notifications ? { recipients: desired.notifications.email.length, timezone: desired.notifications.timezone, daily: desired.notifications.daily, weekly: desired.notifications.weekly } : null
|
|
12376
|
+
};
|
|
12377
|
+
if (action2 === "plan") {
|
|
12378
|
+
emit3(plan, jsonOutput, out, () => {
|
|
12379
|
+
out.log(`monitor plan ${appId}/${env}: ${changed ? "changes pending" : "in sync"}`);
|
|
12380
|
+
out.log(`revision ${currentRevision ?? "not configured"} -> ${desired.revision}`);
|
|
12381
|
+
for (const probe of desired.probes) out.log(`probe ${probe.id} ${probe.route} every ${probe.cadenceMinutes}m`);
|
|
12382
|
+
for (const slo of desired.slos) out.log(`slo ${slo.id} ${slo.indicator.type} ${(slo.target * 100).toFixed(3)}% ${slo.windowMinutes}m`);
|
|
12383
|
+
});
|
|
12384
|
+
return;
|
|
12385
|
+
}
|
|
12386
|
+
if ((env === "prod" || env === "production") && parsed.options.yes !== true) {
|
|
12387
|
+
throw new Error(`refusing to apply live monitoring for "${env}" without --yes; run monitor plan first`);
|
|
12388
|
+
}
|
|
12389
|
+
if (!changed) {
|
|
12390
|
+
emit3({ ...plan, applied: false }, jsonOutput, out, () => out.log(`monitor apply ${appId}/${env}: already in sync`));
|
|
12391
|
+
return;
|
|
12392
|
+
}
|
|
12393
|
+
const applied = await request2(`${base}?env=${encodeURIComponent(env)}`, {
|
|
12394
|
+
method: "PUT",
|
|
12395
|
+
headers,
|
|
12396
|
+
body: JSON.stringify(desired)
|
|
12397
|
+
}, doFetch);
|
|
12398
|
+
emit3({ schemaVersion: 1, appId, env, ...applied }, jsonOutput, out, () => out.log(`monitor apply ${appId}/${env}: ${applied.changed === true ? "applied" : "unchanged"} ${desired.revision}`));
|
|
12399
|
+
return;
|
|
12400
|
+
}
|
|
12401
|
+
if (action2 === "run") {
|
|
12402
|
+
const probeId = parsed.positionals[2];
|
|
12403
|
+
if (!probeId) throw new Error("monitor run requires a probe id");
|
|
12404
|
+
const result2 = await request2(`${base}/probes/${encodeURIComponent(probeId)}/run?env=${encodeURIComponent(env)}`, {
|
|
12405
|
+
method: "POST",
|
|
12406
|
+
headers
|
|
12407
|
+
}, doFetch);
|
|
12408
|
+
emit3(result2, jsonOutput, out, () => {
|
|
12409
|
+
const run = record10(result2.run) ? result2.run : {};
|
|
12410
|
+
out.log(`monitor run ${appId}/${env}/${probeId}: ${string(run.outcome) ?? "unknown"}${run.failure_code ? ` (${String(run.failure_code)})` : ""}`);
|
|
12411
|
+
});
|
|
12412
|
+
return;
|
|
12413
|
+
}
|
|
12414
|
+
let path = action2;
|
|
12415
|
+
if (action2 === "report") {
|
|
12416
|
+
const period = stringOpt(parsed.options.period) ?? "daily";
|
|
12417
|
+
if (period !== "daily" && period !== "weekly") throw new Error("--period must be daily or weekly");
|
|
12418
|
+
path = `report?period=${period}`;
|
|
12419
|
+
} else if (action2 === "incidents") {
|
|
12420
|
+
const params = new URLSearchParams({ limit: String(numberOpt(parsed.options.limit, "--limit") ?? 100) });
|
|
12421
|
+
if (boolOpt(parsed.options.runs) === true) params.set("runs", "true");
|
|
12422
|
+
path = `incidents?${params}`;
|
|
12423
|
+
}
|
|
12424
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
12425
|
+
const result = await request2(`${base}/${path}${separator}env=${encodeURIComponent(env)}`, { headers }, doFetch);
|
|
12426
|
+
emit3(result, jsonOutput, out, () => printRead(action2, appId, env, result, out));
|
|
12427
|
+
}
|
|
12428
|
+
async function request2(url, init, doFetch) {
|
|
12429
|
+
const response2 = await doFetch(url, init);
|
|
12430
|
+
const text3 = await response2.text();
|
|
12431
|
+
let body = {};
|
|
12432
|
+
try {
|
|
12433
|
+
const parsed = text3 ? JSON.parse(text3) : {};
|
|
12434
|
+
body = record10(parsed) ? parsed : { value: parsed };
|
|
12435
|
+
} catch {
|
|
12436
|
+
body = { message: text3.slice(0, 500) };
|
|
12437
|
+
}
|
|
12438
|
+
if (!response2.ok) {
|
|
12439
|
+
const error = record10(body.error) ? body.error : body;
|
|
12440
|
+
throw new Error(string(error.message) ?? string(error.code) ?? `monitor request failed (${response2.status})`);
|
|
12441
|
+
}
|
|
12442
|
+
return body;
|
|
12443
|
+
}
|
|
12444
|
+
function printRead(action2, appId, env, result, out) {
|
|
12445
|
+
if (action2 === "status") {
|
|
12446
|
+
out.log(`monitor status ${appId}/${env}: ${String(result.overall ?? (result.configured === false ? "not configured" : "unknown"))}`);
|
|
12447
|
+
const slos = Array.isArray(result.slos) ? result.slos.filter(record10) : [];
|
|
12448
|
+
for (const slo of slos) out.log(`slo ${String(slo.id)} ${String(slo.state)} ${percent(slo.observed)} observed ${percent(slo.budgetRemaining)} budget remaining`);
|
|
12449
|
+
const incidents = Array.isArray(result.openIncidents) ? result.openIncidents.length : 0;
|
|
12450
|
+
const gaps = Array.isArray(result.monitoringGaps) ? result.monitoringGaps.length : 0;
|
|
12451
|
+
out.log(`open incidents ${incidents}`);
|
|
12452
|
+
out.log(`monitoring gaps ${gaps}`);
|
|
12453
|
+
return;
|
|
12454
|
+
}
|
|
12455
|
+
if (action2 === "incidents") {
|
|
12456
|
+
const incidents = Array.isArray(result.incidents) ? result.incidents.filter(record10) : [];
|
|
12457
|
+
out.log(`monitor incidents ${appId}/${env}: ${incidents.length}`);
|
|
12458
|
+
for (const incident2 of incidents) out.log(`${String(incident2.state)} ${String(incident2.kind)} ${String(incident2.slo_id)} ${new Date(Number(incident2.opened_at)).toISOString()}`);
|
|
12459
|
+
return;
|
|
12460
|
+
}
|
|
12461
|
+
out.log(`monitor report ${appId}/${env}: ${String(result.period)} ${String(result.overall)}`);
|
|
12462
|
+
const probes = Array.isArray(result.probes) ? result.probes.filter(record10) : [];
|
|
12463
|
+
for (const probe of probes) out.log(`probe ${String(probe.id)} ${Number(probe.good)} good ${Number(probe.bad)} bad ${Number(probe.unknown)} unknown`);
|
|
12464
|
+
}
|
|
12465
|
+
function emit3(value2, json, out, human) {
|
|
12466
|
+
if (json) out.log(JSON.stringify(value2, null, 2));
|
|
12467
|
+
else human();
|
|
12468
|
+
}
|
|
12469
|
+
function record10(value2) {
|
|
12470
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
12471
|
+
}
|
|
12472
|
+
function string(value2) {
|
|
12473
|
+
return typeof value2 === "string" ? value2 : null;
|
|
12474
|
+
}
|
|
12475
|
+
function percent(value2) {
|
|
12476
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? `${(value2 * 100).toFixed(2)}%` : "unknown";
|
|
12477
|
+
}
|
|
12478
|
+
|
|
12022
12479
|
// src/provision.ts
|
|
12023
12480
|
var import_apps13 = require("@odla-ai/apps");
|
|
12024
12481
|
var import_ai5 = require("@odla-ai/ai");
|
|
@@ -12172,13 +12629,13 @@ async function safeText7(res) {
|
|
|
12172
12629
|
}
|
|
12173
12630
|
|
|
12174
12631
|
// src/runtime-credentials.ts
|
|
12175
|
-
var
|
|
12632
|
+
var import_node_crypto5 = require("crypto");
|
|
12176
12633
|
function runtimeUrl(cfg, suffix = "") {
|
|
12177
12634
|
return `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials${suffix}`;
|
|
12178
12635
|
}
|
|
12179
12636
|
async function safeError(response2) {
|
|
12180
|
-
const
|
|
12181
|
-
return redactSecrets(
|
|
12637
|
+
const text3 = await response2.text();
|
|
12638
|
+
return redactSecrets(text3.slice(0, 1e3));
|
|
12182
12639
|
}
|
|
12183
12640
|
async function finish(doFetch, cfg, token, sessionId, method) {
|
|
12184
12641
|
return doFetch(runtimeUrl(cfg, `/${encodeURIComponent(sessionId)}`), {
|
|
@@ -12202,7 +12659,7 @@ async function deliverRuntimeCredentials(cfg, options) {
|
|
|
12202
12659
|
},
|
|
12203
12660
|
body: JSON.stringify({
|
|
12204
12661
|
env: options.env,
|
|
12205
|
-
idempotencyKey: `wrangler:${(0,
|
|
12662
|
+
idempotencyKey: `wrangler:${(0, import_node_crypto5.randomUUID)()}`,
|
|
12206
12663
|
target
|
|
12207
12664
|
})
|
|
12208
12665
|
});
|
|
@@ -12581,6 +13038,7 @@ var COMMAND_SURFACE = {
|
|
|
12581
13038
|
doctor: {},
|
|
12582
13039
|
help: {},
|
|
12583
13040
|
init: {},
|
|
13041
|
+
monitor: { plan: {}, apply: {}, run: {}, status: {}, incidents: {}, report: {} },
|
|
12584
13042
|
o11y: { status: {} },
|
|
12585
13043
|
operations: { get: {}, wait: {} },
|
|
12586
13044
|
platform: {
|
|
@@ -12870,12 +13328,12 @@ async function runbookRemove(ctx, slug) {
|
|
|
12870
13328
|
// src/runbook-import.ts
|
|
12871
13329
|
var import_node_fs17 = require("fs");
|
|
12872
13330
|
var import_node_path16 = require("path");
|
|
12873
|
-
function parseRunbook(
|
|
12874
|
-
let rest =
|
|
13331
|
+
function parseRunbook(text3, slug) {
|
|
13332
|
+
let rest = text3;
|
|
12875
13333
|
const meta = {};
|
|
12876
|
-
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(
|
|
13334
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text3);
|
|
12877
13335
|
if (fm) {
|
|
12878
|
-
rest =
|
|
13336
|
+
rest = text3.slice(fm[0].length);
|
|
12879
13337
|
for (const line of fm[1].split(/\r?\n/)) {
|
|
12880
13338
|
const pair = /^(\w+)\s*:\s*(.+)$/.exec(line.trim());
|
|
12881
13339
|
if (!pair) continue;
|
|
@@ -13644,9 +14102,9 @@ function printHostedSecurityIntent(out, intent) {
|
|
|
13644
14102
|
}
|
|
13645
14103
|
function assertHostedSecurityPlanReady(plan) {
|
|
13646
14104
|
const reasons = [];
|
|
13647
|
-
for (const [label,
|
|
13648
|
-
if (!
|
|
13649
|
-
if (!
|
|
14105
|
+
for (const [label, route3] of Object.entries(plan.routes)) {
|
|
14106
|
+
if (!route3.enabled) reasons.push(`${label} is disabled`);
|
|
14107
|
+
if (!route3.credentialReady) reasons.push(`${label} provider credential is unavailable`);
|
|
13650
14108
|
}
|
|
13651
14109
|
if (!plan.independent) reasons.push("discovery and validation are not independently routed");
|
|
13652
14110
|
if (plan.ready && reasons.length === 0) return;
|
|
@@ -13700,20 +14158,20 @@ function enforceHostedReportGate(report4, parsed, out, emitSuccess) {
|
|
|
13700
14158
|
out.log(`security gate passed: 0 confirmed >= ${failOn}; 0 leads >= ${failOnCandidates ?? "disabled"}; coverage ${report4.coverageStatus}. This is not proof that the application is secure.`);
|
|
13701
14159
|
}
|
|
13702
14160
|
}
|
|
13703
|
-
function printHostedSecurityPlanRoute(out, label,
|
|
13704
|
-
const readiness =
|
|
13705
|
-
|
|
13706
|
-
|
|
14161
|
+
function printHostedSecurityPlanRoute(out, label, route3) {
|
|
14162
|
+
const readiness = route3.enabled && route3.credentialReady ? "ready" : [
|
|
14163
|
+
route3.enabled ? void 0 : "disabled",
|
|
14164
|
+
route3.credentialReady ? void 0 : "credential unavailable"
|
|
13707
14165
|
].filter(Boolean).join(", ");
|
|
13708
|
-
out.log(` ${label}: ${
|
|
13709
|
-
out.log(` bounds: ${
|
|
14166
|
+
out.log(` ${label}: ${route3.provider}/${route3.model} \xB7 policy v${route3.policyVersion} \xB7 ${readiness}`);
|
|
14167
|
+
out.log(` bounds: ${route3.maxCallsPerRun} calls/run \xB7 ${route3.maxInputBytes} input bytes/call \xB7 ${route3.maxOutputTokens} output tokens/call`);
|
|
13710
14168
|
}
|
|
13711
14169
|
function printHostedCoverage(out, job) {
|
|
13712
14170
|
const coverage = job.coverage;
|
|
13713
14171
|
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}` : ""}`);
|
|
13714
14172
|
}
|
|
13715
|
-
function routeLabel(
|
|
13716
|
-
return `${
|
|
14173
|
+
function routeLabel(route3) {
|
|
14174
|
+
return `${route3.provider}/${route3.model}${route3.policyVersion ? ` policy v${route3.policyVersion}` : ""}`;
|
|
13717
14175
|
}
|
|
13718
14176
|
var HOSTED_SEVERITIES = ["informational", "low", "medium", "high", "critical"];
|
|
13719
14177
|
function hostedSeverity(value2, flag) {
|
|
@@ -13800,11 +14258,11 @@ function selectEnv(requested, declared, configPath, rootDir) {
|
|
|
13800
14258
|
}
|
|
13801
14259
|
return env;
|
|
13802
14260
|
}
|
|
13803
|
-
async function injectedToken(options,
|
|
13804
|
-
const value2 = options.token ?? await options.getToken?.(Object.freeze({ ...
|
|
14261
|
+
async function injectedToken(options, request3) {
|
|
14262
|
+
const value2 = options.token ?? await options.getToken?.(Object.freeze({ ...request3 }));
|
|
13805
14263
|
if (typeof value2 !== "string" || value2.length < 8 || value2.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value2)) {
|
|
13806
14264
|
throw new Error(
|
|
13807
|
-
|
|
14265
|
+
request3.selfAudit ? "Self-audit requires an injected, scoped platform security token" : "Hosted security requires an injected app developer token or getToken callback"
|
|
13808
14266
|
);
|
|
13809
14267
|
}
|
|
13810
14268
|
return value2;
|
|
@@ -14117,11 +14575,11 @@ async function runLocalSecurityCommand(parsed, dependencies) {
|
|
|
14117
14575
|
sourceDisclosureAck: parsed.options["ack-redacted-source"] === true ? "redacted" : void 0,
|
|
14118
14576
|
fetch: doFetch,
|
|
14119
14577
|
stdout: out,
|
|
14120
|
-
getToken: async (
|
|
14121
|
-
if (
|
|
14578
|
+
getToken: async (request3) => {
|
|
14579
|
+
if (request3.scope === "platform:security:self") {
|
|
14122
14580
|
return getScopedPlatformToken({
|
|
14123
|
-
platform:
|
|
14124
|
-
scope:
|
|
14581
|
+
platform: request3.platform,
|
|
14582
|
+
scope: request3.scope,
|
|
14125
14583
|
email: stringOpt(parsed.options.email),
|
|
14126
14584
|
open,
|
|
14127
14585
|
fetch: doFetch,
|
|
@@ -14130,7 +14588,7 @@ async function runLocalSecurityCommand(parsed, dependencies) {
|
|
|
14130
14588
|
});
|
|
14131
14589
|
}
|
|
14132
14590
|
const cfg = await loadProjectConfig(configPath);
|
|
14133
|
-
if (platformAudience(cfg.platformUrl) !== platformAudience(
|
|
14591
|
+
if (platformAudience(cfg.platformUrl) !== platformAudience(request3.platform)) {
|
|
14134
14592
|
throw new Error("--platform cannot reuse a project developer token from another platform; update odla.config.mjs and authenticate there");
|
|
14135
14593
|
}
|
|
14136
14594
|
return getDeveloperToken(
|
|
@@ -14346,10 +14804,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
14346
14804
|
}
|
|
14347
14805
|
if (command === "bug") {
|
|
14348
14806
|
const action2 = parsed.positionals[1] ?? "list";
|
|
14349
|
-
const
|
|
14807
|
+
const canonical2 = action2 === "report" || action2 === "create" ? "add" : action2;
|
|
14350
14808
|
await pmCommand({
|
|
14351
14809
|
...parsed,
|
|
14352
|
-
positionals: ["pm", "bug",
|
|
14810
|
+
positionals: ["pm", "bug", canonical2, ...parsed.positionals.slice(2)]
|
|
14353
14811
|
}, runtime);
|
|
14354
14812
|
return;
|
|
14355
14813
|
}
|
|
@@ -14361,6 +14819,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
14361
14819
|
await o11yCommand(parsed, runtime);
|
|
14362
14820
|
return;
|
|
14363
14821
|
}
|
|
14822
|
+
if (command === "monitor") {
|
|
14823
|
+
await monitorCommand(parsed, runtime);
|
|
14824
|
+
return;
|
|
14825
|
+
}
|
|
14364
14826
|
if (command === "platform") {
|
|
14365
14827
|
await platformCommand(parsed, runtime);
|
|
14366
14828
|
return;
|
|
@@ -14479,6 +14941,8 @@ async function calendarCommand(parsed, dependencies) {
|
|
|
14479
14941
|
isTerminalHostedSecurityStatus,
|
|
14480
14942
|
listGitHubSecuritySources,
|
|
14481
14943
|
listHostedSecurityJobs,
|
|
14944
|
+
monitorCommand,
|
|
14945
|
+
monitoringWireConfig,
|
|
14482
14946
|
printCapabilities,
|
|
14483
14947
|
provision,
|
|
14484
14948
|
reconcileConfig,
|