@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/bin.cjs
CHANGED
|
@@ -440,10 +440,10 @@ function isManagedDevVar(line) {
|
|
|
440
440
|
const match = line.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/);
|
|
441
441
|
return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
|
|
442
442
|
}
|
|
443
|
-
function writePrivateText(path,
|
|
443
|
+
function writePrivateText(path, text3) {
|
|
444
444
|
(0, import_node_fs5.mkdirSync)((0, import_node_path3.dirname)(path), { recursive: true });
|
|
445
445
|
const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
446
|
-
(0, import_node_fs5.writeFileSync)(temporary,
|
|
446
|
+
(0, import_node_fs5.writeFileSync)(temporary, text3, { mode: 384 });
|
|
447
447
|
(0, import_node_fs5.chmodSync)(temporary, 384);
|
|
448
448
|
(0, import_node_fs5.renameSync)(temporary, path);
|
|
449
449
|
}
|
|
@@ -580,7 +580,7 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
580
580
|
}
|
|
581
581
|
function cachedGrantCovers(cached, required) {
|
|
582
582
|
if (required.optionalProjectCapabilities.length === 0) return true;
|
|
583
|
-
return required.projectIds.every((
|
|
583
|
+
return required.projectIds.every((id2) => cached.projectIds?.includes(id2)) && required.optionalProjectCapabilities.every(
|
|
584
584
|
(capability) => cached.optionalProjectCapabilities?.includes(capability)
|
|
585
585
|
);
|
|
586
586
|
}
|
|
@@ -769,8 +769,8 @@ var init_admin_ai_auth = __esm({
|
|
|
769
769
|
// src/principal-presentation.ts
|
|
770
770
|
function unresolvedPrincipalLabel(credentialKind2, principalId) {
|
|
771
771
|
const kind = typeof credentialKind2 === "string" ? credentialKind2.trim() : "";
|
|
772
|
-
const
|
|
773
|
-
const audit = kind &&
|
|
772
|
+
const id2 = typeof principalId === "string" ? principalId.trim() : "";
|
|
773
|
+
const audit = kind && id2 ? `${kind}:${id2}` : kind || id2;
|
|
774
774
|
return `Unknown principal${audit ? ` [${audit}]` : ""}`;
|
|
775
775
|
}
|
|
776
776
|
var init_principal_presentation = __esm({
|
|
@@ -788,38 +788,38 @@ function adminAiAuditQuery(filters) {
|
|
|
788
788
|
}
|
|
789
789
|
return `?limit=${filters.limit}`;
|
|
790
790
|
}
|
|
791
|
-
async function readAdminAiAudit(
|
|
792
|
-
const response2 = await
|
|
793
|
-
headers:
|
|
791
|
+
async function readAdminAiAudit(request3) {
|
|
792
|
+
const response2 = await request3.fetch(`${request3.platform}/registry/platform/ai-audit${request3.query}`, {
|
|
793
|
+
headers: request3.headers
|
|
794
794
|
});
|
|
795
795
|
const body = await responseBody(response2);
|
|
796
796
|
if (!response2.ok) throw new Error(apiError(response2.status, body));
|
|
797
|
-
if (
|
|
798
|
-
|
|
797
|
+
if (request3.json) {
|
|
798
|
+
request3.stdout.log(JSON.stringify(body, null, 2));
|
|
799
799
|
return;
|
|
800
800
|
}
|
|
801
801
|
const events = isRecord(body) && Array.isArray(body.events) ? body.events.filter(isRecord) : [];
|
|
802
|
-
|
|
802
|
+
request3.stdout.log("when change target before -> after actor");
|
|
803
803
|
for (const event of events) {
|
|
804
804
|
const before = isRecord(event.oldPolicy) ? event.oldPolicy : void 0;
|
|
805
805
|
const after = isRecord(event.newPolicy) ? event.newPolicy : void 0;
|
|
806
|
-
const
|
|
807
|
-
|
|
806
|
+
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";
|
|
807
|
+
request3.stdout.log([
|
|
808
808
|
timestamp(event.createdAt),
|
|
809
809
|
String(event.changeKind ?? ""),
|
|
810
810
|
String(event.purpose ?? event.provider ?? ""),
|
|
811
|
-
|
|
811
|
+
route3,
|
|
812
812
|
unresolvedPrincipalLabel(event.actorType, event.actorId)
|
|
813
813
|
].join(" "));
|
|
814
814
|
}
|
|
815
815
|
}
|
|
816
816
|
async function responseBody(response2) {
|
|
817
|
-
const
|
|
818
|
-
if (!
|
|
817
|
+
const text3 = await response2.text();
|
|
818
|
+
if (!text3) return {};
|
|
819
819
|
try {
|
|
820
|
-
return JSON.parse(
|
|
820
|
+
return JSON.parse(text3);
|
|
821
821
|
} catch {
|
|
822
|
-
return { message:
|
|
822
|
+
return { message: text3.slice(0, 300) };
|
|
823
823
|
}
|
|
824
824
|
}
|
|
825
825
|
function apiError(status, body) {
|
|
@@ -874,14 +874,14 @@ function adminAiUsageQuery(filters) {
|
|
|
874
874
|
const query = params.toString();
|
|
875
875
|
return query ? `?${query}` : "";
|
|
876
876
|
}
|
|
877
|
-
async function readAdminAiUsage(
|
|
878
|
-
const res = await
|
|
879
|
-
headers:
|
|
877
|
+
async function readAdminAiUsage(request3) {
|
|
878
|
+
const res = await request3.fetch(`${request3.platform}/registry/platform/ai-usage${request3.query}`, {
|
|
879
|
+
headers: request3.headers
|
|
880
880
|
});
|
|
881
881
|
const body = await responseBody2(res);
|
|
882
882
|
if (!res.ok) throw new Error(apiError2("read platform AI usage", res.status, body));
|
|
883
|
-
if (
|
|
884
|
-
else printUsage(body,
|
|
883
|
+
if (request3.json) request3.stdout.log(JSON.stringify(body, null, 2));
|
|
884
|
+
else printUsage(body, request3.stdout);
|
|
885
885
|
}
|
|
886
886
|
function usageLimit(value2) {
|
|
887
887
|
if (!Number.isSafeInteger(value2) || value2 < 1 || value2 > 500) {
|
|
@@ -935,12 +935,12 @@ function timestamp2(value2) {
|
|
|
935
935
|
return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
|
|
936
936
|
}
|
|
937
937
|
async function responseBody2(res) {
|
|
938
|
-
const
|
|
939
|
-
if (!
|
|
938
|
+
const text3 = await res.text();
|
|
939
|
+
if (!text3) return {};
|
|
940
940
|
try {
|
|
941
|
-
return JSON.parse(
|
|
941
|
+
return JSON.parse(text3);
|
|
942
942
|
} catch {
|
|
943
|
-
return { message:
|
|
943
|
+
return { message: text3.slice(0, 300) };
|
|
944
944
|
}
|
|
945
945
|
}
|
|
946
946
|
function apiError2(action2, status, body) {
|
|
@@ -1123,12 +1123,12 @@ function catalogModels(body) {
|
|
|
1123
1123
|
return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
|
|
1124
1124
|
}
|
|
1125
1125
|
async function responseBody3(res) {
|
|
1126
|
-
const
|
|
1127
|
-
if (!
|
|
1126
|
+
const text3 = await res.text();
|
|
1127
|
+
if (!text3) return {};
|
|
1128
1128
|
try {
|
|
1129
|
-
return JSON.parse(
|
|
1129
|
+
return JSON.parse(text3);
|
|
1130
1130
|
} catch {
|
|
1131
|
-
return { message:
|
|
1131
|
+
return { message: text3.slice(0, 300) };
|
|
1132
1132
|
}
|
|
1133
1133
|
}
|
|
1134
1134
|
function apiError3(action2, status, body) {
|
|
@@ -1276,7 +1276,7 @@ function calendarServiceConfig(cfg, env) {
|
|
|
1276
1276
|
if (!google) throw new Error("calendar.google is required when the calendar service is enabled");
|
|
1277
1277
|
const configured = google.availabilityCalendars?.[env] ?? google.calendars?.[env];
|
|
1278
1278
|
if (!configured?.length) throw new Error(`calendar.google.availabilityCalendars.${env} is required`);
|
|
1279
|
-
const availability = unique(configured.map((
|
|
1279
|
+
const availability = unique(configured.map((id2) => id2.trim()));
|
|
1280
1280
|
return {
|
|
1281
1281
|
provider: "google",
|
|
1282
1282
|
access: "book",
|
|
@@ -1325,7 +1325,7 @@ function validateCalendarConfig(cfg, envs, services, path) {
|
|
|
1325
1325
|
if (ids.length > 10) {
|
|
1326
1326
|
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must contain at most 10 calendar ids`);
|
|
1327
1327
|
}
|
|
1328
|
-
if (ids.some((
|
|
1328
|
+
if (ids.some((id2) => !safeText2(id2, 1024))) {
|
|
1329
1329
|
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} contains an invalid calendar id`);
|
|
1330
1330
|
}
|
|
1331
1331
|
}
|
|
@@ -1480,6 +1480,182 @@ var init_integration_validation = __esm({
|
|
|
1480
1480
|
}
|
|
1481
1481
|
});
|
|
1482
1482
|
|
|
1483
|
+
// src/monitoring-validation.ts
|
|
1484
|
+
function validateMonitoringConfig(cfg, envs, services, path) {
|
|
1485
|
+
if (!cfg.o11y) return;
|
|
1486
|
+
if (!record(cfg.o11y)) fail(path, "o11y must be an object");
|
|
1487
|
+
only(cfg.o11y, ["service", "endpoint", "version", "monitoring"], `${path}: o11y`);
|
|
1488
|
+
const monitoring = cfg.o11y.monitoring;
|
|
1489
|
+
if (!monitoring) return;
|
|
1490
|
+
if (!services.includes("o11y")) fail(path, 'o11y.monitoring requires "o11y" in services');
|
|
1491
|
+
if (!record(monitoring)) fail(path, "o11y.monitoring must be an object");
|
|
1492
|
+
only(monitoring, ["probes", "slos", "notifications"], `${path}: o11y.monitoring`);
|
|
1493
|
+
if (monitoring.probes !== void 0 && (!Array.isArray(monitoring.probes) || monitoring.probes.length > 50)) {
|
|
1494
|
+
fail(path, "o11y.monitoring.probes must contain at most 50 probes");
|
|
1495
|
+
}
|
|
1496
|
+
const probeIds = /* @__PURE__ */ new Set();
|
|
1497
|
+
(monitoring.probes ?? []).forEach((probe, index) => validateProbe(probe, index, envs, path, probeIds));
|
|
1498
|
+
if (!Array.isArray(monitoring.slos) || monitoring.slos.length < 1 || monitoring.slos.length > 50) {
|
|
1499
|
+
fail(path, "o11y.monitoring.slos must contain 1 through 50 SLOs");
|
|
1500
|
+
}
|
|
1501
|
+
const sloIds = /* @__PURE__ */ new Set();
|
|
1502
|
+
monitoring.slos.forEach((slo, index) => validateSlo(slo, index, path, probeIds, sloIds));
|
|
1503
|
+
if (monitoring.notifications !== void 0) validateNotifications(monitoring.notifications, envs, path);
|
|
1504
|
+
}
|
|
1505
|
+
function validateProbe(value2, index, envs, path, ids) {
|
|
1506
|
+
const label = `${path}: o11y.monitoring.probes[${index}]`;
|
|
1507
|
+
if (!record(value2)) fail(label, "must be an object");
|
|
1508
|
+
only(value2, ["id", "route", "envs", "every", "timeout", "ready", "expect", "enabled"], label);
|
|
1509
|
+
if (!id(value2.id)) fail(label, "id must be lowercase letters, numbers, and hyphens");
|
|
1510
|
+
if (ids.has(value2.id)) fail(label, `id duplicates ${value2.id}`);
|
|
1511
|
+
ids.add(value2.id);
|
|
1512
|
+
if (!route(value2.route)) fail(label, "route must be a relative absolute path without credentials or a fragment");
|
|
1513
|
+
if (!CADENCES.has(String(value2.every))) fail(label, `every must be one of ${[...CADENCES].join(", ")}`);
|
|
1514
|
+
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");
|
|
1515
|
+
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");
|
|
1516
|
+
if (value2.ready !== void 0) {
|
|
1517
|
+
if (!record(value2.ready) || !text(value2.ready.selector, 300)) fail(label, "ready.selector is required");
|
|
1518
|
+
only(value2.ready, ["selector"], `${label}.ready`);
|
|
1519
|
+
}
|
|
1520
|
+
if (!record(value2.expect)) fail(label, "expect must be an object");
|
|
1521
|
+
only(value2.expect, ["status", "titleIncludes", "textIncludes", "accessibility"], `${label}.expect`);
|
|
1522
|
+
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");
|
|
1523
|
+
if (value2.expect.titleIncludes !== void 0 && !text(value2.expect.titleIncludes, 300)) fail(label, "expect.titleIncludes is invalid");
|
|
1524
|
+
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");
|
|
1525
|
+
if (value2.expect.accessibility !== void 0) validateAccessibility(value2.expect.accessibility, label);
|
|
1526
|
+
}
|
|
1527
|
+
function validateAccessibility(value2, label) {
|
|
1528
|
+
if (!Array.isArray(value2) || value2.length > 10) fail(label, "expect.accessibility must contain at most 10 assertions");
|
|
1529
|
+
for (const item of value2) {
|
|
1530
|
+
if (!record(item) || !text(item.role, 80) || !text(item.name, 300)) fail(label, "expect.accessibility entries need role and name");
|
|
1531
|
+
only(item, ["role", "name"], `${label}.expect.accessibility`);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
function validateSlo(value2, index, path, probes, ids) {
|
|
1535
|
+
const label = `${path}: o11y.monitoring.slos[${index}]`;
|
|
1536
|
+
if (!record(value2)) fail(label, "must be an object");
|
|
1537
|
+
only(value2, ["id", "name", "indicator", "target", "window", "alerts", "enabled"], label);
|
|
1538
|
+
if (!id(value2.id) || ids.has(value2.id)) fail(label, "id must be unique lowercase letters, numbers, and hyphens");
|
|
1539
|
+
ids.add(value2.id);
|
|
1540
|
+
if (value2.name !== void 0 && !text(value2.name, 160)) fail(label, "name is invalid");
|
|
1541
|
+
validateIndicator(value2.indicator, label, probes);
|
|
1542
|
+
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");
|
|
1543
|
+
if (!WINDOWS.has(String(value2.window))) fail(label, `window must be one of ${[...WINDOWS].join(", ")}`);
|
|
1544
|
+
if (value2.alerts !== void 0) validateAlerts(value2.alerts, label);
|
|
1545
|
+
}
|
|
1546
|
+
function validateIndicator(value2, label, probes) {
|
|
1547
|
+
if (!record(value2)) fail(label, "indicator must be an object");
|
|
1548
|
+
if (value2.type === "probe-success") {
|
|
1549
|
+
only(value2, ["type", "probes"], `${label}.indicator`);
|
|
1550
|
+
if (!Array.isArray(value2.probes) || value2.probes.length < 1) fail(label, "probe-success must select at least one probe");
|
|
1551
|
+
if (value2.probes.some((probe) => typeof probe !== "string" || !probes.has(probe))) fail(label, "indicator references an unknown probe");
|
|
1552
|
+
return;
|
|
1553
|
+
}
|
|
1554
|
+
if (value2.type !== "o11y-metric") fail(label, "indicator.type must be probe-success or o11y-metric");
|
|
1555
|
+
only(value2, ["type", "metric", "comparator", "threshold", "every", "observationWindow", "route"], `${label}.indicator`);
|
|
1556
|
+
if (!O11Y_METRICS.has(String(value2.metric))) fail(label, "indicator.metric is unsupported");
|
|
1557
|
+
if (!COMPARATORS.has(String(value2.comparator))) fail(label, "indicator.comparator is unsupported");
|
|
1558
|
+
if (typeof value2.threshold !== "number" || !Number.isFinite(value2.threshold)) fail(label, "indicator.threshold must be finite");
|
|
1559
|
+
if (!CADENCES.has(String(value2.every)) || !CADENCES.has(String(value2.observationWindow))) fail(label, "indicator cadence and observationWindow must be supported durations");
|
|
1560
|
+
if (value2.route !== void 0 && !routePattern(value2.route)) fail(label, "indicator.route must be an exact route template or trailing-* prefix");
|
|
1561
|
+
if ((value2.metric === "synthetic_success" || value2.metric === "synthetic_publish_to_visible") && value2.route !== void 0) fail(label, "synthetic indicators cannot select a route");
|
|
1562
|
+
}
|
|
1563
|
+
function validateAlerts(value2, label) {
|
|
1564
|
+
if (!record(value2)) fail(label, "alerts must be an object");
|
|
1565
|
+
only(value2, ["spike", "trend"], `${label}.alerts`);
|
|
1566
|
+
if (value2.spike !== void 0) {
|
|
1567
|
+
if (!record(value2.spike)) fail(label, "alerts.spike must be an object");
|
|
1568
|
+
only(value2.spike, ["badChecks", "withinChecks", "recoverAfter"], `${label}.alerts.spike`);
|
|
1569
|
+
const bad = positive(value2.spike.badChecks, 2), within = positive(value2.spike.withinChecks, 3), recover = positive(value2.spike.recoverAfter, 2);
|
|
1570
|
+
if (bad > within || within > 20 || recover > 20) fail(label, "alerts.spike requires badChecks <= withinChecks <= 20 and recoverAfter <= 20");
|
|
1571
|
+
}
|
|
1572
|
+
if (value2.trend !== void 0) {
|
|
1573
|
+
if (!record(value2.trend)) fail(label, "alerts.trend must be an object");
|
|
1574
|
+
only(value2.trend, ["burnRate", "shortWindow", "longWindow", "minBadChecks"], `${label}.alerts.trend`);
|
|
1575
|
+
const burn = value2.trend.burnRate ?? 1;
|
|
1576
|
+
if (typeof burn !== "number" || !Number.isFinite(burn) || burn <= 0 || burn > 1e3) fail(label, "alerts.trend.burnRate must be greater than 0");
|
|
1577
|
+
if (value2.trend.shortWindow !== void 0 && !SHORT.has(String(value2.trend.shortWindow))) fail(label, "alerts.trend.shortWindow is unsupported");
|
|
1578
|
+
if (value2.trend.longWindow !== void 0 && !LONG.has(String(value2.trend.longWindow))) fail(label, "alerts.trend.longWindow is unsupported");
|
|
1579
|
+
if (positive(value2.trend.minBadChecks, 2) > 100) fail(label, "alerts.trend.minBadChecks must be at most 100");
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
function validateNotifications(value2, envs, path) {
|
|
1583
|
+
if (!record(value2)) fail(path, "o11y.monitoring.notifications must map environments to policies");
|
|
1584
|
+
for (const [env, policy] of Object.entries(value2)) {
|
|
1585
|
+
const label = `${path}: o11y.monitoring.notifications.${env}`;
|
|
1586
|
+
if (!envs.includes(env) && env !== "prod") fail(label, "is not a configured environment");
|
|
1587
|
+
if (!record(policy)) fail(label, "must be an object");
|
|
1588
|
+
only(policy, ["email", "timezone", "daily", "weekly"], label);
|
|
1589
|
+
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");
|
|
1590
|
+
if (!timezone(policy.timezone)) fail(label, "timezone must be an IANA timezone");
|
|
1591
|
+
if (policy.daily !== void 0 && policy.daily !== false && !clock(policy.daily)) fail(label, "daily must be HH:MM or false");
|
|
1592
|
+
if (policy.weekly !== void 0 && policy.weekly !== false) {
|
|
1593
|
+
if (!record(policy.weekly) || !DAYS.has(String(policy.weekly.day)) || !clock(policy.weekly.at)) fail(label, "weekly needs a weekday and HH:MM time");
|
|
1594
|
+
only(policy.weekly, ["day", "at"], `${label}.weekly`);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
function fail(label, message2) {
|
|
1599
|
+
throw new Error(`${label}: ${message2}`);
|
|
1600
|
+
}
|
|
1601
|
+
function record(value2) {
|
|
1602
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
1603
|
+
}
|
|
1604
|
+
function only(value2, keys, label) {
|
|
1605
|
+
const extra = Object.keys(value2).find((key) => !keys.includes(key));
|
|
1606
|
+
if (extra) fail(label, `${extra} is not supported`);
|
|
1607
|
+
}
|
|
1608
|
+
function id(value2) {
|
|
1609
|
+
return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
|
|
1610
|
+
}
|
|
1611
|
+
function text(value2, max) {
|
|
1612
|
+
return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
|
|
1613
|
+
}
|
|
1614
|
+
function positive(value2, fallback) {
|
|
1615
|
+
return value2 === void 0 ? fallback : Number.isSafeInteger(value2) && Number(value2) > 0 ? Number(value2) : Infinity;
|
|
1616
|
+
}
|
|
1617
|
+
function route(value2) {
|
|
1618
|
+
if (typeof value2 !== "string" || value2.length > 2048 || !value2.startsWith("/") || value2.startsWith("//")) return false;
|
|
1619
|
+
try {
|
|
1620
|
+
const url = new URL(value2, "https://probe.invalid");
|
|
1621
|
+
return url.origin === "https://probe.invalid" && !url.hash;
|
|
1622
|
+
} catch {
|
|
1623
|
+
return false;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
function routePattern(value2) {
|
|
1627
|
+
return typeof value2 === "string" && value2.length <= 160 && /^\/[A-Za-z0-9_./:-]+\*?$/.test(value2) && !value2.slice(0, -1).includes("*");
|
|
1628
|
+
}
|
|
1629
|
+
function emailAddress(value2) {
|
|
1630
|
+
return typeof value2 === "string" && value2.length <= 254 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value2);
|
|
1631
|
+
}
|
|
1632
|
+
function clock(value2) {
|
|
1633
|
+
return typeof value2 === "string" && /^(?:[01]\d|2[0-3]):[0-5]\d$/.test(value2);
|
|
1634
|
+
}
|
|
1635
|
+
function timezone(value2) {
|
|
1636
|
+
if (typeof value2 !== "string" || value2.length > 100) return false;
|
|
1637
|
+
try {
|
|
1638
|
+
new Intl.DateTimeFormat("en", { timeZone: value2 }).format(0);
|
|
1639
|
+
return true;
|
|
1640
|
+
} catch {
|
|
1641
|
+
return false;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
var CADENCES, WINDOWS, SHORT, LONG, DAYS, O11Y_METRICS, COMPARATORS;
|
|
1645
|
+
var init_monitoring_validation = __esm({
|
|
1646
|
+
"src/monitoring-validation.ts"() {
|
|
1647
|
+
"use strict";
|
|
1648
|
+
init_cjs_shims();
|
|
1649
|
+
CADENCES = /* @__PURE__ */ new Set(["1m", "2m", "5m", "10m", "15m", "30m", "1h"]);
|
|
1650
|
+
WINDOWS = /* @__PURE__ */ new Set(["7d", "28d", "30d"]);
|
|
1651
|
+
SHORT = /* @__PURE__ */ new Set(["30m", "1h", "6h", "12h", "1d"]);
|
|
1652
|
+
LONG = /* @__PURE__ */ new Set(["1d", "3d", "7d"]);
|
|
1653
|
+
DAYS = /* @__PURE__ */ new Set(["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]);
|
|
1654
|
+
O11Y_METRICS = /* @__PURE__ */ new Set(["error_rate", "latency_p95", "synthetic_success", "synthetic_publish_to_visible"]);
|
|
1655
|
+
COMPARATORS = /* @__PURE__ */ new Set(["gt", "gte", "lt", "lte"]);
|
|
1656
|
+
}
|
|
1657
|
+
});
|
|
1658
|
+
|
|
1483
1659
|
// src/config.ts
|
|
1484
1660
|
async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
|
|
1485
1661
|
const resolved = (0, import_node_path5.resolve)(configPath);
|
|
@@ -1495,6 +1671,7 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
|
|
|
1495
1671
|
const services = unique3(raw.services?.length ? raw.services : DEFAULT_SERVICES);
|
|
1496
1672
|
validateServices(services, resolved);
|
|
1497
1673
|
validateCalendarConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
|
|
1674
|
+
validateMonitoringConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
|
|
1498
1675
|
const local = {
|
|
1499
1676
|
tokenFile: (0, import_node_path5.resolve)(rootDir, raw.local?.tokenFile ?? ".odla/dev-token.json"),
|
|
1500
1677
|
credentialsFile: (0, import_node_path5.resolve)(rootDir, raw.local?.credentialsFile ?? ".odla/credentials.local.json"),
|
|
@@ -1618,6 +1795,7 @@ var init_config = __esm({
|
|
|
1618
1795
|
init_ai_config_validation();
|
|
1619
1796
|
init_calendar_config();
|
|
1620
1797
|
init_integration_validation();
|
|
1798
|
+
init_monitoring_validation();
|
|
1621
1799
|
init_calendar_config();
|
|
1622
1800
|
DEFAULT_PLATFORM = "https://odla.ai";
|
|
1623
1801
|
DEFAULT_ENVS = ["dev"];
|
|
@@ -1959,12 +2137,12 @@ function credentialKind(value2, machine, scopes) {
|
|
|
1959
2137
|
function managerOf(value2) {
|
|
1960
2138
|
if (!value2 || typeof value2 !== "object") return null;
|
|
1961
2139
|
const row = value2;
|
|
1962
|
-
const principalId =
|
|
2140
|
+
const principalId = text2(row.principalId);
|
|
1963
2141
|
if (!principalId) return null;
|
|
1964
2142
|
return {
|
|
1965
2143
|
principalId,
|
|
1966
|
-
displayName:
|
|
1967
|
-
handle:
|
|
2144
|
+
displayName: text2(row.displayName) ?? "Unnamed member",
|
|
2145
|
+
handle: text2(row.handle) ?? ""
|
|
1968
2146
|
};
|
|
1969
2147
|
}
|
|
1970
2148
|
function unnamedPrincipal(kind) {
|
|
@@ -1978,14 +2156,14 @@ async function fetchIdentity(platformUrl, token, doFetch) {
|
|
|
1978
2156
|
});
|
|
1979
2157
|
if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
|
|
1980
2158
|
const body = await res.json();
|
|
1981
|
-
const developerId =
|
|
2159
|
+
const developerId = text2(body.developerId) ?? "";
|
|
1982
2160
|
const machine = body.machine === true;
|
|
1983
2161
|
const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
|
|
1984
|
-
const principalId =
|
|
1985
|
-
const email =
|
|
2162
|
+
const principalId = text2(body.principalId) ?? developerId;
|
|
2163
|
+
const email = text2(body.email);
|
|
1986
2164
|
const kind = principalKind(body.principalKind, machine);
|
|
1987
|
-
const displayName =
|
|
1988
|
-
const handle =
|
|
2165
|
+
const displayName = text2(body.displayName) ?? email ?? unnamedPrincipal(kind);
|
|
2166
|
+
const handle = text2(body.handle) ?? "";
|
|
1989
2167
|
const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
|
|
1990
2168
|
return {
|
|
1991
2169
|
developerId,
|
|
@@ -1995,7 +2173,7 @@ async function fetchIdentity(platformUrl, token, doFetch) {
|
|
|
1995
2173
|
handle,
|
|
1996
2174
|
manager: managerOf(body.manager),
|
|
1997
2175
|
credential: {
|
|
1998
|
-
id:
|
|
2176
|
+
id: text2(credential2.id),
|
|
1999
2177
|
kind: credentialKind(credential2.kind, machine, scopes)
|
|
2000
2178
|
},
|
|
2001
2179
|
email,
|
|
@@ -2081,7 +2259,7 @@ async function whoamiCommand(parsed, deps = {}) {
|
|
|
2081
2259
|
}
|
|
2082
2260
|
}
|
|
2083
2261
|
}
|
|
2084
|
-
var
|
|
2262
|
+
var text2;
|
|
2085
2263
|
var init_whoami_command = __esm({
|
|
2086
2264
|
"src/whoami-command.ts"() {
|
|
2087
2265
|
"use strict";
|
|
@@ -2089,7 +2267,7 @@ var init_whoami_command = __esm({
|
|
|
2089
2267
|
init_argv();
|
|
2090
2268
|
init_operator_context();
|
|
2091
2269
|
init_token();
|
|
2092
|
-
|
|
2270
|
+
text2 = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
2093
2271
|
}
|
|
2094
2272
|
});
|
|
2095
2273
|
|
|
@@ -2220,13 +2398,13 @@ async function agentCommand(parsed, deps = {}) {
|
|
|
2220
2398
|
const base = `${cfg.dbEndpoint}/app/${encodeURIComponent(tenant)}/admin/agent-jobs`;
|
|
2221
2399
|
const headers = { authorization: `Bearer ${credential2}` };
|
|
2222
2400
|
if (action2 === "retry") {
|
|
2223
|
-
const
|
|
2224
|
-
const res2 = await doFetch(`${base}/${encodeURIComponent(
|
|
2401
|
+
const id2 = parsed.positionals[2];
|
|
2402
|
+
const res2 = await doFetch(`${base}/${encodeURIComponent(id2)}/retry`, { method: "POST", headers });
|
|
2225
2403
|
const body2 = await readJson(res2);
|
|
2226
2404
|
if (!res2.ok) throw new Error(`agent retry failed (${res2.status}): ${errorMessage(body2)}`);
|
|
2227
2405
|
const result2 = { v: 1, appId: cfg.app.id, env, tenant, ...body2 };
|
|
2228
2406
|
if (parsed.options.json === true) out.log(JSON.stringify(result2, null, 2));
|
|
2229
|
-
else out.log(`${tenant}: requeued ${
|
|
2407
|
+
else out.log(`${tenant}: requeued ${id2}`);
|
|
2230
2408
|
return;
|
|
2231
2409
|
}
|
|
2232
2410
|
const state2 = stringOpt(parsed.options.state);
|
|
@@ -2328,8 +2506,8 @@ async function appImport(options) {
|
|
|
2328
2506
|
const out = options.stdout ?? console;
|
|
2329
2507
|
const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
|
|
2330
2508
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
2331
|
-
const
|
|
2332
|
-
const { format, sources } = (0, import_import.parseImport)(
|
|
2509
|
+
const text3 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs10.readFileSync)(0, "utf8")))() : (0, import_node_fs10.readFileSync)(options.file, "utf8");
|
|
2510
|
+
const { format, sources } = (0, import_import.parseImport)(text3, options.ns);
|
|
2333
2511
|
if (format === "namespace-map" && options.ns) {
|
|
2334
2512
|
throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
|
|
2335
2513
|
}
|
|
@@ -2666,7 +2844,7 @@ var init_brand_design_unpack = __esm({
|
|
|
2666
2844
|
"text/html": "html",
|
|
2667
2845
|
"application/json": "json"
|
|
2668
2846
|
};
|
|
2669
|
-
encode = (
|
|
2847
|
+
encode = (text3) => new TextEncoder().encode(text3);
|
|
2670
2848
|
}
|
|
2671
2849
|
});
|
|
2672
2850
|
|
|
@@ -2765,18 +2943,18 @@ async function readCalendarStatus(ctx) {
|
|
|
2765
2943
|
}
|
|
2766
2944
|
async function discoverGoogleCalendars(ctx) {
|
|
2767
2945
|
const raw = await calendarJson(ctx, "/calendars", {});
|
|
2768
|
-
const value2 =
|
|
2946
|
+
const value2 = record2(raw);
|
|
2769
2947
|
if (!value2 || !Array.isArray(value2.calendars)) throw new Error("calendar discovery returned an invalid response");
|
|
2770
2948
|
return value2.calendars.map((item, index) => {
|
|
2771
|
-
const calendar =
|
|
2772
|
-
const
|
|
2773
|
-
if (!calendar || !
|
|
2949
|
+
const calendar = record2(item);
|
|
2950
|
+
const id2 = textField(calendar?.id, 1024);
|
|
2951
|
+
if (!calendar || !id2) throw new Error(`calendar discovery returned an invalid calendar at index ${index}`);
|
|
2774
2952
|
const role = calendar.accessRole;
|
|
2775
2953
|
if (role !== void 0 && role !== "freeBusyReader" && role !== "reader" && role !== "writer" && role !== "owner") {
|
|
2776
2954
|
throw new Error(`calendar discovery returned an invalid access role at index ${index}`);
|
|
2777
2955
|
}
|
|
2778
2956
|
return {
|
|
2779
|
-
id,
|
|
2957
|
+
id: id2,
|
|
2780
2958
|
...optionalText("summary", calendar.summary, 500),
|
|
2781
2959
|
...typeof calendar.primary === "boolean" ? { primary: calendar.primary } : {},
|
|
2782
2960
|
...typeof calendar.selected === "boolean" ? { selected: calendar.selected } : {},
|
|
@@ -2804,10 +2982,10 @@ async function pollCalendarConnection(ctx, attemptId) {
|
|
|
2804
2982
|
}
|
|
2805
2983
|
function parseCalendarStatus(raw, env) {
|
|
2806
2984
|
const outer = wrapped(raw, "calendar");
|
|
2807
|
-
const value2 =
|
|
2808
|
-
const connection =
|
|
2809
|
-
const config =
|
|
2810
|
-
const googleConfig =
|
|
2985
|
+
const value2 = record2(outer.attempt) ?? record2(outer.status) ?? outer;
|
|
2986
|
+
const connection = record2(value2.connection) ?? {};
|
|
2987
|
+
const config = record2(value2.config) ?? record2(outer.config) ?? {};
|
|
2988
|
+
const googleConfig = record2(config.google) ?? config;
|
|
2811
2989
|
const stateValue = calendarState(value2.status ?? value2.state ?? connection.status ?? connection.state);
|
|
2812
2990
|
if (!stateValue) {
|
|
2813
2991
|
throw new Error("calendar status returned an invalid connection state");
|
|
@@ -2820,7 +2998,7 @@ function parseCalendarStatus(raw, env) {
|
|
|
2820
2998
|
if (accessValue !== void 0 && accessValue !== "book" && accessValue !== "read") {
|
|
2821
2999
|
throw new Error("calendar status returned unsupported access");
|
|
2822
3000
|
}
|
|
2823
|
-
const errorValue =
|
|
3001
|
+
const errorValue = record2(value2.error) ?? record2(connection.error);
|
|
2824
3002
|
const errorCode2 = textField(value2.lastErrorCode, 128);
|
|
2825
3003
|
const bookingPageValue = Object.hasOwn(value2, "bookingPageUrl") ? value2.bookingPageUrl : Object.hasOwn(config, "bookingPageUrl") ? config.bookingPageUrl : googleConfig.bookingPageUrl;
|
|
2826
3004
|
const connected = typeof (value2.connected ?? connection.connected) === "boolean" ? Boolean(value2.connected ?? connection.connected) : ["healthy", "degraded"].includes(stateValue);
|
|
@@ -2890,11 +3068,11 @@ async function calendarJson(ctx, suffix, init) {
|
|
|
2890
3068
|
return body;
|
|
2891
3069
|
}
|
|
2892
3070
|
function wrapped(raw, key) {
|
|
2893
|
-
const outer =
|
|
3071
|
+
const outer = record2(raw);
|
|
2894
3072
|
if (!outer) throw new Error("calendar returned an invalid response");
|
|
2895
|
-
return
|
|
3073
|
+
return record2(outer[key]) ?? outer;
|
|
2896
3074
|
}
|
|
2897
|
-
function
|
|
3075
|
+
function record2(value2) {
|
|
2898
3076
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
2899
3077
|
}
|
|
2900
3078
|
function textField(value2, max) {
|
|
@@ -2907,9 +3085,9 @@ function calendarIds(value2) {
|
|
|
2907
3085
|
if (!Array.isArray(value2)) return [];
|
|
2908
3086
|
return [...new Set(value2.flatMap((item) => {
|
|
2909
3087
|
if (typeof item === "string") return textField(item, 4096) ? [item] : [];
|
|
2910
|
-
const calendar =
|
|
2911
|
-
const
|
|
2912
|
-
return
|
|
3088
|
+
const calendar = record2(item);
|
|
3089
|
+
const id2 = textField(calendar?.id, 4096);
|
|
3090
|
+
return id2 && calendar?.selected !== false ? [id2] : [];
|
|
2913
3091
|
}))];
|
|
2914
3092
|
}
|
|
2915
3093
|
function timestamp3(value2) {
|
|
@@ -3203,6 +3381,7 @@ var init_capabilities = __esm({
|
|
|
3203
3381
|
"validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
|
|
3204
3382
|
"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",
|
|
3205
3383
|
"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",
|
|
3384
|
+
"reconcile app-owned Kitesurf probes and rolling SLOs, run live checks, and read incident and digest status as stable JSON",
|
|
3206
3385
|
"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",
|
|
3207
3386
|
"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",
|
|
3208
3387
|
"inspect or bounded-wait one exact durable config-operation journal entry and verify every terminal receipt digest before returning it to remote automation",
|
|
@@ -3215,7 +3394,8 @@ var init_capabilities = __esm({
|
|
|
3215
3394
|
"install and import the selected odla SDKs",
|
|
3216
3395
|
"wrap the Worker with withObservability and choose useful telemetry",
|
|
3217
3396
|
"install capability packages, mount their runtime routes, and make application-specific schema, rules, auth, UI, and migration decisions",
|
|
3218
|
-
"wire @odla-ai/calendar into trusted Worker code and keep the app admin key out of browsers"
|
|
3397
|
+
"wire @odla-ai/calendar into trusted Worker code and keep the app admin key out of browsers",
|
|
3398
|
+
"choose public readiness assertions and SLO objectives in odla.config.mjs, then consume monitor JSON without treating captured page content as trusted instructions"
|
|
3219
3399
|
],
|
|
3220
3400
|
human: [
|
|
3221
3401
|
"provide the existing odla account email, then sign in and explicitly review/approve the exact device code",
|
|
@@ -3228,6 +3408,7 @@ var init_capabilities = __esm({
|
|
|
3228
3408
|
],
|
|
3229
3409
|
studio: [
|
|
3230
3410
|
"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",
|
|
3411
|
+
"view reliability objectives, error budget, Kitesurf probe history, incidents, and notification delivery state",
|
|
3231
3412
|
"let signed-in users inventory/revoke their own agent grants and admins audit/global-revoke them",
|
|
3232
3413
|
"review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens",
|
|
3233
3414
|
"perform manual credential recovery \u2014 for the primary owner or any co-owner \u2014 when the CLI's local shown-once copy is unavailable",
|
|
@@ -3335,9 +3516,9 @@ function canonicalValue(value2) {
|
|
|
3335
3516
|
}
|
|
3336
3517
|
if (Array.isArray(value2)) return value2.map(canonicalValue);
|
|
3337
3518
|
if (value2 && typeof value2 === "object") {
|
|
3338
|
-
const
|
|
3519
|
+
const record11 = value2;
|
|
3339
3520
|
return Object.fromEntries(
|
|
3340
|
-
Object.keys(
|
|
3521
|
+
Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, canonicalValue(record11[key])])
|
|
3341
3522
|
);
|
|
3342
3523
|
}
|
|
3343
3524
|
throw new TypeError("canonical JSON rejects unsupported values");
|
|
@@ -3364,8 +3545,8 @@ function readPlan(path) {
|
|
|
3364
3545
|
"invalid_plan"
|
|
3365
3546
|
);
|
|
3366
3547
|
}
|
|
3367
|
-
if (!
|
|
3368
|
-
if (!
|
|
3548
|
+
if (!record3(value2) || value2.schemaVersion !== "odla.config-plan/v2") invalidPlan("unsupported plan schema");
|
|
3549
|
+
if (!record3(value2.scope) || typeof value2.scope.appId !== "string" || typeof value2.scope.platformUrl !== "string") {
|
|
3369
3550
|
invalidPlan("plan scope is invalid");
|
|
3370
3551
|
}
|
|
3371
3552
|
if (!DIGEST.test(String(value2.desiredRevision)) || !DIGEST.test(String(value2.observedRevision))) {
|
|
@@ -3415,10 +3596,10 @@ function assertOperationId(value2) {
|
|
|
3415
3596
|
function assertActions(actions) {
|
|
3416
3597
|
const ids = /* @__PURE__ */ new Set();
|
|
3417
3598
|
for (const action2 of actions) {
|
|
3418
|
-
if (!
|
|
3419
|
-
const
|
|
3420
|
-
if (!ACTION_ID.test(
|
|
3421
|
-
ids.add(
|
|
3599
|
+
if (!record3(action2)) invalidPlan("every plan action must be an object");
|
|
3600
|
+
const id2 = String(action2.id ?? "");
|
|
3601
|
+
if (!ACTION_ID.test(id2) || ids.has(id2)) invalidPlan("plan action ids must be unique frozen ids");
|
|
3602
|
+
ids.add(id2);
|
|
3422
3603
|
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))) {
|
|
3423
3604
|
invalidPlan("plan action metadata is invalid");
|
|
3424
3605
|
}
|
|
@@ -3444,14 +3625,14 @@ function assertConditionalAction(action2) {
|
|
|
3444
3625
|
if (action2.path !== (action2.kind === "configure_service" ? `${base}.config` : base)) {
|
|
3445
3626
|
invalidPlan("service action path is invalid");
|
|
3446
3627
|
}
|
|
3447
|
-
if (action2.applySupport !== "provision" || !
|
|
3628
|
+
if (action2.applySupport !== "provision" || !record3(action2.after)) {
|
|
3448
3629
|
invalidPlan("service action payload is invalid");
|
|
3449
3630
|
}
|
|
3450
3631
|
if (action2.kind === "enable_service") {
|
|
3451
|
-
if (action2.after.enabled !== true || action2.before !== null && !
|
|
3632
|
+
if (action2.after.enabled !== true || action2.before !== null && !record3(action2.before)) {
|
|
3452
3633
|
invalidPlan("service enable action is invalid");
|
|
3453
3634
|
}
|
|
3454
|
-
} else if (!
|
|
3635
|
+
} else if (!record3(action2.before)) {
|
|
3455
3636
|
invalidPlan("service configure action is invalid");
|
|
3456
3637
|
}
|
|
3457
3638
|
}
|
|
@@ -3468,7 +3649,7 @@ function linkState(value2) {
|
|
|
3468
3649
|
function invalidPlan(message2) {
|
|
3469
3650
|
throw new ConfigOperationCommandError(message2, "invalid_plan");
|
|
3470
3651
|
}
|
|
3471
|
-
function
|
|
3652
|
+
function record3(value2) {
|
|
3472
3653
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
3473
3654
|
}
|
|
3474
3655
|
var import_apps3, import_node_fs11, DIGEST, REVISION, OPERATION_ID, ACTION_ID, ENV, SERVICE;
|
|
@@ -3519,9 +3700,9 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
3519
3700
|
}
|
|
3520
3701
|
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
|
|
3521
3702
|
}
|
|
3522
|
-
function errorCode(
|
|
3703
|
+
function errorCode(text3) {
|
|
3523
3704
|
try {
|
|
3524
|
-
const body = JSON.parse(
|
|
3705
|
+
const body = JSON.parse(text3);
|
|
3525
3706
|
return typeof body.error?.code === "string" ? body.error.code : null;
|
|
3526
3707
|
} catch {
|
|
3527
3708
|
return null;
|
|
@@ -3689,7 +3870,7 @@ async function configApply(options) {
|
|
|
3689
3870
|
throw new ConfigOperationCommandError("--idempotency-key is not a safe 1-120 character key", "invalid_plan");
|
|
3690
3871
|
}
|
|
3691
3872
|
const client = await operationClient(cfg, options, "apply");
|
|
3692
|
-
const
|
|
3873
|
+
const request3 = {
|
|
3693
3874
|
schemaVersion: "odla.config-operation-request/v1",
|
|
3694
3875
|
expectedRevision: plan.registryRevision,
|
|
3695
3876
|
desiredRevision: plan.desiredRevision,
|
|
@@ -3701,7 +3882,7 @@ async function configApply(options) {
|
|
|
3701
3882
|
};
|
|
3702
3883
|
let receipt;
|
|
3703
3884
|
try {
|
|
3704
|
-
receipt = await client.applyConfigOperation(cfg.app.id,
|
|
3885
|
+
receipt = await client.applyConfigOperation(cfg.app.id, request3);
|
|
3705
3886
|
} catch (error) {
|
|
3706
3887
|
const retained = retainedReceipt(error);
|
|
3707
3888
|
if (retained) {
|
|
@@ -3800,8 +3981,8 @@ function failureForReceipt(receipt) {
|
|
|
3800
3981
|
return new ConfigOperationCommandError(receipt.error?.message ?? `config operation ${receipt.state}`, "config_operation_failed");
|
|
3801
3982
|
}
|
|
3802
3983
|
function retainedReceipt(error) {
|
|
3803
|
-
if (!(error instanceof import_apps6.AppsError) || !
|
|
3804
|
-
return
|
|
3984
|
+
if (!(error instanceof import_apps6.AppsError) || !record4(error.details)) return null;
|
|
3985
|
+
return record4(error.details.operation) ? error.details.operation : null;
|
|
3805
3986
|
}
|
|
3806
3987
|
function normalizeRequestError(error) {
|
|
3807
3988
|
if (!(error instanceof import_apps6.AppsError)) return error instanceof Error ? error : new Error(String(error));
|
|
@@ -3814,7 +3995,7 @@ function normalizeRequestError(error) {
|
|
|
3814
3995
|
}
|
|
3815
3996
|
return new ConfigOperationCommandError(error.message, error.code || "config_operation_failed");
|
|
3816
3997
|
}
|
|
3817
|
-
function
|
|
3998
|
+
function record4(value2) {
|
|
3818
3999
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
3819
4000
|
}
|
|
3820
4001
|
var import_apps6, import_node_path9, IDEMPOTENCY_KEY, DEFAULT_WAIT_SECONDS, DEFAULT_INTERVAL_SECONDS;
|
|
@@ -4307,15 +4488,15 @@ function readWranglerConfig(path) {
|
|
|
4307
4488
|
return null;
|
|
4308
4489
|
}
|
|
4309
4490
|
}
|
|
4310
|
-
function stripJsonComments(
|
|
4491
|
+
function stripJsonComments(text3) {
|
|
4311
4492
|
let result = "";
|
|
4312
4493
|
let inString = false;
|
|
4313
|
-
for (let i = 0; i <
|
|
4314
|
-
const ch =
|
|
4494
|
+
for (let i = 0; i < text3.length; i++) {
|
|
4495
|
+
const ch = text3[i];
|
|
4315
4496
|
if (inString) {
|
|
4316
4497
|
result += ch;
|
|
4317
4498
|
if (ch === "\\") {
|
|
4318
|
-
result +=
|
|
4499
|
+
result += text3[i + 1] ?? "";
|
|
4319
4500
|
i++;
|
|
4320
4501
|
} else if (ch === '"') {
|
|
4321
4502
|
inString = false;
|
|
@@ -4327,14 +4508,14 @@ function stripJsonComments(text2) {
|
|
|
4327
4508
|
result += ch;
|
|
4328
4509
|
continue;
|
|
4329
4510
|
}
|
|
4330
|
-
if (ch === "/" &&
|
|
4331
|
-
while (i <
|
|
4511
|
+
if (ch === "/" && text3[i + 1] === "/") {
|
|
4512
|
+
while (i < text3.length && text3[i] !== "\n") i++;
|
|
4332
4513
|
result += "\n";
|
|
4333
4514
|
continue;
|
|
4334
4515
|
}
|
|
4335
|
-
if (ch === "/" &&
|
|
4516
|
+
if (ch === "/" && text3[i + 1] === "*") {
|
|
4336
4517
|
i += 2;
|
|
4337
|
-
while (i <
|
|
4518
|
+
while (i < text3.length && !(text3[i] === "*" && text3[i + 1] === "/")) i++;
|
|
4338
4519
|
i++;
|
|
4339
4520
|
continue;
|
|
4340
4521
|
}
|
|
@@ -4373,7 +4554,7 @@ async function wranglerRuntimeTarget(run, opts) {
|
|
|
4373
4554
|
throw new Error(`wrangler is not logged in \u2014 run "wrangler login" (a browser step for the human)`);
|
|
4374
4555
|
}
|
|
4375
4556
|
const discovered = [...new Set(`${whoami.stdout}
|
|
4376
|
-
${whoami.stderr}`.match(/\b[a-f0-9]{32}\b/gi)?.map((
|
|
4557
|
+
${whoami.stderr}`.match(/\b[a-f0-9]{32}\b/gi)?.map((id2) => id2.toLowerCase()) ?? [])];
|
|
4377
4558
|
const accountId = configuredAccount.toLowerCase() || (discovered.length === 1 ? discovered[0] : "");
|
|
4378
4559
|
if (!/^[a-f0-9]{32}$/.test(accountId) || configuredAccount && discovered.length > 0 && !discovered.includes(accountId)) {
|
|
4379
4560
|
throw new Error("Wrangler account is ambiguous or does not match account_id in the config");
|
|
@@ -4918,9 +5099,9 @@ function initProject(options) {
|
|
|
4918
5099
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
4919
5100
|
out.log("updated .gitignore for local odla credentials");
|
|
4920
5101
|
}
|
|
4921
|
-
function writeIfMissing(path,
|
|
5102
|
+
function writeIfMissing(path, text3) {
|
|
4922
5103
|
if ((0, import_node_fs14.existsSync)(path)) return;
|
|
4923
|
-
(0, import_node_fs14.writeFileSync)(path,
|
|
5104
|
+
(0, import_node_fs14.writeFileSync)(path, text3);
|
|
4924
5105
|
}
|
|
4925
5106
|
function configTemplate(input) {
|
|
4926
5107
|
const calendar = input.services.includes("calendar") ? ` calendar: {
|
|
@@ -5150,13 +5331,13 @@ async function secretsSetClerkKey(options) {
|
|
|
5150
5331
|
body: JSON.stringify({ value: value2 })
|
|
5151
5332
|
});
|
|
5152
5333
|
if (!res.ok) {
|
|
5153
|
-
const
|
|
5154
|
-
throw new Error(`store Clerk secret key failed (${res.status}): ${
|
|
5334
|
+
const text3 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
|
|
5335
|
+
throw new Error(`store Clerk secret key failed (${res.status}): ${text3 || "request failed"}`);
|
|
5155
5336
|
}
|
|
5156
5337
|
out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
|
|
5157
5338
|
}
|
|
5158
|
-
function scrubValue(
|
|
5159
|
-
return redactSecrets(
|
|
5339
|
+
function scrubValue(text3, value2) {
|
|
5340
|
+
return redactSecrets(text3).split(value2).join("[value redacted]");
|
|
5160
5341
|
}
|
|
5161
5342
|
async function resolveVaultWrite(options) {
|
|
5162
5343
|
const out = options.stdout ?? console;
|
|
@@ -5261,8 +5442,8 @@ var init_secrets_status = __esm({
|
|
|
5261
5442
|
});
|
|
5262
5443
|
|
|
5263
5444
|
// src/skill-adapters.ts
|
|
5264
|
-
function claudeAdapter(skill,
|
|
5265
|
-
const match =
|
|
5445
|
+
function claudeAdapter(skill, canonical2) {
|
|
5446
|
+
const match = canonical2.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
5266
5447
|
if (!match) throw new Error(`bundled skill ${skill} has no YAML frontmatter`);
|
|
5267
5448
|
const lines = match[1].split(/\r?\n/);
|
|
5268
5449
|
const frontmatter = [];
|
|
@@ -5386,8 +5567,8 @@ function installSkill(options = {}) {
|
|
|
5386
5567
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
5387
5568
|
if (harnesses.includes("claude")) {
|
|
5388
5569
|
for (const skill of skillNames(files)) {
|
|
5389
|
-
const
|
|
5390
|
-
plan((0, import_node_path14.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill,
|
|
5570
|
+
const canonical2 = (0, import_node_fs15.readFileSync)((0, import_node_path14.join)(sourceDir, skill, "SKILL.md"), "utf8");
|
|
5571
|
+
plan((0, import_node_path14.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical2));
|
|
5391
5572
|
}
|
|
5392
5573
|
rememberTarget("claude", claudeRoot);
|
|
5393
5574
|
}
|
|
@@ -5676,7 +5857,7 @@ function assertCalendarHealthy(status, expected) {
|
|
|
5676
5857
|
if (!status.writable) throw new Error('calendar grant does not cover booking writes; run "odla-ai calendar connect" to re-consent');
|
|
5677
5858
|
const hasEventsScope = status.grantedScopes.some((scope) => scope === GOOGLE_CALENDAR_EVENTS_SCOPE);
|
|
5678
5859
|
if (!hasEventsScope) throw new Error("calendar connection is missing calendar.events consent");
|
|
5679
|
-
const missing = expected.availabilityCalendars.filter((
|
|
5860
|
+
const missing = expected.availabilityCalendars.filter((id2) => !status.calendars.includes(id2));
|
|
5680
5861
|
if (missing.length) throw new Error(`calendar connection is missing configured calendars: ${missing.join(", ")}`);
|
|
5681
5862
|
}
|
|
5682
5863
|
async function getJson(doFetch, url, bearer) {
|
|
@@ -6087,8 +6268,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
6087
6268
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
6088
6269
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
6089
6270
|
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
6090
|
-
const entries = inventory.flatMap((
|
|
6091
|
-
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(
|
|
6271
|
+
const entries = inventory.flatMap((record11) => {
|
|
6272
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record11);
|
|
6092
6273
|
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
6093
6274
|
});
|
|
6094
6275
|
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
@@ -6369,8 +6550,8 @@ function normalize(value2) {
|
|
|
6369
6550
|
if (Array.isArray(value2)) return value2.map(normalize);
|
|
6370
6551
|
if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
|
|
6371
6552
|
if (typeof value2 === "object") {
|
|
6372
|
-
const
|
|
6373
|
-
return Object.fromEntries(Object.keys(
|
|
6553
|
+
const record11 = value2;
|
|
6554
|
+
return Object.fromEntries(Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, normalize(record11[key])]));
|
|
6374
6555
|
}
|
|
6375
6556
|
throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
|
|
6376
6557
|
}
|
|
@@ -6449,7 +6630,7 @@ function copyRef(ref) {
|
|
|
6449
6630
|
function normalizeReaders(readers) {
|
|
6450
6631
|
if (readers.kind === "public") return Object.freeze({ kind: "public" });
|
|
6451
6632
|
const principalIds = [...new Set(readers.principalIds)].sort();
|
|
6452
|
-
if (principalIds.some((
|
|
6633
|
+
if (principalIds.some((id2) => !id2)) throw new CamelError("reader_mismatch", "Reader principal IDs must be non-empty.");
|
|
6453
6634
|
return Object.freeze({ kind: "principals", principalIds: Object.freeze(principalIds) });
|
|
6454
6635
|
}
|
|
6455
6636
|
var camelValueBrand, authenticCamelValues;
|
|
@@ -6466,7 +6647,7 @@ var init_chunk_4DQ6BIHP = __esm({
|
|
|
6466
6647
|
// ../camel/dist/code.js
|
|
6467
6648
|
async function digestCodeVerificationReceipt(fields) {
|
|
6468
6649
|
validate(fields);
|
|
6469
|
-
const
|
|
6650
|
+
const canonical2 = {
|
|
6470
6651
|
schemaVersion: fields.schemaVersion,
|
|
6471
6652
|
verificationId: fields.verificationId,
|
|
6472
6653
|
trustedBaseCommitSha: fields.trustedBaseCommitSha,
|
|
@@ -6493,7 +6674,7 @@ async function digestCodeVerificationReceipt(fields) {
|
|
|
6493
6674
|
changedTestsRequireReview: fields.changedTestsRequireReview,
|
|
6494
6675
|
outcome: fields.outcome
|
|
6495
6676
|
};
|
|
6496
|
-
return `sha256:${await sha256Hex(canonicalJson2(
|
|
6677
|
+
return `sha256:${await sha256Hex(canonicalJson2(canonical2))}`;
|
|
6497
6678
|
}
|
|
6498
6679
|
function validate(fields) {
|
|
6499
6680
|
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) {
|
|
@@ -6702,7 +6883,7 @@ async function createConversionRegistry(config) {
|
|
|
6702
6883
|
if (await conversionPolicyDigest(definition) !== policy.digest) throw new CamelError("state_conflict", "Conversion policy digest mismatch.");
|
|
6703
6884
|
if (policy.output.kind === "registered_id") {
|
|
6704
6885
|
const registry = config.registeredIds?.[policy.output.registryId];
|
|
6705
|
-
const validValues = registry && Object.entries(registry.values).every(([candidate,
|
|
6886
|
+
const validValues = registry && Object.entries(registry.values).every(([candidate, id2]) => candidate.length > 0 && typeof id2 === "string" && id2.length > 0);
|
|
6706
6887
|
if (!registry || !validValues || registry.digest !== policy.output.registryDigest || await registeredIdRegistryDigest(registry.values) !== registry.digest) {
|
|
6707
6888
|
throw new CamelError("state_conflict", "Registered-ID registry digest mismatch.");
|
|
6708
6889
|
}
|
|
@@ -6710,63 +6891,63 @@ async function createConversionRegistry(config) {
|
|
|
6710
6891
|
policies.set(policy.conversionId, Object.freeze(policy));
|
|
6711
6892
|
}
|
|
6712
6893
|
const outputCounts = /* @__PURE__ */ new Map();
|
|
6713
|
-
const get = (
|
|
6714
|
-
const policy = policies.get(
|
|
6894
|
+
const get = (id2, kind) => {
|
|
6895
|
+
const policy = policies.get(id2);
|
|
6715
6896
|
if (!policy || policy.output.kind !== kind) throw new CamelError("conversion_rejected", "Conversion policy is missing or has the wrong output kind.");
|
|
6716
6897
|
return policy;
|
|
6717
6898
|
};
|
|
6718
|
-
const checked = (source,
|
|
6719
|
-
const policy = get(
|
|
6899
|
+
const checked = (source, id2, kind) => {
|
|
6900
|
+
const policy = get(id2, kind);
|
|
6720
6901
|
if (utf8Length(source.value) > policy.maximumSourceBytes) throw new CamelError("limit_exceeded", "Unsafe conversion input exceeds its byte bound.");
|
|
6721
6902
|
return policy;
|
|
6722
6903
|
};
|
|
6723
|
-
const
|
|
6904
|
+
const emit4 = (source, policy, value2) => convert(source, policy, value2, outputCounts);
|
|
6724
6905
|
const operations = Object.freeze({
|
|
6725
|
-
boolean: async (value2,
|
|
6726
|
-
const policy = checked(value2,
|
|
6727
|
-
return
|
|
6906
|
+
boolean: async (value2, id2) => {
|
|
6907
|
+
const policy = checked(value2, id2, "boolean");
|
|
6908
|
+
return emit4(value2, policy, requireBoolean(value2.value));
|
|
6728
6909
|
},
|
|
6729
|
-
integer: async (value2,
|
|
6730
|
-
const policy = checked(value2,
|
|
6731
|
-
return
|
|
6910
|
+
integer: async (value2, id2) => {
|
|
6911
|
+
const policy = checked(value2, id2, "integer");
|
|
6912
|
+
return emit4(value2, policy, boundedInteger(value2.value, policy.output));
|
|
6732
6913
|
},
|
|
6733
|
-
finiteNumber: async (value2,
|
|
6734
|
-
const policy = checked(value2,
|
|
6735
|
-
return
|
|
6914
|
+
finiteNumber: async (value2, id2) => {
|
|
6915
|
+
const policy = checked(value2, id2, "finite_number");
|
|
6916
|
+
return emit4(value2, policy, boundedNumber(value2.value, policy.output));
|
|
6736
6917
|
},
|
|
6737
|
-
enum: async (value2,
|
|
6738
|
-
const policy = checked(value2,
|
|
6739
|
-
return
|
|
6918
|
+
enum: async (value2, id2) => {
|
|
6919
|
+
const policy = checked(value2, id2, "enum");
|
|
6920
|
+
return emit4(value2, policy, enumMember(value2.value, policy.output));
|
|
6740
6921
|
},
|
|
6741
|
-
date: async (value2,
|
|
6742
|
-
const policy = checked(value2,
|
|
6743
|
-
return
|
|
6922
|
+
date: async (value2, id2) => {
|
|
6923
|
+
const policy = checked(value2, id2, "date");
|
|
6924
|
+
return emit4(value2, policy, canonicalDate(value2.value, policy.output));
|
|
6744
6925
|
},
|
|
6745
|
-
registeredId: async (value2,
|
|
6746
|
-
const policy = checked(value2,
|
|
6926
|
+
registeredId: async (value2, id2) => {
|
|
6927
|
+
const policy = checked(value2, id2, "registered_id");
|
|
6747
6928
|
const registry = config.registeredIds?.[policy.output.registryId];
|
|
6748
6929
|
const output = typeof value2.value === "string" ? registry?.values[value2.value] : void 0;
|
|
6749
6930
|
if (!output) throw new CamelError("conversion_rejected", "Registered-ID conversion rejected the candidate.");
|
|
6750
|
-
return
|
|
6931
|
+
return emit4(value2, policy, output);
|
|
6751
6932
|
},
|
|
6752
|
-
digest: async (value2,
|
|
6753
|
-
const policy = checked(value2,
|
|
6933
|
+
digest: async (value2, id2) => {
|
|
6934
|
+
const policy = checked(value2, id2, "digest");
|
|
6754
6935
|
if (!(value2.value instanceof Uint8Array)) throw new CamelError("conversion_rejected", "Digest conversion requires bytes.");
|
|
6755
|
-
return
|
|
6936
|
+
return emit4(value2, policy, await sha256Hex(value2.value));
|
|
6756
6937
|
},
|
|
6757
|
-
measure: async (value2, metric,
|
|
6758
|
-
const policy = checked(value2,
|
|
6938
|
+
measure: async (value2, metric, id2) => {
|
|
6939
|
+
const policy = checked(value2, id2, "integer");
|
|
6759
6940
|
const measured = measure(value2.value, metric);
|
|
6760
|
-
return
|
|
6941
|
+
return emit4(value2, policy, boundedInteger(measured, policy.output));
|
|
6761
6942
|
},
|
|
6762
|
-
test: async (value2, predicateId,
|
|
6763
|
-
const policy = checked(value2,
|
|
6943
|
+
test: async (value2, predicateId, id2) => {
|
|
6944
|
+
const policy = checked(value2, id2, "boolean");
|
|
6764
6945
|
const predicate = config.predicates?.[predicateId];
|
|
6765
6946
|
if (!predicate) throw new CamelError("conversion_rejected", "Predicate is not registered.");
|
|
6766
|
-
return
|
|
6947
|
+
return emit4(value2, policy, evaluatePredicate(value2.value, predicate, config.registeredIds));
|
|
6767
6948
|
}
|
|
6768
6949
|
});
|
|
6769
|
-
return Object.freeze({ operations, policy: (
|
|
6950
|
+
return Object.freeze({ operations, policy: (id2) => policies.get(id2) ?? missingPolicy() });
|
|
6770
6951
|
}
|
|
6771
6952
|
async function convert(source, policy, value2, counts) {
|
|
6772
6953
|
const sourceKey = sourceIdentity(source);
|
|
@@ -6809,8 +6990,8 @@ function boundedInteger(value2, spec) {
|
|
|
6809
6990
|
}
|
|
6810
6991
|
function boundedNumber(value2, spec) {
|
|
6811
6992
|
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.");
|
|
6812
|
-
const
|
|
6813
|
-
if (/e/i.test(
|
|
6993
|
+
const text3 = String(value2);
|
|
6994
|
+
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.");
|
|
6814
6995
|
return value2;
|
|
6815
6996
|
}
|
|
6816
6997
|
function enumMember(value2, spec) {
|
|
@@ -6870,10 +7051,10 @@ function createCamelIngress(constants2 = []) {
|
|
|
6870
7051
|
const ingress = {
|
|
6871
7052
|
userInstruction: (value2, input) => createSafeInternal(value2, "user_instruction", metadata("user_instruction", input.id, input.readers)),
|
|
6872
7053
|
systemPolicy: (value2, input) => createSafeInternal(value2, "system_policy", metadata("system_policy", input.id, input.readers)),
|
|
6873
|
-
control: (
|
|
6874
|
-
const item = byId.get(
|
|
7054
|
+
control: (id2) => {
|
|
7055
|
+
const item = byId.get(id2);
|
|
6875
7056
|
if (!item) throw new CamelError("permission_denied", "Unknown control constant.");
|
|
6876
|
-
return createSafeInternal(item.value, "harness_constant", metadata("harness",
|
|
7057
|
+
return createSafeInternal(item.value, "harness_constant", metadata("harness", id2, item.readers));
|
|
6877
7058
|
},
|
|
6878
7059
|
external: (value2, label) => createUnsafeInternal(value2, label),
|
|
6879
7060
|
quarantinedOutput: (value2, input) => {
|
|
@@ -6897,9 +7078,9 @@ function assertNoUnsafeConstant(value2, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
6897
7078
|
}
|
|
6898
7079
|
for (const child of Object.values(value2)) assertNoUnsafeConstant(child, seen);
|
|
6899
7080
|
}
|
|
6900
|
-
function metadata(kind,
|
|
6901
|
-
if (!
|
|
6902
|
-
return { readers, provenance: [{ kind, id }] };
|
|
7081
|
+
function metadata(kind, id2, readers) {
|
|
7082
|
+
if (!id2) throw new CamelError("state_conflict", "Provenance IDs must be non-empty.");
|
|
7083
|
+
return { readers, provenance: [{ kind, id: id2 }] };
|
|
6903
7084
|
}
|
|
6904
7085
|
var init_chunk_VEAUXH4F = __esm({
|
|
6905
7086
|
"../camel/dist/chunk-VEAUXH4F.js"() {
|
|
@@ -6981,7 +7162,7 @@ function isControlOwned(value2) {
|
|
|
6981
7162
|
return value2.label.safeBasis === "system_policy" || value2.label.safeBasis === "harness_constant";
|
|
6982
7163
|
}
|
|
6983
7164
|
function copyRegistries(registries) {
|
|
6984
|
-
return Object.freeze(Object.fromEntries(Object.entries(registries).map(([
|
|
7165
|
+
return Object.freeze(Object.fromEntries(Object.entries(registries).map(([id2, registry]) => [id2, Object.freeze({ digest: registry.digest, values: Object.freeze([...registry.values]) })])));
|
|
6985
7166
|
}
|
|
6986
7167
|
function validateUnsafeSelector(path, value2, tool) {
|
|
6987
7168
|
const policy = tool.unsafeSelectorPolicy;
|
|
@@ -6993,8 +7174,8 @@ function validateUnsafeSelector(path, value2, tool) {
|
|
|
6993
7174
|
return void 0;
|
|
6994
7175
|
}
|
|
6995
7176
|
function looksLikeDestination(value2) {
|
|
6996
|
-
const
|
|
6997
|
-
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(
|
|
7177
|
+
const text3 = value2.trim();
|
|
7178
|
+
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text3) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text3);
|
|
6998
7179
|
}
|
|
6999
7180
|
var init_policy = __esm({
|
|
7000
7181
|
"../camel/dist/policy.js"() {
|
|
@@ -7006,9 +7187,9 @@ var init_policy = __esm({
|
|
|
7006
7187
|
});
|
|
7007
7188
|
|
|
7008
7189
|
// ../graph/dist/chunk-PS2SO4UP.js
|
|
7009
|
-
function parseNodeId(
|
|
7010
|
-
const at =
|
|
7011
|
-
return at < 0 ? { kind: "", name:
|
|
7190
|
+
function parseNodeId(id2) {
|
|
7191
|
+
const at = id2.indexOf(":");
|
|
7192
|
+
return at < 0 ? { kind: "", name: id2 } : { kind: id2.slice(0, at), name: id2.slice(at + 1) };
|
|
7012
7193
|
}
|
|
7013
7194
|
function nodesOfKind(graph, kind) {
|
|
7014
7195
|
return [...graph.nodes.values()].filter((node) => node.kind === kind);
|
|
@@ -7025,14 +7206,14 @@ var init_chunk_PS2SO4UP = __esm({
|
|
|
7025
7206
|
seen = /* @__PURE__ */ new Set();
|
|
7026
7207
|
/** Add or enrich a node. Later attributes win; the kind never changes. */
|
|
7027
7208
|
node(kind, name, attrs) {
|
|
7028
|
-
const
|
|
7029
|
-
const existing = this.byId.get(
|
|
7209
|
+
const id2 = nodeId(kind, name);
|
|
7210
|
+
const existing = this.byId.get(id2);
|
|
7030
7211
|
if (existing) {
|
|
7031
|
-
if (attrs) this.byId.set(
|
|
7032
|
-
return
|
|
7212
|
+
if (attrs) this.byId.set(id2, { ...existing, attrs: { ...existing.attrs, ...attrs } });
|
|
7213
|
+
return id2;
|
|
7033
7214
|
}
|
|
7034
|
-
this.byId.set(
|
|
7035
|
-
return
|
|
7215
|
+
this.byId.set(id2, { id: id2, kind, name, ...attrs ? { attrs } : {} });
|
|
7216
|
+
return id2;
|
|
7036
7217
|
}
|
|
7037
7218
|
/**
|
|
7038
7219
|
* Add a directed edge, minting either endpoint if it is not known yet.
|
|
@@ -7042,10 +7223,10 @@ var init_chunk_PS2SO4UP = __esm({
|
|
|
7042
7223
|
* by how often someone repeated an import.
|
|
7043
7224
|
*/
|
|
7044
7225
|
edge(from, kind, to, attrs) {
|
|
7045
|
-
for (const
|
|
7046
|
-
if (!this.byId.has(
|
|
7047
|
-
const parsed = parseNodeId(
|
|
7048
|
-
this.byId.set(
|
|
7226
|
+
for (const id2 of [from, to]) {
|
|
7227
|
+
if (!this.byId.has(id2)) {
|
|
7228
|
+
const parsed = parseNodeId(id2);
|
|
7229
|
+
this.byId.set(id2, { id: id2, kind: parsed.kind, name: parsed.name });
|
|
7049
7230
|
}
|
|
7050
7231
|
}
|
|
7051
7232
|
const key = `${from} ${kind} ${to}`;
|
|
@@ -7076,17 +7257,17 @@ var init_chunk_PS2SO4UP = __esm({
|
|
|
7076
7257
|
});
|
|
7077
7258
|
|
|
7078
7259
|
// ../graph/dist/index.js
|
|
7079
|
-
function incident(graph,
|
|
7260
|
+
function incident(graph, id2, traversal = {}) {
|
|
7080
7261
|
const direction = traversal.direction ?? "out";
|
|
7081
|
-
const forward = direction === "out" || direction === "both" ? graph.out.get(
|
|
7082
|
-
const backward = direction === "in" || direction === "both" ? graph.in.get(
|
|
7262
|
+
const forward = direction === "out" || direction === "both" ? graph.out.get(id2) ?? [] : [];
|
|
7263
|
+
const backward = direction === "in" || direction === "both" ? graph.in.get(id2) ?? [] : [];
|
|
7083
7264
|
return [...forward, ...backward].filter((edge) => follows(traversal.kinds, edge));
|
|
7084
7265
|
}
|
|
7085
|
-
function neighbors(graph,
|
|
7266
|
+
function neighbors(graph, id2, traversal = {}) {
|
|
7086
7267
|
const seen = /* @__PURE__ */ new Set();
|
|
7087
|
-
for (const edge of incident(graph,
|
|
7088
|
-
const other = otherEnd(edge,
|
|
7089
|
-
if (other !==
|
|
7268
|
+
for (const edge of incident(graph, id2, traversal)) {
|
|
7269
|
+
const other = otherEnd(edge, id2);
|
|
7270
|
+
if (other !== id2) seen.add(other);
|
|
7090
7271
|
}
|
|
7091
7272
|
return [...seen];
|
|
7092
7273
|
}
|
|
@@ -7168,9 +7349,9 @@ async function extractImports(builder, input) {
|
|
|
7168
7349
|
const sources = input.paths.filter(isSourcePath);
|
|
7169
7350
|
const known = new Set(sources);
|
|
7170
7351
|
for (const path of sources) {
|
|
7171
|
-
let
|
|
7352
|
+
let text3;
|
|
7172
7353
|
try {
|
|
7173
|
-
|
|
7354
|
+
text3 = await input.read(path);
|
|
7174
7355
|
} catch {
|
|
7175
7356
|
continue;
|
|
7176
7357
|
}
|
|
@@ -7178,13 +7359,13 @@ async function extractImports(builder, input) {
|
|
|
7178
7359
|
const file = builder.node(FILE, path, pkg ? { pkg } : void 0);
|
|
7179
7360
|
if (pkg) builder.edge(builder.node(PACKAGE, pkg), CONTAINS, file);
|
|
7180
7361
|
const specifiers = /* @__PURE__ */ new Set();
|
|
7181
|
-
for (const match of
|
|
7182
|
-
for (const match of
|
|
7362
|
+
for (const match of text3.matchAll(IMPORT_FROM)) specifiers.add(match[1]);
|
|
7363
|
+
for (const match of text3.matchAll(BARE_IMPORT)) specifiers.add(match[1]);
|
|
7183
7364
|
for (const specifier of specifiers) {
|
|
7184
7365
|
const resolved = resolveImport(path, specifier, known);
|
|
7185
7366
|
if (resolved) builder.edge(file, IMPORTS, nodeId(FILE, resolved));
|
|
7186
7367
|
}
|
|
7187
|
-
for (const name of exportedNames(
|
|
7368
|
+
for (const name of exportedNames(text3)) {
|
|
7188
7369
|
builder.edge(file, EXPORTS, builder.node(SYMBOL, name));
|
|
7189
7370
|
}
|
|
7190
7371
|
}
|
|
@@ -7197,16 +7378,16 @@ async function extractData(builder, input) {
|
|
|
7197
7378
|
};
|
|
7198
7379
|
for (const path of input.paths) {
|
|
7199
7380
|
if (!SOURCE_FILE.test(path) || input.ignore?.(path)) continue;
|
|
7200
|
-
let
|
|
7381
|
+
let text3;
|
|
7201
7382
|
try {
|
|
7202
|
-
|
|
7383
|
+
text3 = await input.read(path);
|
|
7203
7384
|
} catch {
|
|
7204
7385
|
continue;
|
|
7205
7386
|
}
|
|
7206
|
-
for (const statement of
|
|
7387
|
+
for (const statement of text3.matchAll(STATEMENT)) {
|
|
7207
7388
|
const verb = statement[1].toUpperCase().replace(/\s+/g, " ");
|
|
7208
7389
|
const start = statement.index ?? 0;
|
|
7209
|
-
const rest =
|
|
7390
|
+
const rest = text3.slice(start + statement[0].length, start + STATEMENT_WINDOW);
|
|
7210
7391
|
if (verb === "SELECT") {
|
|
7211
7392
|
for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
|
|
7212
7393
|
continue;
|
|
@@ -7222,16 +7403,16 @@ async function extractData(builder, input) {
|
|
|
7222
7403
|
for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
|
|
7223
7404
|
}
|
|
7224
7405
|
}
|
|
7225
|
-
for (const match of
|
|
7226
|
-
touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(
|
|
7406
|
+
for (const match of text3.matchAll(NS_CONST)) {
|
|
7407
|
+
touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(text3, match.index ?? 0));
|
|
7227
7408
|
}
|
|
7228
|
-
for (const match of
|
|
7229
|
-
touch(path, match[1], NAMESPACE, accessFor(
|
|
7409
|
+
for (const match of text3.matchAll(NS_LITERAL)) {
|
|
7410
|
+
touch(path, match[1], NAMESPACE, accessFor(text3, match.index ?? 0));
|
|
7230
7411
|
}
|
|
7231
7412
|
}
|
|
7232
7413
|
}
|
|
7233
|
-
function accessFor(
|
|
7234
|
-
const window =
|
|
7414
|
+
function accessFor(text3, index) {
|
|
7415
|
+
const window = text3.slice(Math.max(0, index - 160), index + 40);
|
|
7235
7416
|
return /\b(?:transact|update|delete|create|insert|Ops)\b/.test(window) ? WRITES : READS;
|
|
7236
7417
|
}
|
|
7237
7418
|
async function buildCodeGraph(input) {
|
|
@@ -7368,13 +7549,13 @@ function createCodeRuntimeControlClient(options) {
|
|
|
7368
7549
|
if (!Number.isSafeInteger(modelRequestTimeoutMs) || modelRequestTimeoutMs < 3e4 || modelRequestTimeoutMs > 30 * 6e4) {
|
|
7369
7550
|
throw new TypeError("modelRequestTimeoutMs must be an integer from 30000 to 1800000");
|
|
7370
7551
|
}
|
|
7371
|
-
const
|
|
7552
|
+
const request3 = options.fetch ?? fetch;
|
|
7372
7553
|
const call2 = async (path, body, timeoutMs = requestTimeoutMs) => {
|
|
7373
7554
|
const timeout = AbortSignal.timeout(timeoutMs);
|
|
7374
7555
|
const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
|
|
7375
7556
|
let response2;
|
|
7376
7557
|
try {
|
|
7377
|
-
response2 = await
|
|
7558
|
+
response2 = await request3(`${endpoint}${path}`, {
|
|
7378
7559
|
method: "POST",
|
|
7379
7560
|
headers: { authorization: `Bearer ${options.token}`, "content-type": "application/json" },
|
|
7380
7561
|
body: JSON.stringify(body),
|
|
@@ -7387,7 +7568,7 @@ function createCodeRuntimeControlClient(options) {
|
|
|
7387
7568
|
}
|
|
7388
7569
|
const value2 = await response2.json().catch(() => null);
|
|
7389
7570
|
if (!response2.ok) {
|
|
7390
|
-
const problem =
|
|
7571
|
+
const problem = record5(record5(value2)?.error);
|
|
7391
7572
|
throw new CodeRuntimeControlError(
|
|
7392
7573
|
typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
|
|
7393
7574
|
response2.status,
|
|
@@ -7409,12 +7590,12 @@ function createCodeRuntimeControlClient(options) {
|
|
|
7409
7590
|
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
|
|
7410
7591
|
),
|
|
7411
7592
|
infer: async (sessionId, inference) => {
|
|
7412
|
-
const value2 =
|
|
7593
|
+
const value2 = record5(await call2(
|
|
7413
7594
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
|
|
7414
7595
|
inference,
|
|
7415
7596
|
modelRequestTimeoutMs
|
|
7416
7597
|
));
|
|
7417
|
-
if (!value2 || value2.requestId !== inference.requestId || !
|
|
7598
|
+
if (!value2 || value2.requestId !== inference.requestId || !record5(value2.response) || !record5(value2.receipt)) {
|
|
7418
7599
|
throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
|
|
7419
7600
|
}
|
|
7420
7601
|
return value2;
|
|
@@ -7482,12 +7663,12 @@ function validateHeartbeat(version, capabilities) {
|
|
|
7482
7663
|
}
|
|
7483
7664
|
}
|
|
7484
7665
|
function parseSnapshot(value2) {
|
|
7485
|
-
const root =
|
|
7486
|
-
const host =
|
|
7666
|
+
const root = record5(value2);
|
|
7667
|
+
const host = record5(root?.host);
|
|
7487
7668
|
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");
|
|
7488
7669
|
const bindingIds = /* @__PURE__ */ new Set();
|
|
7489
7670
|
const bindings = root.bindings.map((item) => {
|
|
7490
|
-
const binding =
|
|
7671
|
+
const binding = record5(item);
|
|
7491
7672
|
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)) {
|
|
7492
7673
|
throw invalid("binding");
|
|
7493
7674
|
}
|
|
@@ -7497,10 +7678,10 @@ function parseSnapshot(value2) {
|
|
|
7497
7678
|
const commandIds = /* @__PURE__ */ new Set();
|
|
7498
7679
|
const commandSequences = /* @__PURE__ */ new Set();
|
|
7499
7680
|
const commands = root.commands.map((item) => {
|
|
7500
|
-
const command =
|
|
7681
|
+
const command = record5(item);
|
|
7501
7682
|
const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
|
|
7502
7683
|
const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
|
|
7503
|
-
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)) || !
|
|
7684
|
+
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");
|
|
7504
7685
|
commandIds.add(command.commandId);
|
|
7505
7686
|
commandSequences.add(sequenceKey);
|
|
7506
7687
|
return command;
|
|
@@ -7508,10 +7689,10 @@ function parseSnapshot(value2) {
|
|
|
7508
7689
|
return { host, bindings, commands };
|
|
7509
7690
|
}
|
|
7510
7691
|
async function parseSource(value2) {
|
|
7511
|
-
const snapshot =
|
|
7692
|
+
const snapshot = record5(record5(value2)?.snapshot);
|
|
7512
7693
|
if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
|
|
7513
7694
|
const files = snapshot.files.map((value22) => {
|
|
7514
|
-
const file =
|
|
7695
|
+
const file = record5(value22);
|
|
7515
7696
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
|
|
7516
7697
|
return { path: file.path, content: file.content };
|
|
7517
7698
|
});
|
|
@@ -7520,11 +7701,11 @@ async function parseSource(value2) {
|
|
|
7520
7701
|
const aliases = /* @__PURE__ */ new Set();
|
|
7521
7702
|
const references = [];
|
|
7522
7703
|
for (const item of referencesValue) {
|
|
7523
|
-
const reference =
|
|
7704
|
+
const reference = record5(item);
|
|
7524
7705
|
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");
|
|
7525
7706
|
aliases.add(reference.alias);
|
|
7526
7707
|
const referenceFiles = reference.files.map((entry) => {
|
|
7527
|
-
const file =
|
|
7708
|
+
const file = record5(entry);
|
|
7528
7709
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
|
|
7529
7710
|
return { path: file.path, content: file.content };
|
|
7530
7711
|
});
|
|
@@ -7539,12 +7720,12 @@ async function parseSource(value2) {
|
|
|
7539
7720
|
return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
|
|
7540
7721
|
}
|
|
7541
7722
|
function parseReview(value2) {
|
|
7542
|
-
const review =
|
|
7723
|
+
const review = record5(record5(value2)?.review);
|
|
7543
7724
|
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");
|
|
7544
7725
|
return review;
|
|
7545
7726
|
}
|
|
7546
7727
|
function parseCandidate(value2) {
|
|
7547
|
-
const candidate =
|
|
7728
|
+
const candidate = record5(record5(value2)?.candidate);
|
|
7548
7729
|
if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
|
|
7549
7730
|
throw invalid("candidate");
|
|
7550
7731
|
}
|
|
@@ -7640,8 +7821,8 @@ function gitApply(cwd, patch2, check) {
|
|
|
7640
7821
|
});
|
|
7641
7822
|
let stderr = "";
|
|
7642
7823
|
child.stderr.setEncoding("utf8");
|
|
7643
|
-
child.stderr.on("data", (
|
|
7644
|
-
if (stderr.length < 4e3) stderr +=
|
|
7824
|
+
child.stderr.on("data", (text3) => {
|
|
7825
|
+
if (stderr.length < 4e3) stderr += text3.slice(0, 4e3);
|
|
7645
7826
|
});
|
|
7646
7827
|
child.once("error", reject);
|
|
7647
7828
|
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr.trim().slice(0, 500)))));
|
|
@@ -8412,7 +8593,7 @@ async function runCodeAgentAttempt(options) {
|
|
|
8412
8593
|
}
|
|
8413
8594
|
}
|
|
8414
8595
|
async function handleCodeRuntimeInference(input) {
|
|
8415
|
-
const { command, metadata: metadata2, request:
|
|
8596
|
+
const { command, metadata: metadata2, request: request3, state: state2 } = input;
|
|
8416
8597
|
if (state2.tokens >= metadata2.maxTokensPerInteraction) {
|
|
8417
8598
|
if (!state2.noticeEmitted) {
|
|
8418
8599
|
state2.noticeEmitted = true;
|
|
@@ -8425,7 +8606,7 @@ async function handleCodeRuntimeInference(input) {
|
|
|
8425
8606
|
return {
|
|
8426
8607
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
8427
8608
|
type: "inference.response",
|
|
8428
|
-
requestId:
|
|
8609
|
+
requestId: request3.requestId,
|
|
8429
8610
|
response: {
|
|
8430
8611
|
id: `budget:${command.commandId}`,
|
|
8431
8612
|
provider: "openai",
|
|
@@ -8439,9 +8620,9 @@ async function handleCodeRuntimeInference(input) {
|
|
|
8439
8620
|
}
|
|
8440
8621
|
const startedAt = Date.now();
|
|
8441
8622
|
const response2 = await input.control.infer(command.sessionId, {
|
|
8442
|
-
requestId:
|
|
8623
|
+
requestId: request3.requestId,
|
|
8443
8624
|
interactionId: command.commandId,
|
|
8444
|
-
call:
|
|
8625
|
+
call: request3.call
|
|
8445
8626
|
});
|
|
8446
8627
|
state2.tokens += response2.receipt.inputTokens + response2.receipt.outputTokens;
|
|
8447
8628
|
await input.event({
|
|
@@ -8458,14 +8639,14 @@ async function handleCodeRuntimeInference(input) {
|
|
|
8458
8639
|
return {
|
|
8459
8640
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
8460
8641
|
type: "inference.response",
|
|
8461
|
-
requestId:
|
|
8642
|
+
requestId: request3.requestId,
|
|
8462
8643
|
response: response2.response
|
|
8463
8644
|
};
|
|
8464
8645
|
}
|
|
8465
8646
|
function createCodeRuntimeInference(options) {
|
|
8466
8647
|
let seq = 0;
|
|
8467
8648
|
return {
|
|
8468
|
-
chat: async (
|
|
8649
|
+
chat: async (request3) => {
|
|
8469
8650
|
const requestId = `${options.command.commandId}:${++seq}`;
|
|
8470
8651
|
const answer = await handleCodeRuntimeInference({
|
|
8471
8652
|
command: options.command,
|
|
@@ -8477,7 +8658,7 @@ function createCodeRuntimeInference(options) {
|
|
|
8477
8658
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
8478
8659
|
type: "inference.request",
|
|
8479
8660
|
requestId,
|
|
8480
|
-
call:
|
|
8661
|
+
call: request3
|
|
8481
8662
|
}
|
|
8482
8663
|
});
|
|
8483
8664
|
if (answer.type !== "inference.response") throw new TypeError("brokered inference returned the wrong frame");
|
|
@@ -8640,9 +8821,9 @@ async function safePrefix(base, paths, prefix) {
|
|
|
8640
8821
|
function descriptor(name, effect, argumentRoles) {
|
|
8641
8822
|
return { name, version: 1, effect, inputSchema: { type: "object" }, argumentRoles, policyId: `odla.code.${name}.v1` };
|
|
8642
8823
|
}
|
|
8643
|
-
async function conversionPolicy(
|
|
8824
|
+
async function conversionPolicy(id2, output) {
|
|
8644
8825
|
const definition = {
|
|
8645
|
-
conversionId:
|
|
8826
|
+
conversionId: id2,
|
|
8646
8827
|
version: 1,
|
|
8647
8828
|
output,
|
|
8648
8829
|
maximumSourceBytes: 1e6,
|
|
@@ -8651,18 +8832,18 @@ async function conversionPolicy(id, output) {
|
|
|
8651
8832
|
};
|
|
8652
8833
|
return { ...definition, digest: await conversionPolicyDigest(definition) };
|
|
8653
8834
|
}
|
|
8654
|
-
async function registeredPolicy(
|
|
8835
|
+
async function registeredPolicy(id2, registryId, values) {
|
|
8655
8836
|
const mapping = Object.fromEntries(values.map((value2) => [value2, value2]));
|
|
8656
|
-
return conversionPolicy(
|
|
8837
|
+
return conversionPolicy(id2, {
|
|
8657
8838
|
kind: "registered_id",
|
|
8658
8839
|
registryId,
|
|
8659
8840
|
registryDigest: await registeredIdRegistryDigest(mapping)
|
|
8660
8841
|
});
|
|
8661
8842
|
}
|
|
8662
8843
|
async function conversionRegistry(policies, values) {
|
|
8663
|
-
const registeredIds = Object.fromEntries(await Promise.all(Object.entries(values).map(async ([
|
|
8844
|
+
const registeredIds = Object.fromEntries(await Promise.all(Object.entries(values).map(async ([id2, entries]) => {
|
|
8664
8845
|
const mapping = Object.fromEntries(entries.map((value2) => [value2, value2]));
|
|
8665
|
-
return [
|
|
8846
|
+
return [id2, { values: mapping, digest: await registeredIdRegistryDigest(mapping) }];
|
|
8666
8847
|
})));
|
|
8667
8848
|
return createConversionRegistry({ policies, registeredIds });
|
|
8668
8849
|
}
|
|
@@ -8716,10 +8897,10 @@ function decision(input, policy, approvalConsumed, tool, actionDigest) {
|
|
|
8716
8897
|
actionDigest: actionDigest ?? (policy.outcome === "require_approval" ? policy.actionDigest : "")
|
|
8717
8898
|
};
|
|
8718
8899
|
}
|
|
8719
|
-
function policyContext(context,
|
|
8900
|
+
function policyContext(context, request3, options, extra) {
|
|
8720
8901
|
return {
|
|
8721
8902
|
lease: context.lease,
|
|
8722
|
-
request:
|
|
8903
|
+
request: request3,
|
|
8723
8904
|
workspaceId: `workspace:${context.lease.task.attemptId}`,
|
|
8724
8905
|
readers: { kind: "principals", principalIds: [options.readerId] },
|
|
8725
8906
|
...extra
|
|
@@ -8738,8 +8919,8 @@ function optionalInteger(value2) {
|
|
|
8738
8919
|
if (!Number.isSafeInteger(value2) || value2 < 1) throw new TypeError("line bounds must be positive integers");
|
|
8739
8920
|
return value2;
|
|
8740
8921
|
}
|
|
8741
|
-
function response(
|
|
8742
|
-
return { requestId:
|
|
8922
|
+
function response(request3, ok, content2, details) {
|
|
8923
|
+
return { requestId: request3.requestId, ok, content: content2, ...details ? { details } : {} };
|
|
8743
8924
|
}
|
|
8744
8925
|
function workspaceGraphs(workspaceDir, paths) {
|
|
8745
8926
|
const existing = cache.get(workspaceDir);
|
|
@@ -8762,19 +8943,19 @@ function renderOverview(graphs, prefix) {
|
|
|
8762
8943
|
return [`${total} source files. Directories, largest first \u2014 read one with sandbox.list --prefix.`, ...lines].join("\n");
|
|
8763
8944
|
}
|
|
8764
8945
|
function renderWhereIs(graphs, symbol) {
|
|
8765
|
-
const sites = neighbors(graphs.graph, nodeId(SYMBOL, symbol), { direction: "in", kinds: ["exports"] }).map((
|
|
8766
|
-
path: shortId(
|
|
8767
|
-
pkg: neighbors(graphs.graph,
|
|
8768
|
-
dependents: incident(graphs.graph,
|
|
8946
|
+
const sites = neighbors(graphs.graph, nodeId(SYMBOL, symbol), { direction: "in", kinds: ["exports"] }).map((id2) => ({
|
|
8947
|
+
path: shortId(id2),
|
|
8948
|
+
pkg: neighbors(graphs.graph, id2, { direction: "in", kinds: ["contains"] })[0],
|
|
8949
|
+
dependents: incident(graphs.graph, id2, { direction: "in", kinds: [IMPORTS] }).length
|
|
8769
8950
|
})).sort((left, right) => right.dependents - left.dependents || left.path.localeCompare(right.path));
|
|
8770
8951
|
if (sites.length === 0) return `No exported symbol named "${symbol}". Try sandbox.search for a textual match.`;
|
|
8771
8952
|
return sites.slice(0, 20).map((site) => `${site.path}${site.pkg ? ` [${shortId(site.pkg)}]` : ""} ${site.dependents} dependents`).join("\n");
|
|
8772
8953
|
}
|
|
8773
8954
|
function renderWhoImports(graphs, path) {
|
|
8774
|
-
const
|
|
8775
|
-
const importers = neighbors(graphs.graph,
|
|
8955
|
+
const id2 = nodeId(FILE, path);
|
|
8956
|
+
const importers = neighbors(graphs.graph, id2, { direction: "in", kinds: [IMPORTS] });
|
|
8776
8957
|
if (importers.length === 0) {
|
|
8777
|
-
return graphs.graph.nodes.has(
|
|
8958
|
+
return graphs.graph.nodes.has(id2) ? `Nothing imports ${path}. It is a leaf.` : `${path} is not a source file in this workspace.`;
|
|
8778
8959
|
}
|
|
8779
8960
|
return importers.slice(0, 40).map(shortId).sort().join("\n");
|
|
8780
8961
|
}
|
|
@@ -8791,11 +8972,11 @@ function renderWhoTouches(graphs, query) {
|
|
|
8791
8972
|
].join("\n");
|
|
8792
8973
|
}).join("\n\n");
|
|
8793
8974
|
}
|
|
8794
|
-
async function read(context,
|
|
8795
|
-
exactKeys(
|
|
8796
|
-
const path = stringField(
|
|
8797
|
-
const startLine = optionalInteger(
|
|
8798
|
-
const endLine = optionalInteger(
|
|
8975
|
+
async function read(context, request3, options, policy) {
|
|
8976
|
+
exactKeys(request3.input, ["path", "startLine", "endLine"]);
|
|
8977
|
+
const path = stringField(request3.input, "path");
|
|
8978
|
+
const startLine = optionalInteger(request3.input.startLine) ?? 1;
|
|
8979
|
+
const endLine = optionalInteger(request3.input.endLine) ?? startLine + (options.maxReadLines ?? 2e3) - 1;
|
|
8799
8980
|
if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
|
|
8800
8981
|
throw new TypeError("requested line range exceeds its bound");
|
|
8801
8982
|
}
|
|
@@ -8803,8 +8984,8 @@ async function read(context, request2, options, policy) {
|
|
|
8803
8984
|
if (!paths.includes(path)) {
|
|
8804
8985
|
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.`);
|
|
8805
8986
|
}
|
|
8806
|
-
const allowed = await policy.read(policyContext(context,
|
|
8807
|
-
if (!allowed) return response(
|
|
8987
|
+
const allowed = await policy.read(policyContext(context, request3, options, { paths, path, startLine, endLine }));
|
|
8988
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8808
8989
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
8809
8990
|
const info = await (0, import_promises10.stat)(target);
|
|
8810
8991
|
if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
|
|
@@ -8817,74 +8998,74 @@ async function read(context, request2, options, policy) {
|
|
|
8817
8998
|
if (Buffer.byteLength(content2) > (options.maxReadBytes ?? 128 * 1024)) {
|
|
8818
8999
|
throw new TypeError("read result exceeds its byte bound");
|
|
8819
9000
|
}
|
|
8820
|
-
return response(
|
|
9001
|
+
return response(request3, true, content2, { path, startLine, endLine: Math.min(endLine, lines.length) });
|
|
8821
9002
|
}
|
|
8822
|
-
async function list(context,
|
|
8823
|
-
exactKeys(
|
|
8824
|
-
const raw =
|
|
9003
|
+
async function list(context, request3, options, policy) {
|
|
9004
|
+
exactKeys(request3.input, ["prefix", "maxEntries"]);
|
|
9005
|
+
const raw = request3.input.prefix;
|
|
8825
9006
|
const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
8826
|
-
const maxEntries = optionalInteger(
|
|
9007
|
+
const maxEntries = optionalInteger(request3.input.maxEntries) ?? 1e3;
|
|
8827
9008
|
if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
|
|
8828
9009
|
const paths = await registeredFiles(context.workspaceDir, 2e4);
|
|
8829
|
-
const allowed = await policy.list(policyContext(context,
|
|
8830
|
-
if (!allowed) return response(
|
|
9010
|
+
const allowed = await policy.list(policyContext(context, request3, options, { paths, ...prefix ? { prefix } : {} }));
|
|
9011
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8831
9012
|
const entries = listWorkspace(paths, { ...prefix ? { prefix } : {}, maxEntries });
|
|
8832
9013
|
if (!entries.length) {
|
|
8833
|
-
return response(
|
|
9014
|
+
return response(request3, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
|
|
8834
9015
|
}
|
|
8835
9016
|
const truncated = entries.length < paths.length && entries.length === maxEntries;
|
|
8836
9017
|
const hint = !prefix && paths.length > 500 ? `
|
|
8837
9018
|
\u2026 ${paths.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
|
|
8838
9019
|
return response(
|
|
8839
|
-
|
|
9020
|
+
request3,
|
|
8840
9021
|
true,
|
|
8841
9022
|
`${entries.join("\n")}${truncated ? `
|
|
8842
9023
|
\u2026 truncated at ${maxEntries} entries` : ""}${hint}`,
|
|
8843
9024
|
{ count: entries.length, truncated }
|
|
8844
9025
|
);
|
|
8845
9026
|
}
|
|
8846
|
-
async function search(context,
|
|
8847
|
-
exactKeys(
|
|
8848
|
-
const query = stringField(
|
|
9027
|
+
async function search(context, request3, options, policy) {
|
|
9028
|
+
exactKeys(request3.input, ["query", "prefix", "maxResults", "caseSensitive"]);
|
|
9029
|
+
const query = stringField(request3.input, "query");
|
|
8849
9030
|
if (query.length > 512) throw new TypeError("search query exceeds its bound");
|
|
8850
|
-
const raw =
|
|
9031
|
+
const raw = request3.input.prefix;
|
|
8851
9032
|
const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
8852
|
-
const maxResults = optionalInteger(
|
|
9033
|
+
const maxResults = optionalInteger(request3.input.maxResults) ?? 100;
|
|
8853
9034
|
if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
|
|
8854
|
-
const caseSensitive =
|
|
9035
|
+
const caseSensitive = request3.input.caseSensitive === void 0 ? true : request3.input.caseSensitive === true;
|
|
8855
9036
|
const paths = await registeredFiles(context.workspaceDir, 2e4);
|
|
8856
|
-
const allowed = await policy.search(policyContext(context,
|
|
8857
|
-
if (!allowed) return response(
|
|
9037
|
+
const allowed = await policy.search(policyContext(context, request3, options, { paths, query, ...prefix ? { prefix } : {} }));
|
|
9038
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8858
9039
|
const matches = await searchWorkspace(context.workspaceDir, paths, {
|
|
8859
9040
|
query,
|
|
8860
9041
|
maxResults,
|
|
8861
9042
|
caseSensitive,
|
|
8862
9043
|
...prefix ? { prefix } : {}
|
|
8863
9044
|
});
|
|
8864
|
-
if (!matches.length) return response(
|
|
8865
|
-
return response(
|
|
9045
|
+
if (!matches.length) return response(request3, true, `No match for "${query}".`, { count: 0 });
|
|
9046
|
+
return response(request3, true, matches.map((match) => `${match.path}:${match.line}: ${match.text}`).join("\n"), {
|
|
8866
9047
|
count: matches.length
|
|
8867
9048
|
});
|
|
8868
9049
|
}
|
|
8869
|
-
async function graphQuery(context,
|
|
8870
|
-
exactKeys(
|
|
8871
|
-
const raw =
|
|
9050
|
+
async function graphQuery(context, request3, options, policy) {
|
|
9051
|
+
exactKeys(request3.input, ["query"]);
|
|
9052
|
+
const raw = request3.input.query;
|
|
8872
9053
|
const query = typeof raw === "string" ? raw : "";
|
|
8873
9054
|
if (query.length > 512) throw new TypeError("query exceeds its bound");
|
|
8874
|
-
const allowed = await policy.graph(policyContext(context,
|
|
8875
|
-
tool:
|
|
9055
|
+
const allowed = await policy.graph(policyContext(context, request3, options, {
|
|
9056
|
+
tool: request3.tool,
|
|
8876
9057
|
selector: query
|
|
8877
9058
|
}));
|
|
8878
|
-
if (!allowed) return response(
|
|
9059
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8879
9060
|
const paths = await registeredFiles(context.workspaceDir, 2e4);
|
|
8880
9061
|
const graphs = await workspaceGraphs(context.workspaceDir, paths);
|
|
8881
|
-
if (
|
|
8882
|
-
return response(
|
|
9062
|
+
if (request3.tool === "sandbox.overview") {
|
|
9063
|
+
return response(request3, true, renderOverview(graphs, query || void 0));
|
|
8883
9064
|
}
|
|
8884
|
-
if (!query) throw new TypeError(`${
|
|
8885
|
-
if (
|
|
8886
|
-
if (
|
|
8887
|
-
return response(
|
|
9065
|
+
if (!query) throw new TypeError(`${request3.tool} requires a query`);
|
|
9066
|
+
if (request3.tool === "sandbox.where_is") return response(request3, true, renderWhereIs(graphs, query));
|
|
9067
|
+
if (request3.tool === "sandbox.who_imports") return response(request3, true, renderWhoImports(graphs, query));
|
|
9068
|
+
return response(request3, true, renderWhoTouches(graphs, query));
|
|
8888
9069
|
}
|
|
8889
9070
|
function createCodeToolBroker(options) {
|
|
8890
9071
|
validateOptions(options);
|
|
@@ -8892,24 +9073,24 @@ function createCodeToolBroker(options) {
|
|
|
8892
9073
|
const policy = createCodePolicyGate(options);
|
|
8893
9074
|
let tail = Promise.resolve();
|
|
8894
9075
|
return {
|
|
8895
|
-
execute(context,
|
|
8896
|
-
const result = tail.then(() =>
|
|
9076
|
+
execute(context, request3) {
|
|
9077
|
+
const result = tail.then(() => route2(context, request3, options, recipes, policy));
|
|
8897
9078
|
tail = result.then(() => void 0, () => void 0);
|
|
8898
9079
|
return result;
|
|
8899
9080
|
}
|
|
8900
9081
|
};
|
|
8901
9082
|
}
|
|
8902
|
-
async function
|
|
9083
|
+
async function route2(context, request3, options, recipes, policy) {
|
|
8903
9084
|
try {
|
|
8904
9085
|
if (context.signal?.aborted) throw new TypeError("tool request was cancelled");
|
|
8905
|
-
if (
|
|
8906
|
-
if (
|
|
8907
|
-
if (
|
|
8908
|
-
if (GRAPH_TOOLS.has(
|
|
8909
|
-
if (
|
|
8910
|
-
return await recipe(context,
|
|
9086
|
+
if (request3.tool === "sandbox.read") return await read(context, request3, options, policy);
|
|
9087
|
+
if (request3.tool === "sandbox.list") return await list(context, request3, options, policy);
|
|
9088
|
+
if (request3.tool === "sandbox.search") return await search(context, request3, options, policy);
|
|
9089
|
+
if (GRAPH_TOOLS.has(request3.tool)) return await graphQuery(context, request3, options, policy);
|
|
9090
|
+
if (request3.tool === "sandbox.apply_patch") return await patch(context, request3, options, policy);
|
|
9091
|
+
return await recipe(context, request3, options, recipes, policy);
|
|
8911
9092
|
} catch (reason) {
|
|
8912
|
-
return response(
|
|
9093
|
+
return response(request3, false, toolFailureMessage(reason));
|
|
8913
9094
|
}
|
|
8914
9095
|
}
|
|
8915
9096
|
function toolFailureMessage(reason) {
|
|
@@ -8921,34 +9102,34 @@ function toolFailureMessage(reason) {
|
|
|
8921
9102
|
if (code === "EACCES" || code === "EPERM") return "that path is not readable through this tool";
|
|
8922
9103
|
return "tool failed closed";
|
|
8923
9104
|
}
|
|
8924
|
-
async function patch(context,
|
|
8925
|
-
exactKeys(
|
|
8926
|
-
const value2 = stringField(
|
|
9105
|
+
async function patch(context, request3, options, policy) {
|
|
9106
|
+
exactKeys(request3.input, ["patch"]);
|
|
9107
|
+
const value2 = stringField(request3.input, "patch");
|
|
8927
9108
|
const paths = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
|
|
8928
9109
|
if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
|
|
8929
9110
|
throw new TypeError("patch targets a read-only reference source");
|
|
8930
9111
|
}
|
|
8931
|
-
const allowed = await policy.patch(policyContext(context,
|
|
8932
|
-
if (!allowed) return response(
|
|
9112
|
+
const allowed = await policy.patch(policyContext(context, request3, options, { patch: value2 }));
|
|
9113
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8933
9114
|
await applyCodePatch(context.workspaceDir, value2, paths);
|
|
8934
|
-
return response(
|
|
9115
|
+
return response(request3, true, `Applied patch to ${paths.length} file(s).`, { paths });
|
|
8935
9116
|
}
|
|
8936
|
-
async function recipe(context,
|
|
8937
|
-
exactKeys(
|
|
8938
|
-
const recipeId = stringField(
|
|
9117
|
+
async function recipe(context, request3, options, recipes, policy) {
|
|
9118
|
+
exactKeys(request3.input, ["recipeId"]);
|
|
9119
|
+
const recipeId = stringField(request3.input, "recipeId");
|
|
8939
9120
|
const digestLimits = {
|
|
8940
9121
|
maxFiles: options.maxRecipeWorkspaceFiles ?? 2e4,
|
|
8941
9122
|
maxBytes: options.maxRecipeWorkspaceBytes ?? 512 * 1024 * 1024
|
|
8942
9123
|
};
|
|
8943
9124
|
const sourceDigest = await digestStagedWorkspace(context.workspaceDir, digestLimits);
|
|
8944
|
-
const allowed = await policy.recipe(policyContext(context,
|
|
9125
|
+
const allowed = await policy.recipe(policyContext(context, request3, options, {
|
|
8945
9126
|
recipeIds: [...recipes.keys()].sort(),
|
|
8946
9127
|
recipeId,
|
|
8947
9128
|
sourceDigest
|
|
8948
9129
|
}));
|
|
8949
|
-
if (!allowed) return response(
|
|
9130
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8950
9131
|
const selected = recipes.get(recipeId);
|
|
8951
|
-
if (!selected) return response(
|
|
9132
|
+
if (!selected) return response(request3, false, "build recipe is not registered");
|
|
8952
9133
|
const staged = await stageWorkspace(context.workspaceDir, {
|
|
8953
9134
|
maxFiles: digestLimits.maxFiles,
|
|
8954
9135
|
maxBytes: digestLimits.maxBytes
|
|
@@ -8965,7 +9146,7 @@ async function recipe(context, request2, options, recipes, policy) {
|
|
|
8965
9146
|
const output = [result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
8966
9147
|
const ok = result.exitCode === 0 && !result.outputLimitExceeded && !result.timedOut;
|
|
8967
9148
|
const status = result.timedOut ? "timed out" : result.outputLimitExceeded ? "exceeded output limit" : ok ? "passed" : `failed with exit ${result.exitCode}`;
|
|
8968
|
-
return response(
|
|
9149
|
+
return response(request3, ok, `Recipe ${recipeId} ${status}.${output ? `
|
|
8969
9150
|
${output}` : ""}`, {
|
|
8970
9151
|
recipeId,
|
|
8971
9152
|
exitCode: result.exitCode,
|
|
@@ -9014,7 +9195,7 @@ async function runGoal(spec, attempt) {
|
|
|
9014
9195
|
const startedAt = now();
|
|
9015
9196
|
const attempts = [];
|
|
9016
9197
|
const boardErrors = [];
|
|
9017
|
-
const
|
|
9198
|
+
const emit4 = async (event) => {
|
|
9018
9199
|
if (!spec.onEvent) return;
|
|
9019
9200
|
try {
|
|
9020
9201
|
await spec.onEvent(event);
|
|
@@ -9027,7 +9208,7 @@ async function runGoal(spec, attempt) {
|
|
|
9027
9208
|
let costKnown = false;
|
|
9028
9209
|
const finish2 = async (stoppedReason) => {
|
|
9029
9210
|
const met = stoppedReason === "proof_passed";
|
|
9030
|
-
await
|
|
9211
|
+
await emit4(met ? { type: "goal_met", attempts: attempts.length, tokens, ...costKnown ? { costUsd } : {} } : {
|
|
9031
9212
|
type: "goal_abandoned",
|
|
9032
9213
|
reason: stoppedReason,
|
|
9033
9214
|
attempts: attempts.length,
|
|
@@ -9048,7 +9229,7 @@ async function runGoal(spec, attempt) {
|
|
|
9048
9229
|
if (spec.signal?.aborted) return finish2("cancelled");
|
|
9049
9230
|
if (spec.budget.deadline !== void 0 && now() >= spec.budget.deadline) return finish2("deadline");
|
|
9050
9231
|
const prompt = index === 1 ? openingPrompt(spec) : retryPrompt(spec, attempts.at(-1));
|
|
9051
|
-
await
|
|
9232
|
+
await emit4({ type: "attempt_started", attempt: index, prompt });
|
|
9052
9233
|
const outcome = await attempt({
|
|
9053
9234
|
attempt: index,
|
|
9054
9235
|
prompt,
|
|
@@ -9068,7 +9249,7 @@ async function runGoal(spec, attempt) {
|
|
|
9068
9249
|
...outcome.error === void 0 ? {} : { error: outcome.error }
|
|
9069
9250
|
});
|
|
9070
9251
|
if (outcome.gatePassed) return finish2("proof_passed");
|
|
9071
|
-
await
|
|
9252
|
+
await emit4({
|
|
9072
9253
|
type: "attempt_failed",
|
|
9073
9254
|
attempt: index,
|
|
9074
9255
|
feedback: outcome.feedback,
|
|
@@ -9117,7 +9298,7 @@ function createCodeRuntimeToolBroker(input, lease, role) {
|
|
|
9117
9298
|
readerId: `code-session:${lease.task.taskId}`,
|
|
9118
9299
|
readOnlyPrefixes: [".odla-references"]
|
|
9119
9300
|
});
|
|
9120
|
-
return role === "coding" ? broker : { execute: (context,
|
|
9301
|
+
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" }) };
|
|
9121
9302
|
}
|
|
9122
9303
|
function codeGoalSpec(payload) {
|
|
9123
9304
|
const goal = payload.goal;
|
|
@@ -9275,7 +9456,7 @@ async function appendCodeRuntimeEvent(control, command, event, refs) {
|
|
|
9275
9456
|
const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
|
|
9276
9457
|
await control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
9277
9458
|
}
|
|
9278
|
-
var import_crypto, import_promises5, import_path5, import_child_process4, import_promises6, import_path6, import_child_process5, import_process2, import_crypto2, import_crypto3, import_fs2, import_promises7, import_path7, import_promises8, import_os3, import_path8, import_ai4, import_promises9, import_path9, import_promises10, import_promises11, import_path10, import_crypto4, CODE_RUNTIME_PROTOCOL_VERSION, CodeRuntimeReconciler, CodeRuntimeControlError,
|
|
9459
|
+
var import_crypto, import_promises5, import_path5, import_child_process4, import_promises6, import_path6, import_child_process5, import_process2, import_crypto2, import_crypto3, import_fs2, import_promises7, import_path7, import_promises8, import_os3, import_path8, import_ai4, import_promises9, import_path9, import_promises10, import_promises11, import_path10, import_crypto4, CODE_RUNTIME_PROTOCOL_VERSION, CodeRuntimeReconciler, CodeRuntimeControlError, record5, invalid, RESERVED, SECRET, PATH, FORBIDDEN, ARTIFACT_PATH, PRIVATE_ARTIFACT_PART, SHA3, DIGEST3, ID3, RULE, DEFAULT_PREFIXES, DEFAULT_SUFFIXES, message, CodeRuntimeCheckpointManager, record22, SOURCE_LIMITS, RESERVED2, SECRET2, V1_SYSTEM_PROMPT, V2_SYSTEM_PROMPT, V3_SYSTEM_PROMPT, SYSTEM_PROMPT_FOR, DEFAULT_MAX_FILES, DEFAULT_MAX_RESULTS, DEFAULT_MAX_FILE_BYTES, DESTINATIONS, READ, LIST, SEARCH, GRAPH, PATCH, RECIPE, cache, shortId, GRAPH_TOOLS, MAX_MEMORY_BODY, POSITIVE, digestRuntimeValue, runtimeErrorMessage, CodePiRuntimeEngine;
|
|
9279
9460
|
var init_chunk_ANNX7VGK = __esm({
|
|
9280
9461
|
"../harness/dist/chunk-ANNX7VGK.js"() {
|
|
9281
9462
|
"use strict";
|
|
@@ -9353,7 +9534,7 @@ var init_chunk_ANNX7VGK = __esm({
|
|
|
9353
9534
|
code;
|
|
9354
9535
|
name = "CodeRuntimeControlError";
|
|
9355
9536
|
};
|
|
9356
|
-
|
|
9537
|
+
record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
9357
9538
|
invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
|
|
9358
9539
|
RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
9359
9540
|
SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
@@ -9500,7 +9681,7 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
|
|
|
9500
9681
|
sourceDigest: "payload"
|
|
9501
9682
|
});
|
|
9502
9683
|
cache = /* @__PURE__ */ new Map();
|
|
9503
|
-
shortId = (
|
|
9684
|
+
shortId = (id2) => id2.slice(id2.indexOf(":") + 1);
|
|
9504
9685
|
GRAPH_TOOLS = /* @__PURE__ */ new Set([
|
|
9505
9686
|
"sandbox.overview",
|
|
9506
9687
|
"sandbox.where_is",
|
|
@@ -9738,18 +9919,18 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
|
|
|
9738
9919
|
/** Report every brokered effect as it starts and finishes. */
|
|
9739
9920
|
#observed(command, active, broker) {
|
|
9740
9921
|
return {
|
|
9741
|
-
execute: async (context,
|
|
9922
|
+
execute: async (context, request3) => {
|
|
9742
9923
|
const startedAt = Date.now();
|
|
9743
9924
|
await this.#event(
|
|
9744
9925
|
command,
|
|
9745
|
-
{ type: "tool", phase: "started", tool:
|
|
9926
|
+
{ type: "tool", phase: "started", tool: request3.tool },
|
|
9746
9927
|
active.conversationRefs
|
|
9747
9928
|
).catch(() => void 0);
|
|
9748
|
-
const response2 = await broker.execute(context,
|
|
9929
|
+
const response2 = await broker.execute(context, request3);
|
|
9749
9930
|
await this.#event(command, {
|
|
9750
9931
|
type: "tool",
|
|
9751
9932
|
phase: "completed",
|
|
9752
|
-
tool:
|
|
9933
|
+
tool: request3.tool,
|
|
9753
9934
|
ok: response2.ok,
|
|
9754
9935
|
durationMs: Date.now() - startedAt
|
|
9755
9936
|
}, active.conversationRefs).catch(() => void 0);
|
|
@@ -9884,7 +10065,7 @@ async function waitForHostedPoll(milliseconds, signal) {
|
|
|
9884
10065
|
}
|
|
9885
10066
|
function isValidHostedSecurityPlan(value2, env) {
|
|
9886
10067
|
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;
|
|
9887
|
-
const validRoute = (
|
|
10068
|
+
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;
|
|
9888
10069
|
return validRoute(value2.routes?.discovery, "security.discovery") && validRoute(value2.routes?.validation, "security.validation");
|
|
9889
10070
|
}
|
|
9890
10071
|
function hostedSecurityCredential(value2) {
|
|
@@ -10210,20 +10391,20 @@ async function runCodeRuntime(input) {
|
|
|
10210
10391
|
}
|
|
10211
10392
|
}
|
|
10212
10393
|
function parseConnection(value2, appId, appEnv) {
|
|
10213
|
-
const root =
|
|
10214
|
-
const host =
|
|
10215
|
-
const offer =
|
|
10216
|
-
const binding =
|
|
10394
|
+
const root = record6(value2);
|
|
10395
|
+
const host = record6(root?.host);
|
|
10396
|
+
const offer = record6(root?.offer);
|
|
10397
|
+
const binding = record6(root?.binding);
|
|
10217
10398
|
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)) {
|
|
10218
10399
|
throw new Error("connect Code host returned an invalid response");
|
|
10219
10400
|
}
|
|
10220
10401
|
return root;
|
|
10221
10402
|
}
|
|
10222
10403
|
function apiFailure(action2, status, value2) {
|
|
10223
|
-
const message2 =
|
|
10404
|
+
const message2 = record6(record6(value2)?.error)?.message;
|
|
10224
10405
|
return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
|
|
10225
10406
|
}
|
|
10226
|
-
function
|
|
10407
|
+
function record6(value2) {
|
|
10227
10408
|
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
10228
10409
|
}
|
|
10229
10410
|
var import_node_fs16, import_node_os3, import_node_path15;
|
|
@@ -10486,9 +10667,9 @@ async function credentialCommand(parsed, deps = {}) {
|
|
|
10486
10667
|
}, doFetch, out, { optionalProjectCapabilities: ["app.manage"] });
|
|
10487
10668
|
const base = `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials`;
|
|
10488
10669
|
if (action2 === "revoke") {
|
|
10489
|
-
const
|
|
10490
|
-
if (!
|
|
10491
|
-
const response3 = await doFetch(`${base}/${encodeURIComponent(
|
|
10670
|
+
const id2 = parsed.positionals[2];
|
|
10671
|
+
if (!id2) throw new Error("credentials revoke requires the exact receipt id from credentials list");
|
|
10672
|
+
const response3 = await doFetch(`${base}/${encodeURIComponent(id2)}`, {
|
|
10492
10673
|
method: "DELETE",
|
|
10493
10674
|
headers: { authorization: `Bearer ${token}` }
|
|
10494
10675
|
});
|
|
@@ -10611,6 +10792,12 @@ Usage:
|
|
|
10611
10792
|
odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
|
|
10612
10793
|
odla-ai context remove <name> --yes [--json]
|
|
10613
10794
|
odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
|
|
10795
|
+
odla-ai monitor plan [--config odla.config.mjs] [--env prod] [--json]
|
|
10796
|
+
odla-ai monitor apply [--config odla.config.mjs] [--env prod] [--json] [--yes]
|
|
10797
|
+
odla-ai monitor run <probe-id> [--app <id>] [--env prod] [--json]
|
|
10798
|
+
odla-ai monitor status [--app <id>] [--context <name>] [--env prod] [--json]
|
|
10799
|
+
odla-ai monitor incidents [--app <id>] [--env prod] [--limit 100] [--runs] [--json]
|
|
10800
|
+
odla-ai monitor report [--app <id>] [--env prod] [--period daily|weekly] [--json]
|
|
10614
10801
|
odla-ai platform status [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
|
|
10615
10802
|
odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
|
|
10616
10803
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
@@ -10750,6 +10937,9 @@ Commands:
|
|
|
10750
10937
|
canary, collector ingest/scheduler trust, Cloudflare-owned
|
|
10751
10938
|
runtime metrics, and a machine verdict.
|
|
10752
10939
|
--json keeps auth progress on stderr for unattended agents.
|
|
10940
|
+
monitor Reconcile checked-in Kitesurf routes, rolling SLOs, spike/trend
|
|
10941
|
+
policies, and email digests; run probes manually and expose
|
|
10942
|
+
stable status, incident, and report JSON to agents and CI.
|
|
10753
10943
|
platform Read canonical fleet health, releases, provider load/freshness,
|
|
10754
10944
|
explicit unknowns, and next actions through a read-only grant.
|
|
10755
10945
|
provision Register services, compose integrations, persist credentials, optionally push secrets.
|
|
@@ -10972,7 +11162,7 @@ async function discussList(ctx, parsed) {
|
|
|
10972
11162
|
}
|
|
10973
11163
|
});
|
|
10974
11164
|
}
|
|
10975
|
-
async function discussRead(ctx,
|
|
11165
|
+
async function discussRead(ctx, id2, parsed) {
|
|
10976
11166
|
const requestedLimit = stringOpt(parsed.options.limit);
|
|
10977
11167
|
const requestedOffset = stringOpt(parsed.options.offset);
|
|
10978
11168
|
if (requestedLimit !== void 0 || requestedOffset !== void 0) {
|
|
@@ -10980,7 +11170,7 @@ async function discussRead(ctx, id, parsed) {
|
|
|
10980
11170
|
limit: requestedLimit ?? "200",
|
|
10981
11171
|
offset: requestedOffset ?? "0"
|
|
10982
11172
|
});
|
|
10983
|
-
const page2 = await request(ctx, "GET", `/topics/${encodeURIComponent(
|
|
11173
|
+
const page2 = await request(ctx, "GET", `/topics/${encodeURIComponent(id2)}?${query}`);
|
|
10984
11174
|
emit(
|
|
10985
11175
|
ctx,
|
|
10986
11176
|
page2,
|
|
@@ -11000,7 +11190,7 @@ async function discussRead(ctx, id, parsed) {
|
|
|
11000
11190
|
const page2 = await request(
|
|
11001
11191
|
ctx,
|
|
11002
11192
|
"GET",
|
|
11003
|
-
`/topics/${encodeURIComponent(
|
|
11193
|
+
`/topics/${encodeURIComponent(id2)}?limit=200&offset=${offset}`
|
|
11004
11194
|
);
|
|
11005
11195
|
topic = page2.topic;
|
|
11006
11196
|
for (const post of page2.posts) posts.set(post.id, post);
|
|
@@ -11042,20 +11232,20 @@ async function discussPost(ctx, parsed) {
|
|
|
11042
11232
|
});
|
|
11043
11233
|
emit(ctx, created, () => ctx.out.log(`opened topic ${created.id}`));
|
|
11044
11234
|
}
|
|
11045
|
-
async function discussReply(ctx,
|
|
11235
|
+
async function discussReply(ctx, id2, parsed) {
|
|
11046
11236
|
const created = await request(
|
|
11047
11237
|
ctx,
|
|
11048
11238
|
"POST",
|
|
11049
|
-
`/topics/${encodeURIComponent(
|
|
11239
|
+
`/topics/${encodeURIComponent(id2)}/replies`,
|
|
11050
11240
|
{ ...content(parsed), mutationId: writeMutationId(parsed) }
|
|
11051
11241
|
);
|
|
11052
11242
|
emit(ctx, created, () => ctx.out.log(`replied ${created.id}`));
|
|
11053
11243
|
}
|
|
11054
|
-
async function discussResolve(ctx,
|
|
11244
|
+
async function discussResolve(ctx, id2, resolved, parsed) {
|
|
11055
11245
|
const result = await request(
|
|
11056
11246
|
ctx,
|
|
11057
11247
|
"PATCH",
|
|
11058
|
-
`/topics/${encodeURIComponent(
|
|
11248
|
+
`/topics/${encodeURIComponent(id2)}`,
|
|
11059
11249
|
{ resolved, mutationId: writeMutationId(parsed) }
|
|
11060
11250
|
);
|
|
11061
11251
|
emit(ctx, result, () => ctx.out.log(`${resolved ? "resolved" : "reopened"} ${result.id}`));
|
|
@@ -11333,9 +11523,9 @@ var init_discuss_watch = __esm({
|
|
|
11333
11523
|
});
|
|
11334
11524
|
|
|
11335
11525
|
// src/discuss-command.ts
|
|
11336
|
-
function requireId(
|
|
11337
|
-
if (!
|
|
11338
|
-
return
|
|
11526
|
+
function requireId(id2, action2) {
|
|
11527
|
+
if (!id2) throw new Error(`"discuss ${action2}" needs a topic id`);
|
|
11528
|
+
return id2;
|
|
11339
11529
|
}
|
|
11340
11530
|
async function buildContext(parsed, deps) {
|
|
11341
11531
|
const context = await resolveOperatorContext(parsed, {
|
|
@@ -11370,7 +11560,7 @@ async function buildContext(parsed, deps) {
|
|
|
11370
11560
|
async function discussCommand(parsed, deps = {}) {
|
|
11371
11561
|
assertArgs(parsed, ALLOWED, 3);
|
|
11372
11562
|
const action2 = parsed.positionals[1];
|
|
11373
|
-
const
|
|
11563
|
+
const id2 = parsed.positionals[2];
|
|
11374
11564
|
if (!action2) throw new Error('"discuss" needs an action. Run "odla-ai help".');
|
|
11375
11565
|
const ctx = await buildContext(parsed, deps);
|
|
11376
11566
|
switch (action2) {
|
|
@@ -11380,17 +11570,17 @@ async function discussCommand(parsed, deps = {}) {
|
|
|
11380
11570
|
case "topics":
|
|
11381
11571
|
return discussList(ctx, parsed);
|
|
11382
11572
|
case "read":
|
|
11383
|
-
return discussRead(ctx, requireId(
|
|
11573
|
+
return discussRead(ctx, requireId(id2, "read"), parsed);
|
|
11384
11574
|
case "post":
|
|
11385
11575
|
return discussPost(ctx, parsed);
|
|
11386
11576
|
case "reply":
|
|
11387
|
-
return discussReply(ctx, requireId(
|
|
11577
|
+
return discussReply(ctx, requireId(id2, "reply"), parsed);
|
|
11388
11578
|
case "resolve":
|
|
11389
|
-
return discussResolve(ctx, requireId(
|
|
11579
|
+
return discussResolve(ctx, requireId(id2, "resolve"), parsed.options.reopen !== true, parsed);
|
|
11390
11580
|
case "who":
|
|
11391
11581
|
return discussWho(ctx, parsed);
|
|
11392
11582
|
case "watch": {
|
|
11393
|
-
const result = await discussWatch(ctx,
|
|
11583
|
+
const result = await discussWatch(ctx, id2, parsed);
|
|
11394
11584
|
if (!result.found) throw new WatchTimeoutError(result.cursor);
|
|
11395
11585
|
return;
|
|
11396
11586
|
}
|
|
@@ -11472,8 +11662,8 @@ function collectFields(parsed, allowClear) {
|
|
|
11472
11662
|
if (allowClear) out[spec.key] = null;
|
|
11473
11663
|
continue;
|
|
11474
11664
|
}
|
|
11475
|
-
const
|
|
11476
|
-
out[spec.key] = spec.num ? Number(
|
|
11665
|
+
const text3 = stringOpt(value2);
|
|
11666
|
+
out[spec.key] = spec.num ? Number(text3) : text3;
|
|
11477
11667
|
}
|
|
11478
11668
|
return out;
|
|
11479
11669
|
}
|
|
@@ -11486,31 +11676,31 @@ function collectEntityFields(entity, parsed, allowClear) {
|
|
|
11486
11676
|
if (entity === "task" && fields.column === "ready") fields.column = "todo";
|
|
11487
11677
|
return fields;
|
|
11488
11678
|
}
|
|
11489
|
-
function statusCol(entity,
|
|
11490
|
-
if (entity === "bug") return `${
|
|
11679
|
+
function statusCol(entity, record11) {
|
|
11680
|
+
if (entity === "bug") return `${record11.status ?? ""}/${record11.severity ?? ""}`;
|
|
11491
11681
|
if (entity === "task") {
|
|
11492
|
-
const state2 =
|
|
11493
|
-
return
|
|
11682
|
+
const state2 = record11.column === "todo" ? "ready" : String(record11.column ?? "");
|
|
11683
|
+
return record11.revision ? `${state2}; r${record11.revision}` : state2;
|
|
11494
11684
|
}
|
|
11495
|
-
return String(
|
|
11685
|
+
return String(record11.status ?? "");
|
|
11496
11686
|
}
|
|
11497
|
-
function referenceMarkup(entity,
|
|
11498
|
-
const label = (
|
|
11499
|
-
return `@[${label}](pm:${entity}/${
|
|
11687
|
+
function referenceMarkup(entity, record11) {
|
|
11688
|
+
const label = (record11.title?.trim() || `${entity} ${record11.id}`).replaceAll("]", ")");
|
|
11689
|
+
return `@[${label}](pm:${entity}/${record11.id})`;
|
|
11500
11690
|
}
|
|
11501
|
-
function studioRecordUrl(ctx, entity,
|
|
11691
|
+
function studioRecordUrl(ctx, entity, id2) {
|
|
11502
11692
|
return new URL(
|
|
11503
|
-
`/studio/pm/${STUDIO_SECTION[entity]}/${encodeURIComponent(
|
|
11693
|
+
`/studio/pm/${STUDIO_SECTION[entity]}/${encodeURIComponent(id2)}`,
|
|
11504
11694
|
ctx.platformUrl
|
|
11505
11695
|
).href;
|
|
11506
11696
|
}
|
|
11507
|
-
function studioRecordLink(ctx, entity,
|
|
11508
|
-
const label = (
|
|
11509
|
-
return `[${label}](${studioRecordUrl(ctx, entity,
|
|
11697
|
+
function studioRecordLink(ctx, entity, record11) {
|
|
11698
|
+
const label = (record11.title?.trim() || `${entity} ${record11.id}`).replaceAll("]", ")");
|
|
11699
|
+
return `[${label}](${studioRecordUrl(ctx, entity, record11.id)})`;
|
|
11510
11700
|
}
|
|
11511
|
-
function printRecord(ctx, entity,
|
|
11701
|
+
function printRecord(ctx, entity, record11) {
|
|
11512
11702
|
ctx.out.log(
|
|
11513
|
-
`${
|
|
11703
|
+
`${record11.id} [${statusCol(entity, record11)}] ${record11.appId} ${studioRecordLink(ctx, entity, record11)}`
|
|
11514
11704
|
);
|
|
11515
11705
|
}
|
|
11516
11706
|
function emit2(ctx, value2, human) {
|
|
@@ -11605,52 +11795,52 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
11605
11795
|
input,
|
|
11606
11796
|
mutationId: writeMutationId2(parsed)
|
|
11607
11797
|
});
|
|
11608
|
-
const
|
|
11609
|
-
emit2(ctx, res, () => ctx.out.log(`created ${entity}: ${studioRecordLink(ctx, entity,
|
|
11798
|
+
const record11 = { id: res.id, appId, title: String(input.title) };
|
|
11799
|
+
emit2(ctx, res, () => ctx.out.log(`created ${entity}: ${studioRecordLink(ctx, entity, record11)}`));
|
|
11610
11800
|
}
|
|
11611
|
-
async function pmGet(ctx, entity,
|
|
11612
|
-
const { record:
|
|
11613
|
-
emit2(ctx,
|
|
11801
|
+
async function pmGet(ctx, entity, id2) {
|
|
11802
|
+
const { record: record11 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id2)}`);
|
|
11803
|
+
emit2(ctx, record11, () => printRecord(ctx, entity, record11));
|
|
11614
11804
|
}
|
|
11615
|
-
async function pmReference(ctx, entity,
|
|
11616
|
-
const { record:
|
|
11805
|
+
async function pmReference(ctx, entity, id2) {
|
|
11806
|
+
const { record: record11 } = await pmRequest(
|
|
11617
11807
|
ctx,
|
|
11618
11808
|
"GET",
|
|
11619
|
-
`/${entity}/${encodeURIComponent(
|
|
11809
|
+
`/${entity}/${encodeURIComponent(id2)}`
|
|
11620
11810
|
);
|
|
11621
|
-
const markup = referenceMarkup(entity,
|
|
11622
|
-
emit2(ctx, { kind: `pm:${entity}`, id:
|
|
11811
|
+
const markup = referenceMarkup(entity, record11);
|
|
11812
|
+
emit2(ctx, { kind: `pm:${entity}`, id: record11.id, label: record11.title ?? "", markup }, () => {
|
|
11623
11813
|
ctx.out.log(markup);
|
|
11624
11814
|
});
|
|
11625
11815
|
}
|
|
11626
|
-
async function pmSet(ctx, entity,
|
|
11816
|
+
async function pmSet(ctx, entity, id2, parsed) {
|
|
11627
11817
|
const patch2 = collectEntityFields(entity, parsed, true);
|
|
11628
11818
|
if (Object.keys(patch2).length === 0)
|
|
11629
11819
|
throw new Error("pm set needs at least one field flag (e.g. --status doing, --assignee me, --no-assignee)");
|
|
11630
|
-
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(
|
|
11820
|
+
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id2)}`, {
|
|
11631
11821
|
patch: patch2,
|
|
11632
11822
|
mutationId: writeMutationId2(parsed)
|
|
11633
11823
|
});
|
|
11634
11824
|
emit2(ctx, res, () => {
|
|
11635
|
-
if (!res.record) return ctx.out.log(`updated ${entity} ${
|
|
11825
|
+
if (!res.record) return ctx.out.log(`updated ${entity} ${id2}`);
|
|
11636
11826
|
ctx.out.log(`${entity}: ${studioRecordLink(ctx, entity, res.record)} \u2192 ${statusCol(entity, res.record)}`);
|
|
11637
11827
|
});
|
|
11638
11828
|
}
|
|
11639
|
-
async function pmDone(ctx, entity,
|
|
11829
|
+
async function pmDone(ctx, entity, id2, parsed) {
|
|
11640
11830
|
const decisionId = stringOpt(parsed.options.decision);
|
|
11641
11831
|
if (decisionId && entity !== "bug") throw new Error("--decision is only valid when completing a bug");
|
|
11642
11832
|
const patch2 = { ...DONE[entity], ...decisionId ? { decisionId } : {} };
|
|
11643
|
-
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(
|
|
11833
|
+
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id2)}`, {
|
|
11644
11834
|
patch: patch2,
|
|
11645
11835
|
mutationId: writeMutationId2(parsed)
|
|
11646
11836
|
});
|
|
11647
11837
|
emit2(ctx, res, () => {
|
|
11648
|
-
const label = res.record ? studioRecordLink(ctx, entity, res.record) :
|
|
11838
|
+
const label = res.record ? studioRecordLink(ctx, entity, res.record) : id2;
|
|
11649
11839
|
const state2 = res.record ? statusCol(entity, res.record) : "done";
|
|
11650
11840
|
ctx.out.log(`${entity}: ${label} \u2192 ${state2}`);
|
|
11651
11841
|
});
|
|
11652
11842
|
}
|
|
11653
|
-
async function pmTaskLifecycle(ctx,
|
|
11843
|
+
async function pmTaskLifecycle(ctx, id2, action2, parsed) {
|
|
11654
11844
|
const rawRevision = stringOpt(parsed.options["expected-revision"]);
|
|
11655
11845
|
const expectedRevision = Number(rawRevision);
|
|
11656
11846
|
if (!rawRevision || !Number.isSafeInteger(expectedRevision) || expectedRevision < 1) {
|
|
@@ -11660,7 +11850,7 @@ async function pmTaskLifecycle(ctx, id, action2, parsed) {
|
|
|
11660
11850
|
const res = action2 === "ready" ? await pmRequest(
|
|
11661
11851
|
ctx,
|
|
11662
11852
|
"PATCH",
|
|
11663
|
-
`/task/${encodeURIComponent(
|
|
11853
|
+
`/task/${encodeURIComponent(id2)}`,
|
|
11664
11854
|
{
|
|
11665
11855
|
patch: {
|
|
11666
11856
|
...collectEntityFields("task", parsed, true),
|
|
@@ -11672,12 +11862,12 @@ async function pmTaskLifecycle(ctx, id, action2, parsed) {
|
|
|
11672
11862
|
) : await pmRequest(
|
|
11673
11863
|
ctx,
|
|
11674
11864
|
"POST",
|
|
11675
|
-
`/task/${encodeURIComponent(
|
|
11865
|
+
`/task/${encodeURIComponent(id2)}/${action2}`,
|
|
11676
11866
|
{ expectedRevision, mutationId }
|
|
11677
11867
|
);
|
|
11678
11868
|
emit2(ctx, res, () => {
|
|
11679
11869
|
const state2 = res.record ? statusCol("task", res.record) : action2;
|
|
11680
|
-
const label = res.record ? studioRecordLink(ctx, "task", res.record) :
|
|
11870
|
+
const label = res.record ? studioRecordLink(ctx, "task", res.record) : id2;
|
|
11681
11871
|
ctx.out.log(`task: ${label} \u2192 ${state2}`);
|
|
11682
11872
|
});
|
|
11683
11873
|
}
|
|
@@ -11706,9 +11896,9 @@ async function pmNext(ctx, parsed) {
|
|
|
11706
11896
|
const result = {
|
|
11707
11897
|
appId,
|
|
11708
11898
|
projectId,
|
|
11709
|
-
openGoals: goals.filter((
|
|
11710
|
-
doing: tasks.filter((
|
|
11711
|
-
ready: tasks.filter((
|
|
11899
|
+
openGoals: goals.filter((record11) => record11.status === "open"),
|
|
11900
|
+
doing: tasks.filter((record11) => record11.column === "doing"),
|
|
11901
|
+
ready: tasks.filter((record11) => record11.column === "todo")
|
|
11712
11902
|
};
|
|
11713
11903
|
emit2(ctx, result, () => {
|
|
11714
11904
|
ctx.out.log(`${appId}: goal-aligned work intake (read only)`);
|
|
@@ -11719,10 +11909,10 @@ async function pmNext(ctx, parsed) {
|
|
|
11719
11909
|
]) {
|
|
11720
11910
|
ctx.out.log(`${label}:`);
|
|
11721
11911
|
if (!records.length) ctx.out.log("- (none)");
|
|
11722
|
-
else for (const
|
|
11912
|
+
else for (const record11 of records) printRecord(
|
|
11723
11913
|
ctx,
|
|
11724
11914
|
label === "open goals" ? "goal" : "task",
|
|
11725
|
-
|
|
11915
|
+
record11
|
|
11726
11916
|
);
|
|
11727
11917
|
}
|
|
11728
11918
|
if (!result.openGoals.length) {
|
|
@@ -11746,9 +11936,9 @@ async function pmHandoff(ctx, parsed) {
|
|
|
11746
11936
|
const handoff = {
|
|
11747
11937
|
appId,
|
|
11748
11938
|
projectId,
|
|
11749
|
-
unmetGoals: goals.filter((
|
|
11750
|
-
activeTasks: tasks.filter((
|
|
11751
|
-
openBugs: bugs.filter((
|
|
11939
|
+
unmetGoals: goals.filter((record11) => record11.status !== "met"),
|
|
11940
|
+
activeTasks: tasks.filter((record11) => record11.column !== "done"),
|
|
11941
|
+
openBugs: bugs.filter((record11) => record11.status !== "fixed" && record11.status !== "wontfix")
|
|
11752
11942
|
};
|
|
11753
11943
|
const result = {
|
|
11754
11944
|
...handoff,
|
|
@@ -11767,17 +11957,17 @@ async function pmHandoff(ctx, parsed) {
|
|
|
11767
11957
|
]) {
|
|
11768
11958
|
ctx.out.log(`${label}:`);
|
|
11769
11959
|
if (!records.length) ctx.out.log("- (none)");
|
|
11770
|
-
else for (const
|
|
11960
|
+
else for (const record11 of records) printRecord(
|
|
11771
11961
|
ctx,
|
|
11772
11962
|
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
11773
|
-
|
|
11963
|
+
record11
|
|
11774
11964
|
);
|
|
11775
11965
|
}
|
|
11776
11966
|
});
|
|
11777
11967
|
}
|
|
11778
|
-
async function pmRemove(ctx, entity,
|
|
11779
|
-
await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(
|
|
11780
|
-
ctx.out.log(`deleted ${entity} ${
|
|
11968
|
+
async function pmRemove(ctx, entity, id2) {
|
|
11969
|
+
await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id2)}`);
|
|
11970
|
+
ctx.out.log(`deleted ${entity} ${id2}`);
|
|
11781
11971
|
}
|
|
11782
11972
|
var init_pm_actions = __esm({
|
|
11783
11973
|
"src/pm-actions.ts"() {
|
|
@@ -11789,15 +11979,15 @@ var init_pm_actions = __esm({
|
|
|
11789
11979
|
});
|
|
11790
11980
|
|
|
11791
11981
|
// src/pm-links.ts
|
|
11792
|
-
async function pmLink(ctx, entity,
|
|
11793
|
-
const { record:
|
|
11982
|
+
async function pmLink(ctx, entity, id2) {
|
|
11983
|
+
const { record: record11 } = await pmRequest(
|
|
11794
11984
|
ctx,
|
|
11795
11985
|
"GET",
|
|
11796
|
-
`/${entity}/${encodeURIComponent(
|
|
11986
|
+
`/${entity}/${encodeURIComponent(id2)}`
|
|
11797
11987
|
);
|
|
11798
|
-
const url = studioRecordUrl(ctx, entity,
|
|
11799
|
-
const markdown = studioRecordLink(ctx, entity,
|
|
11800
|
-
emit2(ctx, { kind: entity, id:
|
|
11988
|
+
const url = studioRecordUrl(ctx, entity, record11.id);
|
|
11989
|
+
const markdown = studioRecordLink(ctx, entity, record11);
|
|
11990
|
+
emit2(ctx, { kind: entity, id: record11.id, label: record11.title ?? "", url, markdown }, () => {
|
|
11801
11991
|
ctx.out.log(markdown);
|
|
11802
11992
|
});
|
|
11803
11993
|
}
|
|
@@ -11810,17 +12000,17 @@ var init_pm_links = __esm({
|
|
|
11810
12000
|
});
|
|
11811
12001
|
|
|
11812
12002
|
// src/pm-comments.ts
|
|
11813
|
-
async function pmComment(ctx, entity,
|
|
12003
|
+
async function pmComment(ctx, entity, id2, parsed) {
|
|
11814
12004
|
const body = stringOpt(parsed.options.body);
|
|
11815
12005
|
if (!body) throw new Error('pm comment needs --body "..."');
|
|
11816
|
-
await pmRequest(ctx, "POST", `/${entity}/${encodeURIComponent(
|
|
12006
|
+
await pmRequest(ctx, "POST", `/${entity}/${encodeURIComponent(id2)}/comments`, {
|
|
11817
12007
|
body,
|
|
11818
12008
|
mutationId: writeMutationId2(parsed)
|
|
11819
12009
|
});
|
|
11820
|
-
ctx.out.log(`commented on ${entity} ${
|
|
12010
|
+
ctx.out.log(`commented on ${entity} ${id2}`);
|
|
11821
12011
|
}
|
|
11822
|
-
async function pmComments(ctx, entity,
|
|
11823
|
-
const { messages } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(
|
|
12012
|
+
async function pmComments(ctx, entity, id2) {
|
|
12013
|
+
const { messages } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id2)}/comments`);
|
|
11824
12014
|
emit2(ctx, messages, () => {
|
|
11825
12015
|
if (messages.length === 0) ctx.out.log("(no comments)");
|
|
11826
12016
|
else for (const message2 of messages) {
|
|
@@ -11845,12 +12035,12 @@ function fieldLine(change) {
|
|
|
11845
12035
|
const before = change.before.length > 60 ? `${change.before.slice(0, 60)}\u2026` : change.before;
|
|
11846
12036
|
return `${change.field} (was: ${before.replace(/\s+/g, " ")})`;
|
|
11847
12037
|
}
|
|
11848
|
-
async function pmHistory(ctx, entity,
|
|
12038
|
+
async function pmHistory(ctx, entity, id2, parsed) {
|
|
11849
12039
|
const limit = numberOpt(parsed.options.limit, "--limit");
|
|
11850
12040
|
const page2 = await pmRequest(
|
|
11851
12041
|
ctx,
|
|
11852
12042
|
"GET",
|
|
11853
|
-
`/${entity}/${encodeURIComponent(
|
|
12043
|
+
`/${entity}/${encodeURIComponent(id2)}/history${limit === void 0 ? "" : `?limit=${limit}`}`
|
|
11854
12044
|
);
|
|
11855
12045
|
emit2(ctx, page2, () => {
|
|
11856
12046
|
if (!page2.entries.length) {
|
|
@@ -11952,16 +12142,16 @@ async function page(ctx, appId, cursor) {
|
|
|
11952
12142
|
}
|
|
11953
12143
|
return data;
|
|
11954
12144
|
}
|
|
11955
|
-
function recordState(
|
|
11956
|
-
if (
|
|
11957
|
-
return String(
|
|
12145
|
+
function recordState(record11) {
|
|
12146
|
+
if (record11.column) return record11.column === "todo" ? "ready" : record11.column;
|
|
12147
|
+
return String(record11.status ?? "");
|
|
11958
12148
|
}
|
|
11959
12149
|
function eventRecord(event) {
|
|
11960
12150
|
return event.payload.payload;
|
|
11961
12151
|
}
|
|
11962
12152
|
function eventLabel(event) {
|
|
11963
|
-
const
|
|
11964
|
-
if (
|
|
12153
|
+
const record11 = eventRecord(event);
|
|
12154
|
+
if (record11) return String(record11.title ?? event.payload.entityId);
|
|
11965
12155
|
const body = event.payload.message?.body?.replace(/\s+/g, " ").trim();
|
|
11966
12156
|
return body || event.payload.entityId;
|
|
11967
12157
|
}
|
|
@@ -11969,10 +12159,10 @@ function report2(ctx, parsed, result) {
|
|
|
11969
12159
|
if (ctx.json) ctx.out.log(JSON.stringify(result, null, 2));
|
|
11970
12160
|
else if (parsed.options.jsonl !== true && result.found) {
|
|
11971
12161
|
for (const event of result.events ?? []) {
|
|
11972
|
-
const
|
|
11973
|
-
const state2 =
|
|
12162
|
+
const record11 = eventRecord(event);
|
|
12163
|
+
const state2 = record11 ? recordState(record11) : "comment";
|
|
11974
12164
|
ctx.out.log(
|
|
11975
|
-
`${event.id} ${event.type} ${state2}${
|
|
12165
|
+
`${event.id} ${event.type} ${state2}${record11?.revision ? `; r${record11.revision}` : ""} ${eventLabel(event)}`
|
|
11976
12166
|
);
|
|
11977
12167
|
}
|
|
11978
12168
|
}
|
|
@@ -12046,8 +12236,8 @@ async function pmWatch(ctx, parsed) {
|
|
|
12046
12236
|
}
|
|
12047
12237
|
firstSuccess = false;
|
|
12048
12238
|
const matching = current.events.filter((event) => {
|
|
12049
|
-
const
|
|
12050
|
-
const state2 =
|
|
12239
|
+
const record11 = eventRecord(event);
|
|
12240
|
+
const state2 = record11 ? recordState(record11).toLowerCase() : "";
|
|
12051
12241
|
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);
|
|
12052
12242
|
});
|
|
12053
12243
|
for (const event of matching) {
|
|
@@ -12155,9 +12345,9 @@ async function pmProjectAdd(ctx, parsed) {
|
|
|
12155
12345
|
});
|
|
12156
12346
|
emit2(ctx, result, () => ctx.out.log(`created project: ${result.project.name} (${result.project.id})`));
|
|
12157
12347
|
}
|
|
12158
|
-
async function pmProjectUse(ctx,
|
|
12348
|
+
async function pmProjectUse(ctx, id2) {
|
|
12159
12349
|
if (!ctx.rootDir) throw new Error("pm project use needs a local project directory");
|
|
12160
|
-
const { project } = await pmRequest(ctx, "GET", `/project/${encodeURIComponent(
|
|
12350
|
+
const { project } = await pmRequest(ctx, "GET", `/project/${encodeURIComponent(id2)}`);
|
|
12161
12351
|
if (project.status !== "active") throw new Error(`project ${project.name} is ${project.status}, not active`);
|
|
12162
12352
|
writePmProjectContext(ctx.rootDir, { appId: project.appId, projectId: project.id });
|
|
12163
12353
|
emit2(ctx, project, () => ctx.out.log(`using ${project.appId} / ${project.name} (${project.id}) in this worktree`));
|
|
@@ -12183,9 +12373,9 @@ function allowedOptions(entity, action2) {
|
|
|
12183
12373
|
const entityOptions = action2 === "list" || action2 === "add" || action2 === "set" || action2 === "done" ? ENTITY_OPTIONS[entity][action2] : [];
|
|
12184
12374
|
return [...COMMON_OPTIONS, ...ACTION_OPTIONS[action2], ...entityOptions];
|
|
12185
12375
|
}
|
|
12186
|
-
function requireId2(
|
|
12187
|
-
if (!
|
|
12188
|
-
return
|
|
12376
|
+
function requireId2(id2, action2) {
|
|
12377
|
+
if (!id2) throw new Error(`"pm ... ${action2}" needs an item id`);
|
|
12378
|
+
return id2;
|
|
12189
12379
|
}
|
|
12190
12380
|
async function buildContext2(parsed, deps) {
|
|
12191
12381
|
const context = await resolveOperatorContext(parsed, {
|
|
@@ -12276,34 +12466,34 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
12276
12466
|
throw new Error(`pm ${action2} is only valid for tasks`);
|
|
12277
12467
|
}
|
|
12278
12468
|
const ctx = await buildContext2(parsed, deps);
|
|
12279
|
-
const
|
|
12469
|
+
const id2 = parsed.positionals[3];
|
|
12280
12470
|
switch (action2) {
|
|
12281
12471
|
case "list":
|
|
12282
12472
|
return pmList(ctx, entity, parsed);
|
|
12283
12473
|
case "add":
|
|
12284
12474
|
return pmAdd(ctx, entity, parsed);
|
|
12285
12475
|
case "get":
|
|
12286
|
-
return pmGet(ctx, entity, requireId2(
|
|
12476
|
+
return pmGet(ctx, entity, requireId2(id2, action2));
|
|
12287
12477
|
case "set":
|
|
12288
|
-
return pmSet(ctx, entity, requireId2(
|
|
12478
|
+
return pmSet(ctx, entity, requireId2(id2, action2), parsed);
|
|
12289
12479
|
case "done":
|
|
12290
|
-
return pmDone(ctx, entity, requireId2(
|
|
12480
|
+
return pmDone(ctx, entity, requireId2(id2, action2), parsed);
|
|
12291
12481
|
case "comment":
|
|
12292
|
-
return pmComment(ctx, entity, requireId2(
|
|
12482
|
+
return pmComment(ctx, entity, requireId2(id2, action2), parsed);
|
|
12293
12483
|
case "comments":
|
|
12294
|
-
return pmComments(ctx, entity, requireId2(
|
|
12484
|
+
return pmComments(ctx, entity, requireId2(id2, action2));
|
|
12295
12485
|
case "history":
|
|
12296
|
-
return pmHistory(ctx, entity, requireId2(
|
|
12486
|
+
return pmHistory(ctx, entity, requireId2(id2, action2), parsed);
|
|
12297
12487
|
case "rm":
|
|
12298
|
-
return pmRemove(ctx, entity, requireId2(
|
|
12488
|
+
return pmRemove(ctx, entity, requireId2(id2, action2));
|
|
12299
12489
|
case "link":
|
|
12300
|
-
return pmLink(ctx, entity, requireId2(
|
|
12490
|
+
return pmLink(ctx, entity, requireId2(id2, action2));
|
|
12301
12491
|
case "ref":
|
|
12302
|
-
return pmReference(ctx, entity, requireId2(
|
|
12492
|
+
return pmReference(ctx, entity, requireId2(id2, action2));
|
|
12303
12493
|
case "ready":
|
|
12304
12494
|
case "claim":
|
|
12305
12495
|
case "release":
|
|
12306
|
-
return pmTaskLifecycle(ctx, requireId2(
|
|
12496
|
+
return pmTaskLifecycle(ctx, requireId2(id2, action2), action2, parsed);
|
|
12307
12497
|
}
|
|
12308
12498
|
}
|
|
12309
12499
|
var ALIASES, COMMON_OPTIONS, ACTION_OPTIONS, ENTITY_OPTIONS;
|
|
@@ -12480,17 +12670,17 @@ async function platformStatus(parsed, deps) {
|
|
|
12480
12670
|
}
|
|
12481
12671
|
}
|
|
12482
12672
|
function isPlatformStatus(value2) {
|
|
12483
|
-
if (!
|
|
12484
|
-
if (!
|
|
12485
|
-
if (!
|
|
12673
|
+
if (!record7(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
|
|
12674
|
+
if (!record7(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
|
|
12675
|
+
if (!record7(value2.catalog) || !record7(value2.summary)) return false;
|
|
12486
12676
|
return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
|
|
12487
12677
|
}
|
|
12488
12678
|
function apiMessage(value2) {
|
|
12489
|
-
if (!
|
|
12490
|
-
const error =
|
|
12679
|
+
if (!record7(value2)) return "request failed";
|
|
12680
|
+
const error = record7(value2.error) ? value2.error : value2;
|
|
12491
12681
|
return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
|
|
12492
12682
|
}
|
|
12493
|
-
function
|
|
12683
|
+
function record7(value2) {
|
|
12494
12684
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
12495
12685
|
}
|
|
12496
12686
|
var init_platform_command = __esm({
|
|
@@ -12541,7 +12731,7 @@ function statusVerdict(reads) {
|
|
|
12541
12731
|
severity: "degraded"
|
|
12542
12732
|
});
|
|
12543
12733
|
}
|
|
12544
|
-
const performance =
|
|
12734
|
+
const performance = record8(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
|
|
12545
12735
|
if (performance?.status === "unavailable") {
|
|
12546
12736
|
reasons.push({
|
|
12547
12737
|
source: "liveSync",
|
|
@@ -12622,7 +12812,7 @@ function statusVerdict(reads) {
|
|
|
12622
12812
|
reasons
|
|
12623
12813
|
};
|
|
12624
12814
|
}
|
|
12625
|
-
function
|
|
12815
|
+
function record8(value2) {
|
|
12626
12816
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
12627
12817
|
}
|
|
12628
12818
|
function numeric2(value2) {
|
|
@@ -12656,7 +12846,7 @@ function printO11yStatus(status, out) {
|
|
|
12656
12846
|
out.log(
|
|
12657
12847
|
`o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
|
|
12658
12848
|
);
|
|
12659
|
-
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(
|
|
12849
|
+
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record9) : [];
|
|
12660
12850
|
const requests = routes.reduce(
|
|
12661
12851
|
(total, row) => total + numeric3(row.requests),
|
|
12662
12852
|
0
|
|
@@ -12668,39 +12858,39 @@ function printO11yStatus(status, out) {
|
|
|
12668
12858
|
out.log(
|
|
12669
12859
|
`application ${status.application.httpStatus} ${requests} requests ${errors} errors`
|
|
12670
12860
|
);
|
|
12671
|
-
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(
|
|
12861
|
+
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record9) : [];
|
|
12672
12862
|
out.log(
|
|
12673
12863
|
`application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
|
|
12674
12864
|
(row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
|
|
12675
12865
|
).join(", ") : "none observed"}`
|
|
12676
12866
|
);
|
|
12677
12867
|
out.log(liveSyncLine(status.liveSync));
|
|
12678
|
-
const canaryDurations =
|
|
12868
|
+
const canaryDurations = record9(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
|
|
12679
12869
|
out.log(
|
|
12680
12870
|
`canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
|
|
12681
12871
|
);
|
|
12682
|
-
const collectorIngest =
|
|
12683
|
-
const collectorStorage =
|
|
12872
|
+
const collectorIngest = record9(status.collector.body.ingest) ? status.collector.body.ingest : {};
|
|
12873
|
+
const collectorStorage = record9(collectorIngest.storage) ? collectorIngest.storage : {};
|
|
12684
12874
|
out.log(
|
|
12685
12875
|
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
12686
12876
|
);
|
|
12687
|
-
const providerMetrics =
|
|
12688
|
-
const providerCapacity =
|
|
12689
|
-
const workerMemory =
|
|
12877
|
+
const providerMetrics = record9(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
12878
|
+
const providerCapacity = record9(status.provider.body.capacity) ? status.provider.body.capacity : {};
|
|
12879
|
+
const workerMemory = record9(providerCapacity.memory) ? providerCapacity.memory : {};
|
|
12690
12880
|
out.log(
|
|
12691
12881
|
`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`
|
|
12692
12882
|
);
|
|
12693
12883
|
for (const line of providerCapacityLines(status.providerCapacity)) {
|
|
12694
12884
|
out.log(line);
|
|
12695
12885
|
}
|
|
12696
|
-
const coverage =
|
|
12697
|
-
const coverageCounts =
|
|
12698
|
-
const coverageBudget =
|
|
12886
|
+
const coverage = record9(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
|
|
12887
|
+
const coverageCounts = record9(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
|
|
12888
|
+
const coverageBudget = record9(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
|
|
12699
12889
|
out.log(
|
|
12700
12890
|
`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`
|
|
12701
12891
|
);
|
|
12702
12892
|
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
12703
|
-
const providerFreshness =
|
|
12893
|
+
const providerFreshness = record9(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
12704
12894
|
out.log(
|
|
12705
12895
|
`cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
|
|
12706
12896
|
);
|
|
@@ -12709,17 +12899,17 @@ function printO11yStatus(status, out) {
|
|
|
12709
12899
|
);
|
|
12710
12900
|
}
|
|
12711
12901
|
function providerCapacityLines(read3) {
|
|
12712
|
-
const resources =
|
|
12713
|
-
const durableObjects =
|
|
12714
|
-
const periodic =
|
|
12715
|
-
const storage =
|
|
12716
|
-
const d1 =
|
|
12717
|
-
const d1Activity =
|
|
12718
|
-
const d1Storage =
|
|
12719
|
-
const d1Latency =
|
|
12720
|
-
const r2 =
|
|
12721
|
-
const r2Operations =
|
|
12722
|
-
const r2Storage =
|
|
12902
|
+
const resources = record9(read3.body.resources) ? read3.body.resources : {};
|
|
12903
|
+
const durableObjects = record9(resources.durableObjects) ? resources.durableObjects : {};
|
|
12904
|
+
const periodic = record9(durableObjects.periodic) ? durableObjects.periodic : {};
|
|
12905
|
+
const storage = record9(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
|
|
12906
|
+
const d1 = record9(resources.d1) ? resources.d1 : {};
|
|
12907
|
+
const d1Activity = record9(d1.activity) ? d1.activity : {};
|
|
12908
|
+
const d1Storage = record9(d1.storage) ? d1.storage : {};
|
|
12909
|
+
const d1Latency = record9(d1Activity.latency) ? d1Activity.latency : {};
|
|
12910
|
+
const r2 = record9(resources.r2) ? resources.r2 : {};
|
|
12911
|
+
const r2Operations = record9(r2.operations) ? r2.operations : {};
|
|
12912
|
+
const r2Storage = record9(r2.storage) ? r2.storage : {};
|
|
12723
12913
|
const status = String(
|
|
12724
12914
|
read3.body.status ?? read3.body.error ?? "unavailable"
|
|
12725
12915
|
);
|
|
@@ -12730,11 +12920,11 @@ function providerCapacityLines(read3) {
|
|
|
12730
12920
|
];
|
|
12731
12921
|
}
|
|
12732
12922
|
function liveSyncLine(read3) {
|
|
12733
|
-
const performance =
|
|
12734
|
-
const commitToSend =
|
|
12923
|
+
const performance = record9(read3.body.performance) ? read3.body.performance : {};
|
|
12924
|
+
const commitToSend = record9(performance.commitToSend) ? performance.commitToSend : {};
|
|
12735
12925
|
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`;
|
|
12736
12926
|
}
|
|
12737
|
-
function
|
|
12927
|
+
function record9(value2) {
|
|
12738
12928
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
12739
12929
|
}
|
|
12740
12930
|
function numeric3(value2) {
|
|
@@ -12911,14 +13101,14 @@ function statusMinutes(value2) {
|
|
|
12911
13101
|
}
|
|
12912
13102
|
async function read2(url, headers, doFetch) {
|
|
12913
13103
|
const response2 = await doFetch(url, { headers });
|
|
12914
|
-
const
|
|
13104
|
+
const text3 = await response2.text();
|
|
12915
13105
|
let body = {};
|
|
12916
|
-
if (
|
|
13106
|
+
if (text3) {
|
|
12917
13107
|
try {
|
|
12918
|
-
const value2 = JSON.parse(
|
|
13108
|
+
const value2 = JSON.parse(text3);
|
|
12919
13109
|
body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
|
|
12920
13110
|
} catch {
|
|
12921
|
-
body = { message:
|
|
13111
|
+
body = { message: text3.slice(0, 300) };
|
|
12922
13112
|
}
|
|
12923
13113
|
}
|
|
12924
13114
|
return { httpStatus: response2.status, body };
|
|
@@ -12935,6 +13125,297 @@ var init_o11y_command = __esm({
|
|
|
12935
13125
|
}
|
|
12936
13126
|
});
|
|
12937
13127
|
|
|
13128
|
+
// src/monitoring-config.ts
|
|
13129
|
+
function monitoringWireConfig(cfg, env) {
|
|
13130
|
+
const monitoring = cfg.o11y?.monitoring;
|
|
13131
|
+
if (!monitoring) throw new Error("o11y.monitoring is not configured");
|
|
13132
|
+
if (!cfg.services.includes("o11y")) throw new Error('o11y.monitoring requires "o11y" in services');
|
|
13133
|
+
const authoredLink = cfg.links?.[env];
|
|
13134
|
+
if (!authoredLink) throw new Error(`links.${env} is required for live monitoring`);
|
|
13135
|
+
const baseUrl = new URL(authoredLink).toString();
|
|
13136
|
+
const selectedProbes = (monitoring.probes ?? []).filter((probe) => !probe.envs || probe.envs.includes(env));
|
|
13137
|
+
const probeIds = new Set(selectedProbes.map((probe) => probe.id));
|
|
13138
|
+
const selectedSlos = monitoring.slos.filter(
|
|
13139
|
+
(slo) => slo.indicator.type === "o11y-metric" || slo.indicator.probes.some((id2) => probeIds.has(id2))
|
|
13140
|
+
);
|
|
13141
|
+
if (selectedSlos.length === 0) throw new Error(`o11y.monitoring has no SLOs for env "${env}"`);
|
|
13142
|
+
for (const slo of selectedSlos) {
|
|
13143
|
+
if (slo.indicator.type !== "probe-success") continue;
|
|
13144
|
+
const unavailable = slo.indicator.probes.filter((id2) => !probeIds.has(id2));
|
|
13145
|
+
if (unavailable.length) throw new Error(`SLO "${slo.id}" mixes probes unavailable in env "${env}": ${unavailable.join(", ")}`);
|
|
13146
|
+
}
|
|
13147
|
+
const payload = {
|
|
13148
|
+
environment: env,
|
|
13149
|
+
baseUrl,
|
|
13150
|
+
probes: selectedProbes.map(normalizeProbe),
|
|
13151
|
+
slos: selectedSlos.map(normalizeSlo),
|
|
13152
|
+
...notification(cfg.o11y.monitoring.notifications?.[env])
|
|
13153
|
+
};
|
|
13154
|
+
const revision = `sha256:${(0, import_node_crypto4.createHash)("sha256").update(canonical(payload)).digest("hex")}`;
|
|
13155
|
+
return { revision, ...payload };
|
|
13156
|
+
}
|
|
13157
|
+
function normalizeProbe(probe) {
|
|
13158
|
+
return {
|
|
13159
|
+
id: probe.id,
|
|
13160
|
+
route: probe.route,
|
|
13161
|
+
cadenceMinutes: durationMinutes(probe.every),
|
|
13162
|
+
timeoutMs: probe.timeout ?? 2e4,
|
|
13163
|
+
...probe.ready?.selector ? { readySelector: probe.ready.selector } : {},
|
|
13164
|
+
expect: {
|
|
13165
|
+
status: probe.expect.status,
|
|
13166
|
+
...probe.expect.titleIncludes ? { titleIncludes: probe.expect.titleIncludes } : {},
|
|
13167
|
+
textIncludes: probe.expect.textIncludes ?? [],
|
|
13168
|
+
accessibility: probe.expect.accessibility ?? []
|
|
13169
|
+
},
|
|
13170
|
+
enabled: probe.enabled !== false
|
|
13171
|
+
};
|
|
13172
|
+
}
|
|
13173
|
+
function normalizeSlo(slo) {
|
|
13174
|
+
return {
|
|
13175
|
+
id: slo.id,
|
|
13176
|
+
name: slo.name ?? slo.id,
|
|
13177
|
+
indicator: normalizeIndicator(slo.indicator),
|
|
13178
|
+
target: slo.target,
|
|
13179
|
+
windowMinutes: durationMinutes(slo.window),
|
|
13180
|
+
spike: {
|
|
13181
|
+
badChecks: slo.alerts?.spike?.badChecks ?? 2,
|
|
13182
|
+
withinChecks: slo.alerts?.spike?.withinChecks ?? 3,
|
|
13183
|
+
recoverAfter: slo.alerts?.spike?.recoverAfter ?? 2
|
|
13184
|
+
},
|
|
13185
|
+
trend: {
|
|
13186
|
+
burnRate: slo.alerts?.trend?.burnRate ?? 1,
|
|
13187
|
+
shortMinutes: durationMinutes(slo.alerts?.trend?.shortWindow ?? "6h"),
|
|
13188
|
+
longMinutes: durationMinutes(slo.alerts?.trend?.longWindow ?? "3d"),
|
|
13189
|
+
minBadChecks: slo.alerts?.trend?.minBadChecks ?? 2
|
|
13190
|
+
},
|
|
13191
|
+
enabled: slo.enabled !== false
|
|
13192
|
+
};
|
|
13193
|
+
}
|
|
13194
|
+
function normalizeIndicator(indicator) {
|
|
13195
|
+
if (indicator.type === "probe-success") {
|
|
13196
|
+
return { type: "probe-success", probes: [...new Set(indicator.probes)] };
|
|
13197
|
+
}
|
|
13198
|
+
return {
|
|
13199
|
+
type: "o11y-metric",
|
|
13200
|
+
metric: indicator.metric,
|
|
13201
|
+
comparator: indicator.comparator,
|
|
13202
|
+
threshold: indicator.threshold,
|
|
13203
|
+
cadenceMinutes: durationMinutes(indicator.every),
|
|
13204
|
+
observationWindowMinutes: durationMinutes(indicator.observationWindow),
|
|
13205
|
+
...indicator.route ? { route: indicator.route } : {}
|
|
13206
|
+
};
|
|
13207
|
+
}
|
|
13208
|
+
function notification(policy) {
|
|
13209
|
+
if (!policy) return {};
|
|
13210
|
+
return {
|
|
13211
|
+
notifications: {
|
|
13212
|
+
email: [...new Set(policy.email.map((email) => email.trim().toLowerCase()))],
|
|
13213
|
+
timezone: policy.timezone,
|
|
13214
|
+
daily: policy.daily === void 0 ? "08:00" : policy.daily,
|
|
13215
|
+
weekly: policy.weekly === void 0 ? { day: "monday", at: "08:00" } : policy.weekly
|
|
13216
|
+
}
|
|
13217
|
+
};
|
|
13218
|
+
}
|
|
13219
|
+
function durationMinutes(value2) {
|
|
13220
|
+
const match = /^(\d+)(m|h|d)$/.exec(value2);
|
|
13221
|
+
if (!match) throw new Error(`unsupported duration ${value2}`);
|
|
13222
|
+
const amount = Number(match[1]);
|
|
13223
|
+
return amount * (match[2] === "d" ? 1440 : match[2] === "h" ? 60 : 1);
|
|
13224
|
+
}
|
|
13225
|
+
function canonical(value2) {
|
|
13226
|
+
if (Array.isArray(value2)) return `[${value2.map(canonical).join(",")}]`;
|
|
13227
|
+
if (value2 && typeof value2 === "object") {
|
|
13228
|
+
return `{${Object.entries(value2).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`).join(",")}}`;
|
|
13229
|
+
}
|
|
13230
|
+
return JSON.stringify(value2);
|
|
13231
|
+
}
|
|
13232
|
+
var import_node_crypto4;
|
|
13233
|
+
var init_monitoring_config = __esm({
|
|
13234
|
+
"src/monitoring-config.ts"() {
|
|
13235
|
+
"use strict";
|
|
13236
|
+
init_cjs_shims();
|
|
13237
|
+
import_node_crypto4 = require("crypto");
|
|
13238
|
+
}
|
|
13239
|
+
});
|
|
13240
|
+
|
|
13241
|
+
// src/monitor-command.ts
|
|
13242
|
+
async function monitorCommand(parsed, deps = {}) {
|
|
13243
|
+
assertArgs(parsed, OPTIONS, 3);
|
|
13244
|
+
const action2 = parsed.positionals[1] ?? "status";
|
|
13245
|
+
if (!["plan", "apply", "run", "status", "incidents", "report"].includes(action2)) {
|
|
13246
|
+
throw new Error(`unknown monitor action "${action2}". Try "odla-ai monitor status --json".`);
|
|
13247
|
+
}
|
|
13248
|
+
const context = await resolveOperatorContext(parsed, {
|
|
13249
|
+
allowMissingConfig: action2 !== "plan" && action2 !== "apply",
|
|
13250
|
+
requireApp: true
|
|
13251
|
+
});
|
|
13252
|
+
if ((action2 === "plan" || action2 === "apply") && context.config.status !== "loaded") {
|
|
13253
|
+
throw new Error(`monitor ${action2} requires odla.config.mjs`);
|
|
13254
|
+
}
|
|
13255
|
+
const env = context.environment.value ?? context.cfg.envs[0] ?? "prod";
|
|
13256
|
+
const appId = context.app.value;
|
|
13257
|
+
const doFetch = deps.fetch ?? fetch;
|
|
13258
|
+
const out = deps.stdout ?? console;
|
|
13259
|
+
const token = await getDeveloperToken(
|
|
13260
|
+
context.cfg,
|
|
13261
|
+
{
|
|
13262
|
+
configPath: context.cfg.configPath,
|
|
13263
|
+
token: stringOpt(parsed.options.token),
|
|
13264
|
+
email: stringOpt(parsed.options.email),
|
|
13265
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
13266
|
+
openApprovalUrl: deps.openUrl
|
|
13267
|
+
},
|
|
13268
|
+
doFetch,
|
|
13269
|
+
out,
|
|
13270
|
+
action2 === "apply" || action2 === "run" ? { optionalProjectCapabilities: ["app.manage"] } : {}
|
|
13271
|
+
);
|
|
13272
|
+
const base = `${context.cfg.platformUrl}/o11y/${encodeURIComponent(appId)}/monitoring`;
|
|
13273
|
+
const headers = { authorization: `Bearer ${token}`, "content-type": "application/json" };
|
|
13274
|
+
const jsonOutput = parsed.options.json === true;
|
|
13275
|
+
if (action2 === "plan" || action2 === "apply") {
|
|
13276
|
+
const desired = monitoringWireConfig(context.cfg, env);
|
|
13277
|
+
const live = await request2(`${base}?env=${encodeURIComponent(env)}`, { headers }, doFetch);
|
|
13278
|
+
const currentRevision = record10(live.config) ? string(live.config.revision) : null;
|
|
13279
|
+
const changed = currentRevision !== desired.revision;
|
|
13280
|
+
const plan = {
|
|
13281
|
+
schemaVersion: 1,
|
|
13282
|
+
appId,
|
|
13283
|
+
env,
|
|
13284
|
+
currentRevision,
|
|
13285
|
+
desiredRevision: desired.revision,
|
|
13286
|
+
changed,
|
|
13287
|
+
probes: desired.probes.map((probe) => ({ id: probe.id, route: probe.route, cadenceMinutes: probe.cadenceMinutes })),
|
|
13288
|
+
slos: desired.slos.map((slo) => ({ id: slo.id, indicator: slo.indicator, target: slo.target, windowMinutes: slo.windowMinutes })),
|
|
13289
|
+
notifications: desired.notifications ? { recipients: desired.notifications.email.length, timezone: desired.notifications.timezone, daily: desired.notifications.daily, weekly: desired.notifications.weekly } : null
|
|
13290
|
+
};
|
|
13291
|
+
if (action2 === "plan") {
|
|
13292
|
+
emit3(plan, jsonOutput, out, () => {
|
|
13293
|
+
out.log(`monitor plan ${appId}/${env}: ${changed ? "changes pending" : "in sync"}`);
|
|
13294
|
+
out.log(`revision ${currentRevision ?? "not configured"} -> ${desired.revision}`);
|
|
13295
|
+
for (const probe of desired.probes) out.log(`probe ${probe.id} ${probe.route} every ${probe.cadenceMinutes}m`);
|
|
13296
|
+
for (const slo of desired.slos) out.log(`slo ${slo.id} ${slo.indicator.type} ${(slo.target * 100).toFixed(3)}% ${slo.windowMinutes}m`);
|
|
13297
|
+
});
|
|
13298
|
+
return;
|
|
13299
|
+
}
|
|
13300
|
+
if ((env === "prod" || env === "production") && parsed.options.yes !== true) {
|
|
13301
|
+
throw new Error(`refusing to apply live monitoring for "${env}" without --yes; run monitor plan first`);
|
|
13302
|
+
}
|
|
13303
|
+
if (!changed) {
|
|
13304
|
+
emit3({ ...plan, applied: false }, jsonOutput, out, () => out.log(`monitor apply ${appId}/${env}: already in sync`));
|
|
13305
|
+
return;
|
|
13306
|
+
}
|
|
13307
|
+
const applied = await request2(`${base}?env=${encodeURIComponent(env)}`, {
|
|
13308
|
+
method: "PUT",
|
|
13309
|
+
headers,
|
|
13310
|
+
body: JSON.stringify(desired)
|
|
13311
|
+
}, doFetch);
|
|
13312
|
+
emit3({ schemaVersion: 1, appId, env, ...applied }, jsonOutput, out, () => out.log(`monitor apply ${appId}/${env}: ${applied.changed === true ? "applied" : "unchanged"} ${desired.revision}`));
|
|
13313
|
+
return;
|
|
13314
|
+
}
|
|
13315
|
+
if (action2 === "run") {
|
|
13316
|
+
const probeId = parsed.positionals[2];
|
|
13317
|
+
if (!probeId) throw new Error("monitor run requires a probe id");
|
|
13318
|
+
const result2 = await request2(`${base}/probes/${encodeURIComponent(probeId)}/run?env=${encodeURIComponent(env)}`, {
|
|
13319
|
+
method: "POST",
|
|
13320
|
+
headers
|
|
13321
|
+
}, doFetch);
|
|
13322
|
+
emit3(result2, jsonOutput, out, () => {
|
|
13323
|
+
const run = record10(result2.run) ? result2.run : {};
|
|
13324
|
+
out.log(`monitor run ${appId}/${env}/${probeId}: ${string(run.outcome) ?? "unknown"}${run.failure_code ? ` (${String(run.failure_code)})` : ""}`);
|
|
13325
|
+
});
|
|
13326
|
+
return;
|
|
13327
|
+
}
|
|
13328
|
+
let path = action2;
|
|
13329
|
+
if (action2 === "report") {
|
|
13330
|
+
const period = stringOpt(parsed.options.period) ?? "daily";
|
|
13331
|
+
if (period !== "daily" && period !== "weekly") throw new Error("--period must be daily or weekly");
|
|
13332
|
+
path = `report?period=${period}`;
|
|
13333
|
+
} else if (action2 === "incidents") {
|
|
13334
|
+
const params = new URLSearchParams({ limit: String(numberOpt(parsed.options.limit, "--limit") ?? 100) });
|
|
13335
|
+
if (boolOpt(parsed.options.runs) === true) params.set("runs", "true");
|
|
13336
|
+
path = `incidents?${params}`;
|
|
13337
|
+
}
|
|
13338
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
13339
|
+
const result = await request2(`${base}/${path}${separator}env=${encodeURIComponent(env)}`, { headers }, doFetch);
|
|
13340
|
+
emit3(result, jsonOutput, out, () => printRead(action2, appId, env, result, out));
|
|
13341
|
+
}
|
|
13342
|
+
async function request2(url, init, doFetch) {
|
|
13343
|
+
const response2 = await doFetch(url, init);
|
|
13344
|
+
const text3 = await response2.text();
|
|
13345
|
+
let body = {};
|
|
13346
|
+
try {
|
|
13347
|
+
const parsed = text3 ? JSON.parse(text3) : {};
|
|
13348
|
+
body = record10(parsed) ? parsed : { value: parsed };
|
|
13349
|
+
} catch {
|
|
13350
|
+
body = { message: text3.slice(0, 500) };
|
|
13351
|
+
}
|
|
13352
|
+
if (!response2.ok) {
|
|
13353
|
+
const error = record10(body.error) ? body.error : body;
|
|
13354
|
+
throw new Error(string(error.message) ?? string(error.code) ?? `monitor request failed (${response2.status})`);
|
|
13355
|
+
}
|
|
13356
|
+
return body;
|
|
13357
|
+
}
|
|
13358
|
+
function printRead(action2, appId, env, result, out) {
|
|
13359
|
+
if (action2 === "status") {
|
|
13360
|
+
out.log(`monitor status ${appId}/${env}: ${String(result.overall ?? (result.configured === false ? "not configured" : "unknown"))}`);
|
|
13361
|
+
const slos = Array.isArray(result.slos) ? result.slos.filter(record10) : [];
|
|
13362
|
+
for (const slo of slos) out.log(`slo ${String(slo.id)} ${String(slo.state)} ${percent(slo.observed)} observed ${percent(slo.budgetRemaining)} budget remaining`);
|
|
13363
|
+
const incidents = Array.isArray(result.openIncidents) ? result.openIncidents.length : 0;
|
|
13364
|
+
const gaps = Array.isArray(result.monitoringGaps) ? result.monitoringGaps.length : 0;
|
|
13365
|
+
out.log(`open incidents ${incidents}`);
|
|
13366
|
+
out.log(`monitoring gaps ${gaps}`);
|
|
13367
|
+
return;
|
|
13368
|
+
}
|
|
13369
|
+
if (action2 === "incidents") {
|
|
13370
|
+
const incidents = Array.isArray(result.incidents) ? result.incidents.filter(record10) : [];
|
|
13371
|
+
out.log(`monitor incidents ${appId}/${env}: ${incidents.length}`);
|
|
13372
|
+
for (const incident2 of incidents) out.log(`${String(incident2.state)} ${String(incident2.kind)} ${String(incident2.slo_id)} ${new Date(Number(incident2.opened_at)).toISOString()}`);
|
|
13373
|
+
return;
|
|
13374
|
+
}
|
|
13375
|
+
out.log(`monitor report ${appId}/${env}: ${String(result.period)} ${String(result.overall)}`);
|
|
13376
|
+
const probes = Array.isArray(result.probes) ? result.probes.filter(record10) : [];
|
|
13377
|
+
for (const probe of probes) out.log(`probe ${String(probe.id)} ${Number(probe.good)} good ${Number(probe.bad)} bad ${Number(probe.unknown)} unknown`);
|
|
13378
|
+
}
|
|
13379
|
+
function emit3(value2, json, out, human) {
|
|
13380
|
+
if (json) out.log(JSON.stringify(value2, null, 2));
|
|
13381
|
+
else human();
|
|
13382
|
+
}
|
|
13383
|
+
function record10(value2) {
|
|
13384
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
13385
|
+
}
|
|
13386
|
+
function string(value2) {
|
|
13387
|
+
return typeof value2 === "string" ? value2 : null;
|
|
13388
|
+
}
|
|
13389
|
+
function percent(value2) {
|
|
13390
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? `${(value2 * 100).toFixed(2)}%` : "unknown";
|
|
13391
|
+
}
|
|
13392
|
+
var OPTIONS;
|
|
13393
|
+
var init_monitor_command = __esm({
|
|
13394
|
+
"src/monitor-command.ts"() {
|
|
13395
|
+
"use strict";
|
|
13396
|
+
init_cjs_shims();
|
|
13397
|
+
init_argv();
|
|
13398
|
+
init_monitoring_config();
|
|
13399
|
+
init_operator_context();
|
|
13400
|
+
init_token();
|
|
13401
|
+
OPTIONS = [
|
|
13402
|
+
"config",
|
|
13403
|
+
"context",
|
|
13404
|
+
"platform",
|
|
13405
|
+
"token",
|
|
13406
|
+
"email",
|
|
13407
|
+
"json",
|
|
13408
|
+
"app",
|
|
13409
|
+
"env",
|
|
13410
|
+
"open",
|
|
13411
|
+
"yes",
|
|
13412
|
+
"period",
|
|
13413
|
+
"limit",
|
|
13414
|
+
"runs"
|
|
13415
|
+
];
|
|
13416
|
+
}
|
|
13417
|
+
});
|
|
13418
|
+
|
|
12938
13419
|
// src/integration-provision.ts
|
|
12939
13420
|
async function provisionIntegrationSeeds(doFetch, endpoint, tenantId, dbKey, integrations, env, out) {
|
|
12940
13421
|
const base = `${endpoint}/app/${encodeURIComponent(tenantId)}`;
|
|
@@ -13104,8 +13585,8 @@ function runtimeUrl(cfg, suffix = "") {
|
|
|
13104
13585
|
return `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials${suffix}`;
|
|
13105
13586
|
}
|
|
13106
13587
|
async function safeError(response2) {
|
|
13107
|
-
const
|
|
13108
|
-
return redactSecrets(
|
|
13588
|
+
const text3 = await response2.text();
|
|
13589
|
+
return redactSecrets(text3.slice(0, 1e3));
|
|
13109
13590
|
}
|
|
13110
13591
|
async function finish(doFetch, cfg, token, sessionId, method) {
|
|
13111
13592
|
return doFetch(runtimeUrl(cfg, `/${encodeURIComponent(sessionId)}`), {
|
|
@@ -13129,7 +13610,7 @@ async function deliverRuntimeCredentials(cfg, options) {
|
|
|
13129
13610
|
},
|
|
13130
13611
|
body: JSON.stringify({
|
|
13131
13612
|
env: options.env,
|
|
13132
|
-
idempotencyKey: `wrangler:${(0,
|
|
13613
|
+
idempotencyKey: `wrangler:${(0, import_node_crypto5.randomUUID)()}`,
|
|
13133
13614
|
target
|
|
13134
13615
|
})
|
|
13135
13616
|
});
|
|
@@ -13179,12 +13660,12 @@ async function deliverRuntimeCredentials(cfg, options) {
|
|
|
13179
13660
|
...values.ODLA_O11Y_TOKEN ? { o11yToken: values.ODLA_O11Y_TOKEN } : {}
|
|
13180
13661
|
};
|
|
13181
13662
|
}
|
|
13182
|
-
var
|
|
13663
|
+
var import_node_crypto5;
|
|
13183
13664
|
var init_runtime_credentials = __esm({
|
|
13184
13665
|
"src/runtime-credentials.ts"() {
|
|
13185
13666
|
"use strict";
|
|
13186
13667
|
init_cjs_shims();
|
|
13187
|
-
|
|
13668
|
+
import_node_crypto5 = require("crypto");
|
|
13188
13669
|
init_redact();
|
|
13189
13670
|
init_wrangler();
|
|
13190
13671
|
}
|
|
@@ -13584,6 +14065,7 @@ var init_surface = __esm({
|
|
|
13584
14065
|
doctor: {},
|
|
13585
14066
|
help: {},
|
|
13586
14067
|
init: {},
|
|
14068
|
+
monitor: { plan: {}, apply: {}, run: {}, status: {}, incidents: {}, report: {} },
|
|
13587
14069
|
o11y: { status: {} },
|
|
13588
14070
|
operations: { get: {}, wait: {} },
|
|
13589
14071
|
platform: {
|
|
@@ -13798,12 +14280,12 @@ var init_runbook_actions = __esm({
|
|
|
13798
14280
|
});
|
|
13799
14281
|
|
|
13800
14282
|
// src/runbook-import.ts
|
|
13801
|
-
function parseRunbook(
|
|
13802
|
-
let rest =
|
|
14283
|
+
function parseRunbook(text3, slug) {
|
|
14284
|
+
let rest = text3;
|
|
13803
14285
|
const meta = {};
|
|
13804
|
-
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(
|
|
14286
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text3);
|
|
13805
14287
|
if (fm) {
|
|
13806
|
-
rest =
|
|
14288
|
+
rest = text3.slice(fm[0].length);
|
|
13807
14289
|
for (const line of fm[1].split(/\r?\n/)) {
|
|
13808
14290
|
const pair = /^(\w+)\s*:\s*(.+)$/.exec(line.trim());
|
|
13809
14291
|
if (!pair) continue;
|
|
@@ -14657,9 +15139,9 @@ function printHostedSecurityIntent(out, intent) {
|
|
|
14657
15139
|
}
|
|
14658
15140
|
function assertHostedSecurityPlanReady(plan) {
|
|
14659
15141
|
const reasons = [];
|
|
14660
|
-
for (const [label,
|
|
14661
|
-
if (!
|
|
14662
|
-
if (!
|
|
15142
|
+
for (const [label, route3] of Object.entries(plan.routes)) {
|
|
15143
|
+
if (!route3.enabled) reasons.push(`${label} is disabled`);
|
|
15144
|
+
if (!route3.credentialReady) reasons.push(`${label} provider credential is unavailable`);
|
|
14663
15145
|
}
|
|
14664
15146
|
if (!plan.independent) reasons.push("discovery and validation are not independently routed");
|
|
14665
15147
|
if (plan.ready && reasons.length === 0) return;
|
|
@@ -14713,20 +15195,20 @@ function enforceHostedReportGate(report4, parsed, out, emitSuccess) {
|
|
|
14713
15195
|
out.log(`security gate passed: 0 confirmed >= ${failOn}; 0 leads >= ${failOnCandidates ?? "disabled"}; coverage ${report4.coverageStatus}. This is not proof that the application is secure.`);
|
|
14714
15196
|
}
|
|
14715
15197
|
}
|
|
14716
|
-
function printHostedSecurityPlanRoute(out, label,
|
|
14717
|
-
const readiness =
|
|
14718
|
-
|
|
14719
|
-
|
|
15198
|
+
function printHostedSecurityPlanRoute(out, label, route3) {
|
|
15199
|
+
const readiness = route3.enabled && route3.credentialReady ? "ready" : [
|
|
15200
|
+
route3.enabled ? void 0 : "disabled",
|
|
15201
|
+
route3.credentialReady ? void 0 : "credential unavailable"
|
|
14720
15202
|
].filter(Boolean).join(", ");
|
|
14721
|
-
out.log(` ${label}: ${
|
|
14722
|
-
out.log(` bounds: ${
|
|
15203
|
+
out.log(` ${label}: ${route3.provider}/${route3.model} \xB7 policy v${route3.policyVersion} \xB7 ${readiness}`);
|
|
15204
|
+
out.log(` bounds: ${route3.maxCallsPerRun} calls/run \xB7 ${route3.maxInputBytes} input bytes/call \xB7 ${route3.maxOutputTokens} output tokens/call`);
|
|
14723
15205
|
}
|
|
14724
15206
|
function printHostedCoverage(out, job) {
|
|
14725
15207
|
const coverage = job.coverage;
|
|
14726
15208
|
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}` : ""}`);
|
|
14727
15209
|
}
|
|
14728
|
-
function routeLabel(
|
|
14729
|
-
return `${
|
|
15210
|
+
function routeLabel(route3) {
|
|
15211
|
+
return `${route3.provider}/${route3.model}${route3.policyVersion ? ` policy v${route3.policyVersion}` : ""}`;
|
|
14730
15212
|
}
|
|
14731
15213
|
function hostedSeverity(value2, flag) {
|
|
14732
15214
|
if (HOSTED_SEVERITIES.includes(value2)) {
|
|
@@ -14815,11 +15297,11 @@ function selectEnv(requested, declared, configPath, rootDir) {
|
|
|
14815
15297
|
}
|
|
14816
15298
|
return env;
|
|
14817
15299
|
}
|
|
14818
|
-
async function injectedToken(options,
|
|
14819
|
-
const value2 = options.token ?? await options.getToken?.(Object.freeze({ ...
|
|
15300
|
+
async function injectedToken(options, request3) {
|
|
15301
|
+
const value2 = options.token ?? await options.getToken?.(Object.freeze({ ...request3 }));
|
|
14820
15302
|
if (typeof value2 !== "string" || value2.length < 8 || value2.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value2)) {
|
|
14821
15303
|
throw new Error(
|
|
14822
|
-
|
|
15304
|
+
request3.selfAudit ? "Self-audit requires an injected, scoped platform security token" : "Hosted security requires an injected app developer token or getToken callback"
|
|
14823
15305
|
);
|
|
14824
15306
|
}
|
|
14825
15307
|
return value2;
|
|
@@ -15157,11 +15639,11 @@ async function runLocalSecurityCommand(parsed, dependencies) {
|
|
|
15157
15639
|
sourceDisclosureAck: parsed.options["ack-redacted-source"] === true ? "redacted" : void 0,
|
|
15158
15640
|
fetch: doFetch,
|
|
15159
15641
|
stdout: out,
|
|
15160
|
-
getToken: async (
|
|
15161
|
-
if (
|
|
15642
|
+
getToken: async (request3) => {
|
|
15643
|
+
if (request3.scope === "platform:security:self") {
|
|
15162
15644
|
return getScopedPlatformToken({
|
|
15163
|
-
platform:
|
|
15164
|
-
scope:
|
|
15645
|
+
platform: request3.platform,
|
|
15646
|
+
scope: request3.scope,
|
|
15165
15647
|
email: stringOpt(parsed.options.email),
|
|
15166
15648
|
open,
|
|
15167
15649
|
fetch: doFetch,
|
|
@@ -15170,7 +15652,7 @@ async function runLocalSecurityCommand(parsed, dependencies) {
|
|
|
15170
15652
|
});
|
|
15171
15653
|
}
|
|
15172
15654
|
const cfg = await loadProjectConfig(configPath);
|
|
15173
|
-
if (platformAudience(cfg.platformUrl) !== platformAudience(
|
|
15655
|
+
if (platformAudience(cfg.platformUrl) !== platformAudience(request3.platform)) {
|
|
15174
15656
|
throw new Error("--platform cannot reuse a project developer token from another platform; update odla.config.mjs and authenticate there");
|
|
15175
15657
|
}
|
|
15176
15658
|
return getDeveloperToken(
|
|
@@ -15408,10 +15890,10 @@ async function runCli(argv2 = process.argv.slice(2), dependencies = {}) {
|
|
|
15408
15890
|
}
|
|
15409
15891
|
if (command === "bug") {
|
|
15410
15892
|
const action2 = parsed.positionals[1] ?? "list";
|
|
15411
|
-
const
|
|
15893
|
+
const canonical2 = action2 === "report" || action2 === "create" ? "add" : action2;
|
|
15412
15894
|
await pmCommand({
|
|
15413
15895
|
...parsed,
|
|
15414
|
-
positionals: ["pm", "bug",
|
|
15896
|
+
positionals: ["pm", "bug", canonical2, ...parsed.positionals.slice(2)]
|
|
15415
15897
|
}, runtime);
|
|
15416
15898
|
return;
|
|
15417
15899
|
}
|
|
@@ -15423,6 +15905,10 @@ async function runCli(argv2 = process.argv.slice(2), dependencies = {}) {
|
|
|
15423
15905
|
await o11yCommand(parsed, runtime);
|
|
15424
15906
|
return;
|
|
15425
15907
|
}
|
|
15908
|
+
if (command === "monitor") {
|
|
15909
|
+
await monitorCommand(parsed, runtime);
|
|
15910
|
+
return;
|
|
15911
|
+
}
|
|
15426
15912
|
if (command === "platform") {
|
|
15427
15913
|
await platformCommand(parsed, runtime);
|
|
15428
15914
|
return;
|
|
@@ -15519,6 +16005,7 @@ var init_cli = __esm({
|
|
|
15519
16005
|
init_pm_command();
|
|
15520
16006
|
init_platform_command();
|
|
15521
16007
|
init_o11y_command();
|
|
16008
|
+
init_monitor_command();
|
|
15522
16009
|
init_provision();
|
|
15523
16010
|
init_record();
|
|
15524
16011
|
init_redact();
|