@odla-ai/cli 0.32.1 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -0
- package/dist/bin.cjs +1370 -624
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-L6YTOTWU.js → chunk-LGNNX6AP.js} +1309 -616
- package/dist/chunk-LGNNX6AP.js.map +1 -0
- package/dist/{cli-LYFPBGNH.js → cli-IN6WGMSY.js} +2 -2
- package/dist/index.cjs +1314 -619
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +218 -6
- package/dist/index.d.ts +218 -6
- package/dist/index.js +5 -1
- package/package.json +2 -2
- package/skills/odla/SKILL.md +10 -0
- package/dist/chunk-L6YTOTWU.js.map +0 -1
- /package/dist/{cli-LYFPBGNH.js.map → cli-IN6WGMSY.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -72,6 +72,8 @@ __export(index_exports, {
|
|
|
72
72
|
isTerminalHostedSecurityStatus: () => isTerminalHostedSecurityStatus,
|
|
73
73
|
listGitHubSecuritySources: () => listGitHubSecuritySources,
|
|
74
74
|
listHostedSecurityJobs: () => listHostedSecurityJobs,
|
|
75
|
+
monitorCommand: () => monitorCommand,
|
|
76
|
+
monitoringWireConfig: () => monitoringWireConfig,
|
|
75
77
|
printCapabilities: () => printCapabilities,
|
|
76
78
|
provision: () => provision,
|
|
77
79
|
reconcileConfig: () => reconcileConfig,
|
|
@@ -342,10 +344,10 @@ function isManagedDevVar(line) {
|
|
|
342
344
|
const match = line.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/);
|
|
343
345
|
return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
|
|
344
346
|
}
|
|
345
|
-
function writePrivateText(path,
|
|
347
|
+
function writePrivateText(path, text3) {
|
|
346
348
|
(0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(path), { recursive: true });
|
|
347
349
|
const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
348
|
-
(0, import_node_fs2.writeFileSync)(temporary,
|
|
350
|
+
(0, import_node_fs2.writeFileSync)(temporary, text3, { mode: 384 });
|
|
349
351
|
(0, import_node_fs2.chmodSync)(temporary, 384);
|
|
350
352
|
(0, import_node_fs2.renameSync)(temporary, path);
|
|
351
353
|
}
|
|
@@ -460,7 +462,7 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
460
462
|
}
|
|
461
463
|
function cachedGrantCovers(cached, required) {
|
|
462
464
|
if (required.optionalProjectCapabilities.length === 0) return true;
|
|
463
|
-
return required.projectIds.every((
|
|
465
|
+
return required.projectIds.every((id2) => cached.projectIds?.includes(id2)) && required.optionalProjectCapabilities.every(
|
|
464
466
|
(capability) => cached.optionalProjectCapabilities?.includes(capability)
|
|
465
467
|
);
|
|
466
468
|
}
|
|
@@ -619,8 +621,8 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
619
621
|
// src/principal-presentation.ts
|
|
620
622
|
function unresolvedPrincipalLabel(credentialKind2, principalId) {
|
|
621
623
|
const kind = typeof credentialKind2 === "string" ? credentialKind2.trim() : "";
|
|
622
|
-
const
|
|
623
|
-
const audit = kind &&
|
|
624
|
+
const id2 = typeof principalId === "string" ? principalId.trim() : "";
|
|
625
|
+
const audit = kind && id2 ? `${kind}:${id2}` : kind || id2;
|
|
624
626
|
return `Unknown principal${audit ? ` [${audit}]` : ""}`;
|
|
625
627
|
}
|
|
626
628
|
|
|
@@ -632,38 +634,38 @@ function adminAiAuditQuery(filters) {
|
|
|
632
634
|
}
|
|
633
635
|
return `?limit=${filters.limit}`;
|
|
634
636
|
}
|
|
635
|
-
async function readAdminAiAudit(
|
|
636
|
-
const response2 = await
|
|
637
|
-
headers:
|
|
637
|
+
async function readAdminAiAudit(request3) {
|
|
638
|
+
const response2 = await request3.fetch(`${request3.platform}/registry/platform/ai-audit${request3.query}`, {
|
|
639
|
+
headers: request3.headers
|
|
638
640
|
});
|
|
639
641
|
const body = await responseBody(response2);
|
|
640
642
|
if (!response2.ok) throw new Error(apiError(response2.status, body));
|
|
641
|
-
if (
|
|
642
|
-
|
|
643
|
+
if (request3.json) {
|
|
644
|
+
request3.stdout.log(JSON.stringify(body, null, 2));
|
|
643
645
|
return;
|
|
644
646
|
}
|
|
645
647
|
const events = isRecord(body) && Array.isArray(body.events) ? body.events.filter(isRecord) : [];
|
|
646
|
-
|
|
648
|
+
request3.stdout.log("when change target before -> after actor");
|
|
647
649
|
for (const event of events) {
|
|
648
650
|
const before = isRecord(event.oldPolicy) ? event.oldPolicy : void 0;
|
|
649
651
|
const after = isRecord(event.newPolicy) ? event.newPolicy : void 0;
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
+
const route3 = before && after ? `${String(before.provider)}/${String(before.model)}@v${String(before.version)} -> ${String(after.provider)}/${String(after.model)}@v${String(after.version)}` : "value not retained";
|
|
653
|
+
request3.stdout.log([
|
|
652
654
|
timestamp(event.createdAt),
|
|
653
655
|
String(event.changeKind ?? ""),
|
|
654
656
|
String(event.purpose ?? event.provider ?? ""),
|
|
655
|
-
|
|
657
|
+
route3,
|
|
656
658
|
unresolvedPrincipalLabel(event.actorType, event.actorId)
|
|
657
659
|
].join(" "));
|
|
658
660
|
}
|
|
659
661
|
}
|
|
660
662
|
async function responseBody(response2) {
|
|
661
|
-
const
|
|
662
|
-
if (!
|
|
663
|
+
const text3 = await response2.text();
|
|
664
|
+
if (!text3) return {};
|
|
663
665
|
try {
|
|
664
|
-
return JSON.parse(
|
|
666
|
+
return JSON.parse(text3);
|
|
665
667
|
} catch {
|
|
666
|
-
return { message:
|
|
668
|
+
return { message: text3.slice(0, 300) };
|
|
667
669
|
}
|
|
668
670
|
}
|
|
669
671
|
function apiError(status, body) {
|
|
@@ -704,14 +706,14 @@ function adminAiUsageQuery(filters) {
|
|
|
704
706
|
const query = params.toString();
|
|
705
707
|
return query ? `?${query}` : "";
|
|
706
708
|
}
|
|
707
|
-
async function readAdminAiUsage(
|
|
708
|
-
const res = await
|
|
709
|
-
headers:
|
|
709
|
+
async function readAdminAiUsage(request3) {
|
|
710
|
+
const res = await request3.fetch(`${request3.platform}/registry/platform/ai-usage${request3.query}`, {
|
|
711
|
+
headers: request3.headers
|
|
710
712
|
});
|
|
711
713
|
const body = await responseBody2(res);
|
|
712
714
|
if (!res.ok) throw new Error(apiError2("read platform AI usage", res.status, body));
|
|
713
|
-
if (
|
|
714
|
-
else printUsage(body,
|
|
715
|
+
if (request3.json) request3.stdout.log(JSON.stringify(body, null, 2));
|
|
716
|
+
else printUsage(body, request3.stdout);
|
|
715
717
|
}
|
|
716
718
|
function usageLimit(value2) {
|
|
717
719
|
if (!Number.isSafeInteger(value2) || value2 < 1 || value2 > 500) {
|
|
@@ -765,12 +767,12 @@ function timestamp2(value2) {
|
|
|
765
767
|
return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
|
|
766
768
|
}
|
|
767
769
|
async function responseBody2(res) {
|
|
768
|
-
const
|
|
769
|
-
if (!
|
|
770
|
+
const text3 = await res.text();
|
|
771
|
+
if (!text3) return {};
|
|
770
772
|
try {
|
|
771
|
-
return JSON.parse(
|
|
773
|
+
return JSON.parse(text3);
|
|
772
774
|
} catch {
|
|
773
|
-
return { message:
|
|
775
|
+
return { message: text3.slice(0, 300) };
|
|
774
776
|
}
|
|
775
777
|
}
|
|
776
778
|
function apiError2(action2, status, body) {
|
|
@@ -946,12 +948,12 @@ function catalogModels(body) {
|
|
|
946
948
|
return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
|
|
947
949
|
}
|
|
948
950
|
async function responseBody3(res) {
|
|
949
|
-
const
|
|
950
|
-
if (!
|
|
951
|
+
const text3 = await res.text();
|
|
952
|
+
if (!text3) return {};
|
|
951
953
|
try {
|
|
952
|
-
return JSON.parse(
|
|
954
|
+
return JSON.parse(text3);
|
|
953
955
|
} catch {
|
|
954
|
-
return { message:
|
|
956
|
+
return { message: text3.slice(0, 300) };
|
|
955
957
|
}
|
|
956
958
|
}
|
|
957
959
|
function apiError3(action2, status, body) {
|
|
@@ -1075,6 +1077,111 @@ function safeText(value2, max) {
|
|
|
1075
1077
|
return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
|
|
1076
1078
|
}
|
|
1077
1079
|
|
|
1080
|
+
// src/calendar-config.ts
|
|
1081
|
+
function calendarServiceConfig(cfg, env) {
|
|
1082
|
+
if (!cfg.services.includes("calendar")) throw new Error("calendar service is not enabled in config services");
|
|
1083
|
+
if (!cfg.envs.includes(env) && env !== "prod") throw new Error(`calendar env "${env}" is not declared in config envs`);
|
|
1084
|
+
const google = cfg.calendar?.google;
|
|
1085
|
+
if (!google) throw new Error("calendar.google is required when the calendar service is enabled");
|
|
1086
|
+
const configured = google.availabilityCalendars?.[env] ?? google.calendars?.[env];
|
|
1087
|
+
if (!configured?.length) throw new Error(`calendar.google.availabilityCalendars.${env} is required`);
|
|
1088
|
+
const availability = unique(configured.map((id2) => id2.trim()));
|
|
1089
|
+
return {
|
|
1090
|
+
provider: "google",
|
|
1091
|
+
access: "book",
|
|
1092
|
+
bookingCalendarId: google.bookingCalendar?.[env]?.trim() ?? availability[0],
|
|
1093
|
+
availabilityCalendars: availability
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
function calendarBookingPageUrl(cfg, env) {
|
|
1097
|
+
const value2 = cfg.calendar?.google.bookingPageUrl?.[env];
|
|
1098
|
+
if (value2 === void 0 || value2 === null) return value2;
|
|
1099
|
+
return new URL(value2).toString();
|
|
1100
|
+
}
|
|
1101
|
+
function validateCalendarConfig(cfg, envs, services, path) {
|
|
1102
|
+
const enabled = services.includes("calendar");
|
|
1103
|
+
if (!cfg.calendar) {
|
|
1104
|
+
if (enabled) throw new Error(`${path}: calendar.google is required when services includes "calendar"`);
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
if (!isRecord5(cfg.calendar)) throw new Error(`${path}: calendar must be an object`);
|
|
1108
|
+
assertOnly2(cfg.calendar, ["google"], `${path}: calendar`);
|
|
1109
|
+
if (!isRecord5(cfg.calendar.google)) throw new Error(`${path}: calendar.google must be an object`);
|
|
1110
|
+
const google = cfg.calendar.google;
|
|
1111
|
+
assertOnly2(
|
|
1112
|
+
google,
|
|
1113
|
+
["availabilityCalendars", "calendars", "bookingCalendar", "bookingPageUrl"],
|
|
1114
|
+
`${path}: calendar.google`
|
|
1115
|
+
);
|
|
1116
|
+
const availabilityKey = google.availabilityCalendars !== void 0 ? "availabilityCalendars" : google.calendars !== void 0 ? "calendars" : null;
|
|
1117
|
+
if (!availabilityKey || google.availabilityCalendars !== void 0 && google.calendars !== void 0) {
|
|
1118
|
+
throw new Error(`${path}: calendar.google requires exactly one of availabilityCalendars or calendars (legacy)`);
|
|
1119
|
+
}
|
|
1120
|
+
const availability = google[availabilityKey];
|
|
1121
|
+
if (!isRecord5(availability)) throw new Error(`${path}: calendar.google.${availabilityKey} must map env names to calendar ids`);
|
|
1122
|
+
const unknownEnv = Object.keys(availability).find((env) => !envs.includes(env) && env !== "prod");
|
|
1123
|
+
if (unknownEnv) throw new Error(`${path}: calendar.google.${availabilityKey}.${unknownEnv} is not in config envs`);
|
|
1124
|
+
for (const env of envs) {
|
|
1125
|
+
const ids = availability[env];
|
|
1126
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
1127
|
+
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
for (const [env, ids] of Object.entries(availability)) {
|
|
1131
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
1132
|
+
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
|
|
1133
|
+
}
|
|
1134
|
+
if (ids.length > 10) {
|
|
1135
|
+
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must contain at most 10 calendar ids`);
|
|
1136
|
+
}
|
|
1137
|
+
if (ids.some((id2) => !safeText2(id2, 1024))) {
|
|
1138
|
+
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} contains an invalid calendar id`);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
if (google.bookingCalendar !== void 0) {
|
|
1142
|
+
if (!isRecord5(google.bookingCalendar)) throw new Error(`${path}: calendar.google.bookingCalendar must map env names to one calendar id`);
|
|
1143
|
+
const unknownBookingEnv = Object.keys(google.bookingCalendar).find((env) => !envs.includes(env) && env !== "prod");
|
|
1144
|
+
if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingCalendar.${unknownBookingEnv} is not in config envs`);
|
|
1145
|
+
for (const [env, value2] of Object.entries(google.bookingCalendar)) {
|
|
1146
|
+
if (!safeText2(value2, 1024)) {
|
|
1147
|
+
throw new Error(`${path}: calendar.google.bookingCalendar.${env} must be a calendar id`);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
if (google.bookingPageUrl !== void 0) {
|
|
1152
|
+
if (!isRecord5(google.bookingPageUrl)) throw new Error(`${path}: calendar.google.bookingPageUrl must map env names to HTTPS URLs or null`);
|
|
1153
|
+
const unknownBookingEnv = Object.keys(google.bookingPageUrl).find((env) => !envs.includes(env) && env !== "prod");
|
|
1154
|
+
if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingPageUrl.${unknownBookingEnv} is not in config envs`);
|
|
1155
|
+
for (const [env, value2] of Object.entries(google.bookingPageUrl)) {
|
|
1156
|
+
if (value2 !== null && !safeHttpsUrl(value2)) {
|
|
1157
|
+
throw new Error(`${path}: calendar.google.bookingPageUrl.${env} must be an HTTPS URL without credentials or fragment`);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
function assertOnly2(value2, allowed, label) {
|
|
1163
|
+
const extra = Object.keys(value2).find((key) => !allowed.includes(key));
|
|
1164
|
+
if (extra) throw new Error(`${label}.${extra} is not supported`);
|
|
1165
|
+
}
|
|
1166
|
+
function isRecord5(value2) {
|
|
1167
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
1168
|
+
}
|
|
1169
|
+
function safeText2(value2, max) {
|
|
1170
|
+
return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
|
|
1171
|
+
}
|
|
1172
|
+
function safeHttpsUrl(value2) {
|
|
1173
|
+
if (typeof value2 !== "string" || value2.length > 2048) return false;
|
|
1174
|
+
try {
|
|
1175
|
+
const url = new URL(value2);
|
|
1176
|
+
return url.protocol === "https:" && !url.username && !url.password && !url.hash;
|
|
1177
|
+
} catch {
|
|
1178
|
+
return false;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
function unique(values) {
|
|
1182
|
+
return [...new Set(values.filter(Boolean))];
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1078
1185
|
// src/integration-validation.ts
|
|
1079
1186
|
function validateIntegrations(cfg, path, defaultServices) {
|
|
1080
1187
|
if (cfg.integrations === void 0) return;
|
|
@@ -1082,36 +1189,61 @@ function validateIntegrations(cfg, path, defaultServices) {
|
|
|
1082
1189
|
const ids = /* @__PURE__ */ new Set();
|
|
1083
1190
|
for (const [index, integration] of cfg.integrations.entries()) {
|
|
1084
1191
|
const at = `${path}: integrations[${index}]`;
|
|
1085
|
-
if (!
|
|
1192
|
+
if (!isRecord6(integration)) throw new Error(`${at} must be an object`);
|
|
1086
1193
|
if (!validId(integration.id)) throw new Error(`${at}.id must be lowercase letters, numbers, and hyphens`);
|
|
1087
1194
|
if (ids.has(integration.id)) throw new Error(`${path}: duplicate integration id "${integration.id}"`);
|
|
1088
1195
|
ids.add(integration.id);
|
|
1089
|
-
if (!
|
|
1090
|
-
if (!
|
|
1091
|
-
if (integration.schema !== void 0 && (!
|
|
1196
|
+
if (!safeText3(integration.title, 200)) throw new Error(`${at}.title is required`);
|
|
1197
|
+
if (!safeText3(integration.npm, 200)) throw new Error(`${at}.npm is required`);
|
|
1198
|
+
if (integration.schema !== void 0 && (!isRecord6(integration.schema) || !isRecord6(integration.schema.entities))) {
|
|
1092
1199
|
throw new Error(`${at}.schema must contain an entities object`);
|
|
1093
1200
|
}
|
|
1094
|
-
if (integration.rules !== void 0 && !
|
|
1201
|
+
if (integration.rules !== void 0 && !isRecord6(integration.rules)) throw new Error(`${at}.rules must be an object`);
|
|
1095
1202
|
validateSeeds(integration, at);
|
|
1096
1203
|
validateProbes(integration, at);
|
|
1204
|
+
validateSecrets(integration.secrets, at);
|
|
1097
1205
|
}
|
|
1098
1206
|
const needsDb = cfg.integrations.some((integration) => integration.schema || integration.rules || integration.seeds?.length);
|
|
1099
|
-
const services =
|
|
1207
|
+
const services = unique2(cfg.services?.length ? cfg.services : defaultServices);
|
|
1100
1208
|
if (needsDb && !services.includes("db")) throw new Error(`${path}: schema/rules/seed integrations require the db service`);
|
|
1101
1209
|
}
|
|
1210
|
+
var SECRET_NAME = /^\$?[a-z][a-z0-9_]*$/;
|
|
1211
|
+
function validateSecrets(value2, at) {
|
|
1212
|
+
if (value2 === void 0) return;
|
|
1213
|
+
if (!Array.isArray(value2)) throw new Error(`${at}.secrets must be an array`);
|
|
1214
|
+
const names = /* @__PURE__ */ new Set();
|
|
1215
|
+
for (const [index, secret] of value2.entries()) {
|
|
1216
|
+
const sat = `${at}.secrets[${index}]`;
|
|
1217
|
+
if (!isRecord6(secret)) throw new Error(`${sat} must be an object`);
|
|
1218
|
+
if (typeof secret.name !== "string" || !SECRET_NAME.test(secret.name) || secret.name.length > 64) {
|
|
1219
|
+
throw new Error(`${sat}.name must be lowercase snake_case (optionally "$"-prefixed when reserved), e.g. "clerk_webhook_secret"`);
|
|
1220
|
+
}
|
|
1221
|
+
const dollar = secret.name.startsWith("$");
|
|
1222
|
+
if (dollar !== (secret.reserved === true)) {
|
|
1223
|
+
throw new Error(
|
|
1224
|
+
dollar ? `${sat}.name is "$"-prefixed, so it must also set reserved: true` : `${sat} sets reserved: true, so its name must be "$"-prefixed`
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
if (!safeText3(secret.description, 500)) throw new Error(`${sat}.description is required \u2014 it is what doctor and the docs show`);
|
|
1228
|
+
if (secret.pattern !== void 0 && !safeText3(secret.pattern, 64)) throw new Error(`${sat}.pattern must be a non-empty prefix string`);
|
|
1229
|
+
if (secret.required !== void 0 && typeof secret.required !== "boolean") throw new Error(`${sat}.required must be a boolean`);
|
|
1230
|
+
if (names.has(secret.name)) throw new Error(`${at} declares secret "${secret.name}" twice`);
|
|
1231
|
+
names.add(secret.name);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1102
1234
|
function validateSeeds(integration, at) {
|
|
1103
1235
|
if (integration.seeds === void 0) return;
|
|
1104
1236
|
if (!Array.isArray(integration.seeds)) throw new Error(`${at}.seeds must be an array`);
|
|
1105
1237
|
const ids = /* @__PURE__ */ new Set();
|
|
1106
1238
|
for (const [index, seed] of integration.seeds.entries()) {
|
|
1107
1239
|
const sat = `${at}.seeds[${index}]`;
|
|
1108
|
-
if (!
|
|
1240
|
+
if (!isRecord6(seed) || !safeText3(seed.id, 200) || !safeText3(seed.ns, 200)) throw new Error(`${sat} requires id and ns`);
|
|
1109
1241
|
if (ids.has(seed.id)) throw new Error(`${at} has duplicate seed id "${seed.id}"`);
|
|
1110
1242
|
ids.add(seed.id);
|
|
1111
|
-
if (!
|
|
1243
|
+
if (!isRecord6(seed.key) || !safeText3(seed.key.attr, 200) || !safeText3(seed.key.value, 2048)) {
|
|
1112
1244
|
throw new Error(`${sat}.key requires string attr and value`);
|
|
1113
1245
|
}
|
|
1114
|
-
if (!
|
|
1246
|
+
if (!isRecord6(seed.attrs)) throw new Error(`${sat}.attrs must be an object`);
|
|
1115
1247
|
if (Object.hasOwn(seed.attrs, seed.key.attr) && seed.attrs[seed.key.attr] !== seed.key.value) {
|
|
1116
1248
|
throw new Error(`${sat}.attrs.${seed.key.attr} conflicts with its natural key`);
|
|
1117
1249
|
}
|
|
@@ -1122,16 +1254,16 @@ function validateProbes(integration, at) {
|
|
|
1122
1254
|
if (!Array.isArray(integration.probes)) throw new Error(`${at}.probes must be an array`);
|
|
1123
1255
|
for (const [index, probe] of integration.probes.entries()) {
|
|
1124
1256
|
const pat = `${at}.probes[${index}]`;
|
|
1125
|
-
if (!
|
|
1257
|
+
if (!isRecord6(probe) || !safeProbePath(probe.path)) throw new Error(`${pat}.path must be an absolute path without query or fragment`);
|
|
1126
1258
|
if (!Number.isInteger(probe.expectedStatus) || probe.expectedStatus < 100 || probe.expectedStatus > 599) {
|
|
1127
1259
|
throw new Error(`${pat}.expectedStatus must be an HTTP status`);
|
|
1128
1260
|
}
|
|
1129
1261
|
}
|
|
1130
1262
|
}
|
|
1131
|
-
function
|
|
1263
|
+
function isRecord6(value2) {
|
|
1132
1264
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
1133
1265
|
}
|
|
1134
|
-
function
|
|
1266
|
+
function safeText3(value2, max) {
|
|
1135
1267
|
return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
|
|
1136
1268
|
}
|
|
1137
1269
|
function safeProbePath(value2) {
|
|
@@ -1140,10 +1272,179 @@ function safeProbePath(value2) {
|
|
|
1140
1272
|
function validId(value2) {
|
|
1141
1273
|
return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
|
|
1142
1274
|
}
|
|
1143
|
-
function
|
|
1275
|
+
function unique2(values) {
|
|
1144
1276
|
return [...new Set(values.filter(Boolean))];
|
|
1145
1277
|
}
|
|
1146
1278
|
|
|
1279
|
+
// src/monitoring-validation.ts
|
|
1280
|
+
var CADENCES = /* @__PURE__ */ new Set(["1m", "2m", "5m", "10m", "15m", "30m", "1h"]);
|
|
1281
|
+
var WINDOWS = /* @__PURE__ */ new Set(["7d", "28d", "30d"]);
|
|
1282
|
+
var SHORT = /* @__PURE__ */ new Set(["30m", "1h", "6h", "12h", "1d"]);
|
|
1283
|
+
var LONG = /* @__PURE__ */ new Set(["1d", "3d", "7d"]);
|
|
1284
|
+
var DAYS = /* @__PURE__ */ new Set(["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]);
|
|
1285
|
+
var O11Y_METRICS = /* @__PURE__ */ new Set(["error_rate", "latency_p95", "synthetic_success", "synthetic_publish_to_visible"]);
|
|
1286
|
+
var COMPARATORS = /* @__PURE__ */ new Set(["gt", "gte", "lt", "lte"]);
|
|
1287
|
+
function validateMonitoringConfig(cfg, envs, services, path) {
|
|
1288
|
+
if (!cfg.o11y) return;
|
|
1289
|
+
if (!record(cfg.o11y)) fail(path, "o11y must be an object");
|
|
1290
|
+
only(cfg.o11y, ["service", "endpoint", "version", "monitoring"], `${path}: o11y`);
|
|
1291
|
+
const monitoring = cfg.o11y.monitoring;
|
|
1292
|
+
if (!monitoring) return;
|
|
1293
|
+
if (!services.includes("o11y")) fail(path, 'o11y.monitoring requires "o11y" in services');
|
|
1294
|
+
if (!record(monitoring)) fail(path, "o11y.monitoring must be an object");
|
|
1295
|
+
only(monitoring, ["probes", "slos", "notifications"], `${path}: o11y.monitoring`);
|
|
1296
|
+
if (monitoring.probes !== void 0 && (!Array.isArray(monitoring.probes) || monitoring.probes.length > 50)) {
|
|
1297
|
+
fail(path, "o11y.monitoring.probes must contain at most 50 probes");
|
|
1298
|
+
}
|
|
1299
|
+
const probeIds = /* @__PURE__ */ new Set();
|
|
1300
|
+
(monitoring.probes ?? []).forEach((probe, index) => validateProbe(probe, index, envs, path, probeIds));
|
|
1301
|
+
if (!Array.isArray(monitoring.slos) || monitoring.slos.length < 1 || monitoring.slos.length > 50) {
|
|
1302
|
+
fail(path, "o11y.monitoring.slos must contain 1 through 50 SLOs");
|
|
1303
|
+
}
|
|
1304
|
+
const sloIds = /* @__PURE__ */ new Set();
|
|
1305
|
+
monitoring.slos.forEach((slo, index) => validateSlo(slo, index, path, probeIds, sloIds));
|
|
1306
|
+
if (monitoring.notifications !== void 0) validateNotifications(monitoring.notifications, envs, path);
|
|
1307
|
+
}
|
|
1308
|
+
function validateProbe(value2, index, envs, path, ids) {
|
|
1309
|
+
const label = `${path}: o11y.monitoring.probes[${index}]`;
|
|
1310
|
+
if (!record(value2)) fail(label, "must be an object");
|
|
1311
|
+
only(value2, ["id", "route", "envs", "every", "timeout", "ready", "expect", "enabled"], label);
|
|
1312
|
+
if (!id(value2.id)) fail(label, "id must be lowercase letters, numbers, and hyphens");
|
|
1313
|
+
if (ids.has(value2.id)) fail(label, `id duplicates ${value2.id}`);
|
|
1314
|
+
ids.add(value2.id);
|
|
1315
|
+
if (!route(value2.route)) fail(label, "route must be a relative absolute path without credentials or a fragment");
|
|
1316
|
+
if (!CADENCES.has(String(value2.every))) fail(label, `every must be one of ${[...CADENCES].join(", ")}`);
|
|
1317
|
+
if (value2.timeout !== void 0 && (!Number.isSafeInteger(value2.timeout) || Number(value2.timeout) < 1e3 || Number(value2.timeout) > 6e4)) fail(label, "timeout must be an integer from 1000 through 60000");
|
|
1318
|
+
if (value2.envs !== void 0 && (!Array.isArray(value2.envs) || value2.envs.length < 1 || value2.envs.some((env) => typeof env !== "string" || !envs.includes(env) && env !== "prod"))) fail(label, "envs must contain configured environment names");
|
|
1319
|
+
if (value2.ready !== void 0) {
|
|
1320
|
+
if (!record(value2.ready) || !text(value2.ready.selector, 300)) fail(label, "ready.selector is required");
|
|
1321
|
+
only(value2.ready, ["selector"], `${label}.ready`);
|
|
1322
|
+
}
|
|
1323
|
+
if (!record(value2.expect)) fail(label, "expect must be an object");
|
|
1324
|
+
only(value2.expect, ["status", "titleIncludes", "textIncludes", "accessibility"], `${label}.expect`);
|
|
1325
|
+
if (!Number.isSafeInteger(value2.expect.status) || Number(value2.expect.status) < 100 || Number(value2.expect.status) > 599) fail(label, "expect.status must be an HTTP status from 100 through 599");
|
|
1326
|
+
if (value2.expect.titleIncludes !== void 0 && !text(value2.expect.titleIncludes, 300)) fail(label, "expect.titleIncludes is invalid");
|
|
1327
|
+
if (value2.expect.textIncludes !== void 0 && (!Array.isArray(value2.expect.textIncludes) || value2.expect.textIncludes.length > 10 || value2.expect.textIncludes.some((item) => !text(item, 500)))) fail(label, "expect.textIncludes must contain at most 10 bounded strings");
|
|
1328
|
+
if (value2.expect.accessibility !== void 0) validateAccessibility(value2.expect.accessibility, label);
|
|
1329
|
+
}
|
|
1330
|
+
function validateAccessibility(value2, label) {
|
|
1331
|
+
if (!Array.isArray(value2) || value2.length > 10) fail(label, "expect.accessibility must contain at most 10 assertions");
|
|
1332
|
+
for (const item of value2) {
|
|
1333
|
+
if (!record(item) || !text(item.role, 80) || !text(item.name, 300)) fail(label, "expect.accessibility entries need role and name");
|
|
1334
|
+
only(item, ["role", "name"], `${label}.expect.accessibility`);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
function validateSlo(value2, index, path, probes, ids) {
|
|
1338
|
+
const label = `${path}: o11y.monitoring.slos[${index}]`;
|
|
1339
|
+
if (!record(value2)) fail(label, "must be an object");
|
|
1340
|
+
only(value2, ["id", "name", "indicator", "target", "window", "alerts", "enabled"], label);
|
|
1341
|
+
if (!id(value2.id) || ids.has(value2.id)) fail(label, "id must be unique lowercase letters, numbers, and hyphens");
|
|
1342
|
+
ids.add(value2.id);
|
|
1343
|
+
if (value2.name !== void 0 && !text(value2.name, 160)) fail(label, "name is invalid");
|
|
1344
|
+
validateIndicator(value2.indicator, label, probes);
|
|
1345
|
+
if (typeof value2.target !== "number" || !Number.isFinite(value2.target) || value2.target <= 0 || value2.target >= 1) fail(label, "target must be a fraction greater than 0 and less than 1");
|
|
1346
|
+
if (!WINDOWS.has(String(value2.window))) fail(label, `window must be one of ${[...WINDOWS].join(", ")}`);
|
|
1347
|
+
if (value2.alerts !== void 0) validateAlerts(value2.alerts, label);
|
|
1348
|
+
}
|
|
1349
|
+
function validateIndicator(value2, label, probes) {
|
|
1350
|
+
if (!record(value2)) fail(label, "indicator must be an object");
|
|
1351
|
+
if (value2.type === "probe-success") {
|
|
1352
|
+
only(value2, ["type", "probes"], `${label}.indicator`);
|
|
1353
|
+
if (!Array.isArray(value2.probes) || value2.probes.length < 1) fail(label, "probe-success must select at least one probe");
|
|
1354
|
+
if (value2.probes.some((probe) => typeof probe !== "string" || !probes.has(probe))) fail(label, "indicator references an unknown probe");
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
if (value2.type !== "o11y-metric") fail(label, "indicator.type must be probe-success or o11y-metric");
|
|
1358
|
+
only(value2, ["type", "metric", "comparator", "threshold", "every", "observationWindow", "route"], `${label}.indicator`);
|
|
1359
|
+
if (!O11Y_METRICS.has(String(value2.metric))) fail(label, "indicator.metric is unsupported");
|
|
1360
|
+
if (!COMPARATORS.has(String(value2.comparator))) fail(label, "indicator.comparator is unsupported");
|
|
1361
|
+
if (typeof value2.threshold !== "number" || !Number.isFinite(value2.threshold)) fail(label, "indicator.threshold must be finite");
|
|
1362
|
+
if (!CADENCES.has(String(value2.every)) || !CADENCES.has(String(value2.observationWindow))) fail(label, "indicator cadence and observationWindow must be supported durations");
|
|
1363
|
+
if (value2.route !== void 0 && !routePattern(value2.route)) fail(label, "indicator.route must be an exact route template or trailing-* prefix");
|
|
1364
|
+
if ((value2.metric === "synthetic_success" || value2.metric === "synthetic_publish_to_visible") && value2.route !== void 0) fail(label, "synthetic indicators cannot select a route");
|
|
1365
|
+
}
|
|
1366
|
+
function validateAlerts(value2, label) {
|
|
1367
|
+
if (!record(value2)) fail(label, "alerts must be an object");
|
|
1368
|
+
only(value2, ["spike", "trend"], `${label}.alerts`);
|
|
1369
|
+
if (value2.spike !== void 0) {
|
|
1370
|
+
if (!record(value2.spike)) fail(label, "alerts.spike must be an object");
|
|
1371
|
+
only(value2.spike, ["badChecks", "withinChecks", "recoverAfter"], `${label}.alerts.spike`);
|
|
1372
|
+
const bad = positive(value2.spike.badChecks, 2), within = positive(value2.spike.withinChecks, 3), recover = positive(value2.spike.recoverAfter, 2);
|
|
1373
|
+
if (bad > within || within > 20 || recover > 20) fail(label, "alerts.spike requires badChecks <= withinChecks <= 20 and recoverAfter <= 20");
|
|
1374
|
+
}
|
|
1375
|
+
if (value2.trend !== void 0) {
|
|
1376
|
+
if (!record(value2.trend)) fail(label, "alerts.trend must be an object");
|
|
1377
|
+
only(value2.trend, ["burnRate", "shortWindow", "longWindow", "minBadChecks"], `${label}.alerts.trend`);
|
|
1378
|
+
const burn = value2.trend.burnRate ?? 1;
|
|
1379
|
+
if (typeof burn !== "number" || !Number.isFinite(burn) || burn <= 0 || burn > 1e3) fail(label, "alerts.trend.burnRate must be greater than 0");
|
|
1380
|
+
if (value2.trend.shortWindow !== void 0 && !SHORT.has(String(value2.trend.shortWindow))) fail(label, "alerts.trend.shortWindow is unsupported");
|
|
1381
|
+
if (value2.trend.longWindow !== void 0 && !LONG.has(String(value2.trend.longWindow))) fail(label, "alerts.trend.longWindow is unsupported");
|
|
1382
|
+
if (positive(value2.trend.minBadChecks, 2) > 100) fail(label, "alerts.trend.minBadChecks must be at most 100");
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
function validateNotifications(value2, envs, path) {
|
|
1386
|
+
if (!record(value2)) fail(path, "o11y.monitoring.notifications must map environments to policies");
|
|
1387
|
+
for (const [env, policy] of Object.entries(value2)) {
|
|
1388
|
+
const label = `${path}: o11y.monitoring.notifications.${env}`;
|
|
1389
|
+
if (!envs.includes(env) && env !== "prod") fail(label, "is not a configured environment");
|
|
1390
|
+
if (!record(policy)) fail(label, "must be an object");
|
|
1391
|
+
only(policy, ["email", "timezone", "daily", "weekly"], label);
|
|
1392
|
+
if (!Array.isArray(policy.email) || policy.email.length < 1 || policy.email.length > 10 || policy.email.some((email) => !emailAddress(email))) fail(label, "email must contain 1 through 10 email addresses");
|
|
1393
|
+
if (!timezone(policy.timezone)) fail(label, "timezone must be an IANA timezone");
|
|
1394
|
+
if (policy.daily !== void 0 && policy.daily !== false && !clock(policy.daily)) fail(label, "daily must be HH:MM or false");
|
|
1395
|
+
if (policy.weekly !== void 0 && policy.weekly !== false) {
|
|
1396
|
+
if (!record(policy.weekly) || !DAYS.has(String(policy.weekly.day)) || !clock(policy.weekly.at)) fail(label, "weekly needs a weekday and HH:MM time");
|
|
1397
|
+
only(policy.weekly, ["day", "at"], `${label}.weekly`);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
function fail(label, message2) {
|
|
1402
|
+
throw new Error(`${label}: ${message2}`);
|
|
1403
|
+
}
|
|
1404
|
+
function record(value2) {
|
|
1405
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
1406
|
+
}
|
|
1407
|
+
function only(value2, keys, label) {
|
|
1408
|
+
const extra = Object.keys(value2).find((key) => !keys.includes(key));
|
|
1409
|
+
if (extra) fail(label, `${extra} is not supported`);
|
|
1410
|
+
}
|
|
1411
|
+
function id(value2) {
|
|
1412
|
+
return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
|
|
1413
|
+
}
|
|
1414
|
+
function text(value2, max) {
|
|
1415
|
+
return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
|
|
1416
|
+
}
|
|
1417
|
+
function positive(value2, fallback) {
|
|
1418
|
+
return value2 === void 0 ? fallback : Number.isSafeInteger(value2) && Number(value2) > 0 ? Number(value2) : Infinity;
|
|
1419
|
+
}
|
|
1420
|
+
function route(value2) {
|
|
1421
|
+
if (typeof value2 !== "string" || value2.length > 2048 || !value2.startsWith("/") || value2.startsWith("//")) return false;
|
|
1422
|
+
try {
|
|
1423
|
+
const url = new URL(value2, "https://probe.invalid");
|
|
1424
|
+
return url.origin === "https://probe.invalid" && !url.hash;
|
|
1425
|
+
} catch {
|
|
1426
|
+
return false;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
function routePattern(value2) {
|
|
1430
|
+
return typeof value2 === "string" && value2.length <= 160 && /^\/[A-Za-z0-9_./:-]+\*?$/.test(value2) && !value2.slice(0, -1).includes("*");
|
|
1431
|
+
}
|
|
1432
|
+
function emailAddress(value2) {
|
|
1433
|
+
return typeof value2 === "string" && value2.length <= 254 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value2);
|
|
1434
|
+
}
|
|
1435
|
+
function clock(value2) {
|
|
1436
|
+
return typeof value2 === "string" && /^(?:[01]\d|2[0-3]):[0-5]\d$/.test(value2);
|
|
1437
|
+
}
|
|
1438
|
+
function timezone(value2) {
|
|
1439
|
+
if (typeof value2 !== "string" || value2.length > 100) return false;
|
|
1440
|
+
try {
|
|
1441
|
+
new Intl.DateTimeFormat("en", { timeZone: value2 }).format(0);
|
|
1442
|
+
return true;
|
|
1443
|
+
} catch {
|
|
1444
|
+
return false;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1147
1448
|
// src/config.ts
|
|
1148
1449
|
var DEFAULT_PLATFORM = "https://odla.ai";
|
|
1149
1450
|
var DEFAULT_ENVS = ["dev"];
|
|
@@ -1160,10 +1461,11 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
|
|
|
1160
1461
|
validateRawConfig(raw, resolved);
|
|
1161
1462
|
const platformUrl = trimSlash(process.env.ODLA_PLATFORM_URL || raw.platformUrl || DEFAULT_PLATFORM);
|
|
1162
1463
|
const dbEndpoint = trimSlash(process.env.ODLA_DB_ENDPOINT || raw.dbEndpoint || platformUrl);
|
|
1163
|
-
const envs =
|
|
1164
|
-
const services =
|
|
1464
|
+
const envs = unique3(raw.envs?.length ? raw.envs : DEFAULT_ENVS);
|
|
1465
|
+
const services = unique3(raw.services?.length ? raw.services : DEFAULT_SERVICES);
|
|
1165
1466
|
validateServices(services, resolved);
|
|
1166
|
-
validateCalendarConfig(raw,
|
|
1467
|
+
validateCalendarConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
|
|
1468
|
+
validateMonitoringConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
|
|
1167
1469
|
const local = {
|
|
1168
1470
|
tokenFile: (0, import_node_path4.resolve)(rootDir, raw.local?.tokenFile ?? ".odla/dev-token.json"),
|
|
1169
1471
|
credentialsFile: (0, import_node_path4.resolve)(rootDir, raw.local?.credentialsFile ?? ".odla/credentials.local.json"),
|
|
@@ -1211,26 +1513,6 @@ function buildPlan(cfg) {
|
|
|
1211
1513
|
aiProvider: cfg.ai?.provider
|
|
1212
1514
|
};
|
|
1213
1515
|
}
|
|
1214
|
-
function calendarServiceConfig(cfg, env) {
|
|
1215
|
-
if (!cfg.services.includes("calendar")) throw new Error("calendar service is not enabled in config services");
|
|
1216
|
-
if (!cfg.envs.includes(env) && env !== "prod") throw new Error(`calendar env "${env}" is not declared in config envs`);
|
|
1217
|
-
const google = cfg.calendar?.google;
|
|
1218
|
-
if (!google) throw new Error("calendar.google is required when the calendar service is enabled");
|
|
1219
|
-
const configured = google.availabilityCalendars?.[env] ?? google.calendars?.[env];
|
|
1220
|
-
if (!configured?.length) throw new Error(`calendar.google.availabilityCalendars.${env} is required`);
|
|
1221
|
-
const availability = unique2(configured.map((id) => id.trim()));
|
|
1222
|
-
return {
|
|
1223
|
-
provider: "google",
|
|
1224
|
-
access: "book",
|
|
1225
|
-
bookingCalendarId: google.bookingCalendar?.[env]?.trim() ?? availability[0],
|
|
1226
|
-
availabilityCalendars: availability
|
|
1227
|
-
};
|
|
1228
|
-
}
|
|
1229
|
-
function calendarBookingPageUrl(cfg, env) {
|
|
1230
|
-
const value2 = cfg.calendar?.google.bookingPageUrl?.[env];
|
|
1231
|
-
if (value2 === void 0 || value2 === null) return value2;
|
|
1232
|
-
return new URL(value2).toString();
|
|
1233
|
-
}
|
|
1234
1516
|
function rulesFromSchema(schema) {
|
|
1235
1517
|
const entities = serializedEntities(schema);
|
|
1236
1518
|
return Object.fromEntries(
|
|
@@ -1262,69 +1544,9 @@ function validateRawConfig(raw, path) {
|
|
|
1262
1544
|
throw new Error(`${path}: services must be an array of non-empty names`);
|
|
1263
1545
|
}
|
|
1264
1546
|
validateAiConfig(cfg, path);
|
|
1547
|
+
validateSecrets(cfg.secrets, `${path}: config`);
|
|
1265
1548
|
validateIntegrations(cfg, path, DEFAULT_SERVICES);
|
|
1266
1549
|
}
|
|
1267
|
-
function validateCalendarConfig(cfg, envs, services, path) {
|
|
1268
|
-
const enabled = services.includes("calendar");
|
|
1269
|
-
if (!cfg.calendar) {
|
|
1270
|
-
if (enabled) throw new Error(`${path}: calendar.google is required when services includes "calendar"`);
|
|
1271
|
-
return;
|
|
1272
|
-
}
|
|
1273
|
-
if (!isRecord6(cfg.calendar)) throw new Error(`${path}: calendar must be an object`);
|
|
1274
|
-
assertOnly2(cfg.calendar, ["google"], `${path}: calendar`);
|
|
1275
|
-
if (!isRecord6(cfg.calendar.google)) throw new Error(`${path}: calendar.google must be an object`);
|
|
1276
|
-
const google = cfg.calendar.google;
|
|
1277
|
-
assertOnly2(
|
|
1278
|
-
google,
|
|
1279
|
-
["availabilityCalendars", "calendars", "bookingCalendar", "bookingPageUrl"],
|
|
1280
|
-
`${path}: calendar.google`
|
|
1281
|
-
);
|
|
1282
|
-
const availabilityKey = google.availabilityCalendars !== void 0 ? "availabilityCalendars" : google.calendars !== void 0 ? "calendars" : null;
|
|
1283
|
-
if (!availabilityKey || google.availabilityCalendars !== void 0 && google.calendars !== void 0) {
|
|
1284
|
-
throw new Error(`${path}: calendar.google requires exactly one of availabilityCalendars or calendars (legacy)`);
|
|
1285
|
-
}
|
|
1286
|
-
const availability = google[availabilityKey];
|
|
1287
|
-
if (!isRecord6(availability)) throw new Error(`${path}: calendar.google.${availabilityKey} must map env names to calendar ids`);
|
|
1288
|
-
const unknownEnv = Object.keys(availability).find((env) => !envs.includes(env) && env !== "prod");
|
|
1289
|
-
if (unknownEnv) throw new Error(`${path}: calendar.google.${availabilityKey}.${unknownEnv} is not in config envs`);
|
|
1290
|
-
for (const env of envs) {
|
|
1291
|
-
const ids = availability[env];
|
|
1292
|
-
if (!Array.isArray(ids) || ids.length === 0) {
|
|
1293
|
-
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
for (const [env, ids] of Object.entries(availability)) {
|
|
1297
|
-
if (!Array.isArray(ids) || ids.length === 0) {
|
|
1298
|
-
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
|
|
1299
|
-
}
|
|
1300
|
-
if (ids.length > 10) {
|
|
1301
|
-
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must contain at most 10 calendar ids`);
|
|
1302
|
-
}
|
|
1303
|
-
if (ids.some((id) => !safeText3(id, 1024))) {
|
|
1304
|
-
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} contains an invalid calendar id`);
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
if (google.bookingCalendar !== void 0) {
|
|
1308
|
-
if (!isRecord6(google.bookingCalendar)) throw new Error(`${path}: calendar.google.bookingCalendar must map env names to one calendar id`);
|
|
1309
|
-
const unknownBookingEnv = Object.keys(google.bookingCalendar).find((env) => !envs.includes(env) && env !== "prod");
|
|
1310
|
-
if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingCalendar.${unknownBookingEnv} is not in config envs`);
|
|
1311
|
-
for (const [env, value2] of Object.entries(google.bookingCalendar)) {
|
|
1312
|
-
if (!safeText3(value2, 1024)) {
|
|
1313
|
-
throw new Error(`${path}: calendar.google.bookingCalendar.${env} must be a calendar id`);
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
if (google.bookingPageUrl !== void 0) {
|
|
1318
|
-
if (!isRecord6(google.bookingPageUrl)) throw new Error(`${path}: calendar.google.bookingPageUrl must map env names to HTTPS URLs or null`);
|
|
1319
|
-
const unknownBookingEnv = Object.keys(google.bookingPageUrl).find((env) => !envs.includes(env) && env !== "prod");
|
|
1320
|
-
if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingPageUrl.${unknownBookingEnv} is not in config envs`);
|
|
1321
|
-
for (const [env, value2] of Object.entries(google.bookingPageUrl)) {
|
|
1322
|
-
if (value2 !== null && !safeHttpsUrl(value2)) {
|
|
1323
|
-
throw new Error(`${path}: calendar.google.bookingPageUrl.${env} must be an HTTPS URL without credentials or fragment`);
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
1550
|
function validateServices(services, path) {
|
|
1329
1551
|
for (const service of services) {
|
|
1330
1552
|
const definition = (0, import_apps.appServiceDefinition)(service);
|
|
@@ -1338,25 +1560,6 @@ function validateServices(services, path) {
|
|
|
1338
1560
|
}
|
|
1339
1561
|
}
|
|
1340
1562
|
}
|
|
1341
|
-
function assertOnly2(value2, allowed, label) {
|
|
1342
|
-
const extra = Object.keys(value2).find((key) => !allowed.includes(key));
|
|
1343
|
-
if (extra) throw new Error(`${label}.${extra} is not supported`);
|
|
1344
|
-
}
|
|
1345
|
-
function isRecord6(value2) {
|
|
1346
|
-
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
1347
|
-
}
|
|
1348
|
-
function safeText3(value2, max) {
|
|
1349
|
-
return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
|
|
1350
|
-
}
|
|
1351
|
-
function safeHttpsUrl(value2) {
|
|
1352
|
-
if (typeof value2 !== "string" || value2.length > 2048) return false;
|
|
1353
|
-
try {
|
|
1354
|
-
const url = new URL(value2);
|
|
1355
|
-
return url.protocol === "https:" && !url.username && !url.password && !url.hash;
|
|
1356
|
-
} catch {
|
|
1357
|
-
return false;
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
1563
|
function validId2(value2) {
|
|
1361
1564
|
return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
|
|
1362
1565
|
}
|
|
@@ -1371,7 +1574,7 @@ async function loadConfigModule(path) {
|
|
|
1371
1574
|
function trimSlash(value2) {
|
|
1372
1575
|
return value2.replace(/\/+$/, "");
|
|
1373
1576
|
}
|
|
1374
|
-
function
|
|
1577
|
+
function unique3(values) {
|
|
1375
1578
|
return [...new Set(values.filter(Boolean))];
|
|
1376
1579
|
}
|
|
1377
1580
|
|
|
@@ -1664,7 +1867,7 @@ async function adminCommand(parsed, deps = {}) {
|
|
|
1664
1867
|
var import_node_process10 = __toESM(require("process"), 1);
|
|
1665
1868
|
|
|
1666
1869
|
// src/whoami-command.ts
|
|
1667
|
-
var
|
|
1870
|
+
var text2 = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
1668
1871
|
function principalKind(value2, machine) {
|
|
1669
1872
|
return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
|
|
1670
1873
|
}
|
|
@@ -1677,12 +1880,12 @@ function credentialKind(value2, machine, scopes) {
|
|
|
1677
1880
|
function managerOf(value2) {
|
|
1678
1881
|
if (!value2 || typeof value2 !== "object") return null;
|
|
1679
1882
|
const row = value2;
|
|
1680
|
-
const principalId =
|
|
1883
|
+
const principalId = text2(row.principalId);
|
|
1681
1884
|
if (!principalId) return null;
|
|
1682
1885
|
return {
|
|
1683
1886
|
principalId,
|
|
1684
|
-
displayName:
|
|
1685
|
-
handle:
|
|
1887
|
+
displayName: text2(row.displayName) ?? "Unnamed member",
|
|
1888
|
+
handle: text2(row.handle) ?? ""
|
|
1686
1889
|
};
|
|
1687
1890
|
}
|
|
1688
1891
|
function unnamedPrincipal(kind) {
|
|
@@ -1696,14 +1899,14 @@ async function fetchIdentity(platformUrl, token, doFetch) {
|
|
|
1696
1899
|
});
|
|
1697
1900
|
if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
|
|
1698
1901
|
const body = await res.json();
|
|
1699
|
-
const developerId =
|
|
1902
|
+
const developerId = text2(body.developerId) ?? "";
|
|
1700
1903
|
const machine = body.machine === true;
|
|
1701
1904
|
const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
|
|
1702
|
-
const principalId =
|
|
1703
|
-
const email =
|
|
1905
|
+
const principalId = text2(body.principalId) ?? developerId;
|
|
1906
|
+
const email = text2(body.email);
|
|
1704
1907
|
const kind = principalKind(body.principalKind, machine);
|
|
1705
|
-
const displayName =
|
|
1706
|
-
const handle =
|
|
1908
|
+
const displayName = text2(body.displayName) ?? email ?? unnamedPrincipal(kind);
|
|
1909
|
+
const handle = text2(body.handle) ?? "";
|
|
1707
1910
|
const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
|
|
1708
1911
|
return {
|
|
1709
1912
|
developerId,
|
|
@@ -1713,7 +1916,7 @@ async function fetchIdentity(platformUrl, token, doFetch) {
|
|
|
1713
1916
|
handle,
|
|
1714
1917
|
manager: managerOf(body.manager),
|
|
1715
1918
|
credential: {
|
|
1716
|
-
id:
|
|
1919
|
+
id: text2(credential2.id),
|
|
1717
1920
|
kind: credentialKind(credential2.kind, machine, scopes)
|
|
1718
1921
|
},
|
|
1719
1922
|
email,
|
|
@@ -1908,13 +2111,13 @@ async function agentCommand(parsed, deps = {}) {
|
|
|
1908
2111
|
const base = `${cfg.dbEndpoint}/app/${encodeURIComponent(tenant)}/admin/agent-jobs`;
|
|
1909
2112
|
const headers = { authorization: `Bearer ${credential2}` };
|
|
1910
2113
|
if (action2 === "retry") {
|
|
1911
|
-
const
|
|
1912
|
-
const res2 = await doFetch(`${base}/${encodeURIComponent(
|
|
2114
|
+
const id2 = parsed.positionals[2];
|
|
2115
|
+
const res2 = await doFetch(`${base}/${encodeURIComponent(id2)}/retry`, { method: "POST", headers });
|
|
1913
2116
|
const body2 = await readJson(res2);
|
|
1914
2117
|
if (!res2.ok) throw new Error(`agent retry failed (${res2.status}): ${errorMessage(body2)}`);
|
|
1915
2118
|
const result2 = { v: 1, appId: cfg.app.id, env, tenant, ...body2 };
|
|
1916
2119
|
if (parsed.options.json === true) out.log(JSON.stringify(result2, null, 2));
|
|
1917
|
-
else out.log(`${tenant}: requeued ${
|
|
2120
|
+
else out.log(`${tenant}: requeued ${id2}`);
|
|
1918
2121
|
return;
|
|
1919
2122
|
}
|
|
1920
2123
|
const state2 = stringOpt(parsed.options.state);
|
|
@@ -1994,8 +2197,8 @@ async function appImport(options) {
|
|
|
1994
2197
|
const out = options.stdout ?? console;
|
|
1995
2198
|
const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
|
|
1996
2199
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
1997
|
-
const
|
|
1998
|
-
const { format, sources } = (0, import_import.parseImport)(
|
|
2200
|
+
const text3 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs7.readFileSync)(0, "utf8")))() : (0, import_node_fs7.readFileSync)(options.file, "utf8");
|
|
2201
|
+
const { format, sources } = (0, import_import.parseImport)(text3, options.ns);
|
|
1999
2202
|
if (format === "namespace-map" && options.ns) {
|
|
2000
2203
|
throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
|
|
2001
2204
|
}
|
|
@@ -2206,7 +2409,7 @@ var EXTENSIONS = {
|
|
|
2206
2409
|
"text/html": "html",
|
|
2207
2410
|
"application/json": "json"
|
|
2208
2411
|
};
|
|
2209
|
-
var encode = (
|
|
2412
|
+
var encode = (text3) => new TextEncoder().encode(text3);
|
|
2210
2413
|
function assetFileName(uuid, mime) {
|
|
2211
2414
|
const ext = EXTENSIONS[mime.split(";")[0].trim().toLowerCase()] ?? "bin";
|
|
2212
2415
|
return `${uuid.replace(/[^a-zA-Z0-9._-]/g, "_")}.${ext}`;
|
|
@@ -2418,18 +2621,18 @@ async function readCalendarStatus(ctx) {
|
|
|
2418
2621
|
}
|
|
2419
2622
|
async function discoverGoogleCalendars(ctx) {
|
|
2420
2623
|
const raw = await calendarJson(ctx, "/calendars", {});
|
|
2421
|
-
const value2 =
|
|
2624
|
+
const value2 = record2(raw);
|
|
2422
2625
|
if (!value2 || !Array.isArray(value2.calendars)) throw new Error("calendar discovery returned an invalid response");
|
|
2423
2626
|
return value2.calendars.map((item, index) => {
|
|
2424
|
-
const calendar =
|
|
2425
|
-
const
|
|
2426
|
-
if (!calendar || !
|
|
2627
|
+
const calendar = record2(item);
|
|
2628
|
+
const id2 = textField(calendar?.id, 1024);
|
|
2629
|
+
if (!calendar || !id2) throw new Error(`calendar discovery returned an invalid calendar at index ${index}`);
|
|
2427
2630
|
const role = calendar.accessRole;
|
|
2428
2631
|
if (role !== void 0 && role !== "freeBusyReader" && role !== "reader" && role !== "writer" && role !== "owner") {
|
|
2429
2632
|
throw new Error(`calendar discovery returned an invalid access role at index ${index}`);
|
|
2430
2633
|
}
|
|
2431
2634
|
return {
|
|
2432
|
-
id,
|
|
2635
|
+
id: id2,
|
|
2433
2636
|
...optionalText("summary", calendar.summary, 500),
|
|
2434
2637
|
...typeof calendar.primary === "boolean" ? { primary: calendar.primary } : {},
|
|
2435
2638
|
...typeof calendar.selected === "boolean" ? { selected: calendar.selected } : {},
|
|
@@ -2457,10 +2660,10 @@ async function pollCalendarConnection(ctx, attemptId) {
|
|
|
2457
2660
|
}
|
|
2458
2661
|
function parseCalendarStatus(raw, env) {
|
|
2459
2662
|
const outer = wrapped(raw, "calendar");
|
|
2460
|
-
const value2 =
|
|
2461
|
-
const connection =
|
|
2462
|
-
const config =
|
|
2463
|
-
const googleConfig =
|
|
2663
|
+
const value2 = record2(outer.attempt) ?? record2(outer.status) ?? outer;
|
|
2664
|
+
const connection = record2(value2.connection) ?? {};
|
|
2665
|
+
const config = record2(value2.config) ?? record2(outer.config) ?? {};
|
|
2666
|
+
const googleConfig = record2(config.google) ?? config;
|
|
2464
2667
|
const stateValue = calendarState(value2.status ?? value2.state ?? connection.status ?? connection.state);
|
|
2465
2668
|
if (!stateValue) {
|
|
2466
2669
|
throw new Error("calendar status returned an invalid connection state");
|
|
@@ -2473,7 +2676,7 @@ function parseCalendarStatus(raw, env) {
|
|
|
2473
2676
|
if (accessValue !== void 0 && accessValue !== "book" && accessValue !== "read") {
|
|
2474
2677
|
throw new Error("calendar status returned unsupported access");
|
|
2475
2678
|
}
|
|
2476
|
-
const errorValue =
|
|
2679
|
+
const errorValue = record2(value2.error) ?? record2(connection.error);
|
|
2477
2680
|
const errorCode2 = textField(value2.lastErrorCode, 128);
|
|
2478
2681
|
const bookingPageValue = Object.hasOwn(value2, "bookingPageUrl") ? value2.bookingPageUrl : Object.hasOwn(config, "bookingPageUrl") ? config.bookingPageUrl : googleConfig.bookingPageUrl;
|
|
2479
2682
|
const connected = typeof (value2.connected ?? connection.connected) === "boolean" ? Boolean(value2.connected ?? connection.connected) : ["healthy", "degraded"].includes(stateValue);
|
|
@@ -2543,11 +2746,11 @@ async function calendarJson(ctx, suffix, init) {
|
|
|
2543
2746
|
return body;
|
|
2544
2747
|
}
|
|
2545
2748
|
function wrapped(raw, key) {
|
|
2546
|
-
const outer =
|
|
2749
|
+
const outer = record2(raw);
|
|
2547
2750
|
if (!outer) throw new Error("calendar returned an invalid response");
|
|
2548
|
-
return
|
|
2751
|
+
return record2(outer[key]) ?? outer;
|
|
2549
2752
|
}
|
|
2550
|
-
function
|
|
2753
|
+
function record2(value2) {
|
|
2551
2754
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
2552
2755
|
}
|
|
2553
2756
|
function textField(value2, max) {
|
|
@@ -2560,9 +2763,9 @@ function calendarIds(value2) {
|
|
|
2560
2763
|
if (!Array.isArray(value2)) return [];
|
|
2561
2764
|
return [...new Set(value2.flatMap((item) => {
|
|
2562
2765
|
if (typeof item === "string") return textField(item, 4096) ? [item] : [];
|
|
2563
|
-
const calendar =
|
|
2564
|
-
const
|
|
2565
|
-
return
|
|
2766
|
+
const calendar = record2(item);
|
|
2767
|
+
const id2 = textField(calendar?.id, 4096);
|
|
2768
|
+
return id2 && calendar?.selected !== false ? [id2] : [];
|
|
2566
2769
|
}))];
|
|
2567
2770
|
}
|
|
2568
2771
|
function timestamp3(value2) {
|
|
@@ -2797,6 +3000,7 @@ var CAPABILITIES = {
|
|
|
2797
3000
|
"validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
|
|
2798
3001
|
"save and explicitly select non-secret named operator contexts with isolated credential caches; resolve and explain platform, app, environment, and credential provenance without authenticating; then run PM, Discussions, o11y, runbook, and identity operations outside a project checkout",
|
|
2799
3002
|
"read one versioned o11y status envelope spanning application RED, exact Worker versions and Cloudflare colos observed in traffic, current live-sync freshness/load, the protected commit-to-visible canary, collector ingest/scheduler trust, provider-owned runtime metrics, account-scoped Durable Object, D1, and R2 evidence under odla-db, and a bounded machine verdict",
|
|
3003
|
+
"reconcile app-owned Kitesurf probes and rolling SLOs, run live checks, and read incident and digest status as stable JSON",
|
|
2800
3004
|
"read one canonical platform fleet snapshot over private service bindings, including release identities, probe latency, Cloudflare load/runtime freshness, explicit unknowns, and stable next actions through a read-only capability",
|
|
2801
3005
|
"compare one project's checked-in Registry intent with live owner-visible state, freeze a secret-free Registry-revision-bound plan through app:config:read, and conditionally apply checkpoint-free actions through exact app:config:write operation routes",
|
|
2802
3006
|
"inspect or bounded-wait one exact durable config-operation journal entry and verify every terminal receipt digest before returning it to remote automation",
|
|
@@ -2809,7 +3013,8 @@ var CAPABILITIES = {
|
|
|
2809
3013
|
"install and import the selected odla SDKs",
|
|
2810
3014
|
"wrap the Worker with withObservability and choose useful telemetry",
|
|
2811
3015
|
"install capability packages, mount their runtime routes, and make application-specific schema, rules, auth, UI, and migration decisions",
|
|
2812
|
-
"wire @odla-ai/calendar into trusted Worker code and keep the app admin key out of browsers"
|
|
3016
|
+
"wire @odla-ai/calendar into trusted Worker code and keep the app admin key out of browsers",
|
|
3017
|
+
"choose public readiness assertions and SLO objectives in odla.config.mjs, then consume monitor JSON without treating captured page content as trusted instructions"
|
|
2813
3018
|
],
|
|
2814
3019
|
human: [
|
|
2815
3020
|
"provide the existing odla account email, then sign in and explicitly review/approve the exact device code",
|
|
@@ -2822,6 +3027,7 @@ var CAPABILITIES = {
|
|
|
2822
3027
|
],
|
|
2823
3028
|
studio: [
|
|
2824
3029
|
"view application telemetry grouped by exact Worker version, reconciled live-sync load/freshness, commit-to-visible canary health, provider-owned Worker runtime metrics, and environment state",
|
|
3030
|
+
"view reliability objectives, error budget, Kitesurf probe history, incidents, and notification delivery state",
|
|
2825
3031
|
"let signed-in users inventory/revoke their own agent grants and admins audit/global-revoke them",
|
|
2826
3032
|
"review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens",
|
|
2827
3033
|
"perform manual credential recovery \u2014 for the primary owner or any co-owner \u2014 when the CLI's local shown-once copy is unavailable",
|
|
@@ -2943,9 +3149,9 @@ function canonicalValue(value2) {
|
|
|
2943
3149
|
}
|
|
2944
3150
|
if (Array.isArray(value2)) return value2.map(canonicalValue);
|
|
2945
3151
|
if (value2 && typeof value2 === "object") {
|
|
2946
|
-
const
|
|
3152
|
+
const record11 = value2;
|
|
2947
3153
|
return Object.fromEntries(
|
|
2948
|
-
Object.keys(
|
|
3154
|
+
Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, canonicalValue(record11[key])])
|
|
2949
3155
|
);
|
|
2950
3156
|
}
|
|
2951
3157
|
throw new TypeError("canonical JSON rejects unsupported values");
|
|
@@ -2970,8 +3176,8 @@ function readPlan(path) {
|
|
|
2970
3176
|
"invalid_plan"
|
|
2971
3177
|
);
|
|
2972
3178
|
}
|
|
2973
|
-
if (!
|
|
2974
|
-
if (!
|
|
3179
|
+
if (!record3(value2) || value2.schemaVersion !== "odla.config-plan/v2") invalidPlan("unsupported plan schema");
|
|
3180
|
+
if (!record3(value2.scope) || typeof value2.scope.appId !== "string" || typeof value2.scope.platformUrl !== "string") {
|
|
2975
3181
|
invalidPlan("plan scope is invalid");
|
|
2976
3182
|
}
|
|
2977
3183
|
if (!DIGEST.test(String(value2.desiredRevision)) || !DIGEST.test(String(value2.observedRevision))) {
|
|
@@ -3021,10 +3227,10 @@ function assertOperationId(value2) {
|
|
|
3021
3227
|
function assertActions(actions) {
|
|
3022
3228
|
const ids = /* @__PURE__ */ new Set();
|
|
3023
3229
|
for (const action2 of actions) {
|
|
3024
|
-
if (!
|
|
3025
|
-
const
|
|
3026
|
-
if (!ACTION_ID.test(
|
|
3027
|
-
ids.add(
|
|
3230
|
+
if (!record3(action2)) invalidPlan("every plan action must be an object");
|
|
3231
|
+
const id2 = String(action2.id ?? "");
|
|
3232
|
+
if (!ACTION_ID.test(id2) || ids.has(id2)) invalidPlan("plan action ids must be unique frozen ids");
|
|
3233
|
+
ids.add(id2);
|
|
3028
3234
|
if (typeof action2.path !== "string" || typeof action2.reason !== "string" || !action2.reason || action2.reason.length > 500 || typeof action2.requiresApproval !== "boolean" || !["low", "medium", "high"].includes(String(action2.risk)) || !["provision", "command", "studio"].includes(String(action2.applySupport))) {
|
|
3029
3235
|
invalidPlan("plan action metadata is invalid");
|
|
3030
3236
|
}
|
|
@@ -3050,14 +3256,14 @@ function assertConditionalAction(action2) {
|
|
|
3050
3256
|
if (action2.path !== (action2.kind === "configure_service" ? `${base}.config` : base)) {
|
|
3051
3257
|
invalidPlan("service action path is invalid");
|
|
3052
3258
|
}
|
|
3053
|
-
if (action2.applySupport !== "provision" || !
|
|
3259
|
+
if (action2.applySupport !== "provision" || !record3(action2.after)) {
|
|
3054
3260
|
invalidPlan("service action payload is invalid");
|
|
3055
3261
|
}
|
|
3056
3262
|
if (action2.kind === "enable_service") {
|
|
3057
|
-
if (action2.after.enabled !== true || action2.before !== null && !
|
|
3263
|
+
if (action2.after.enabled !== true || action2.before !== null && !record3(action2.before)) {
|
|
3058
3264
|
invalidPlan("service enable action is invalid");
|
|
3059
3265
|
}
|
|
3060
|
-
} else if (!
|
|
3266
|
+
} else if (!record3(action2.before)) {
|
|
3061
3267
|
invalidPlan("service configure action is invalid");
|
|
3062
3268
|
}
|
|
3063
3269
|
}
|
|
@@ -3074,7 +3280,7 @@ function linkState(value2) {
|
|
|
3074
3280
|
function invalidPlan(message2) {
|
|
3075
3281
|
throw new ConfigOperationCommandError(message2, "invalid_plan");
|
|
3076
3282
|
}
|
|
3077
|
-
function
|
|
3283
|
+
function record3(value2) {
|
|
3078
3284
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
3079
3285
|
}
|
|
3080
3286
|
|
|
@@ -3113,9 +3319,9 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
3113
3319
|
}
|
|
3114
3320
|
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
|
|
3115
3321
|
}
|
|
3116
|
-
function errorCode(
|
|
3322
|
+
function errorCode(text3) {
|
|
3117
3323
|
try {
|
|
3118
|
-
const body = JSON.parse(
|
|
3324
|
+
const body = JSON.parse(text3);
|
|
3119
3325
|
return typeof body.error?.code === "string" ? body.error.code : null;
|
|
3120
3326
|
} catch {
|
|
3121
3327
|
return null;
|
|
@@ -3258,7 +3464,7 @@ async function configApply(options) {
|
|
|
3258
3464
|
throw new ConfigOperationCommandError("--idempotency-key is not a safe 1-120 character key", "invalid_plan");
|
|
3259
3465
|
}
|
|
3260
3466
|
const client = await operationClient(cfg, options, "apply");
|
|
3261
|
-
const
|
|
3467
|
+
const request3 = {
|
|
3262
3468
|
schemaVersion: "odla.config-operation-request/v1",
|
|
3263
3469
|
expectedRevision: plan.registryRevision,
|
|
3264
3470
|
desiredRevision: plan.desiredRevision,
|
|
@@ -3270,7 +3476,7 @@ async function configApply(options) {
|
|
|
3270
3476
|
};
|
|
3271
3477
|
let receipt;
|
|
3272
3478
|
try {
|
|
3273
|
-
receipt = await client.applyConfigOperation(cfg.app.id,
|
|
3479
|
+
receipt = await client.applyConfigOperation(cfg.app.id, request3);
|
|
3274
3480
|
} catch (error) {
|
|
3275
3481
|
const retained = retainedReceipt(error);
|
|
3276
3482
|
if (retained) {
|
|
@@ -3369,8 +3575,8 @@ function failureForReceipt(receipt) {
|
|
|
3369
3575
|
return new ConfigOperationCommandError(receipt.error?.message ?? `config operation ${receipt.state}`, "config_operation_failed");
|
|
3370
3576
|
}
|
|
3371
3577
|
function retainedReceipt(error) {
|
|
3372
|
-
if (!(error instanceof import_apps6.AppsError) || !
|
|
3373
|
-
return
|
|
3578
|
+
if (!(error instanceof import_apps6.AppsError) || !record4(error.details)) return null;
|
|
3579
|
+
return record4(error.details.operation) ? error.details.operation : null;
|
|
3374
3580
|
}
|
|
3375
3581
|
function normalizeRequestError(error) {
|
|
3376
3582
|
if (!(error instanceof import_apps6.AppsError)) return error instanceof Error ? error : new Error(String(error));
|
|
@@ -3383,7 +3589,7 @@ function normalizeRequestError(error) {
|
|
|
3383
3589
|
}
|
|
3384
3590
|
return new ConfigOperationCommandError(error.message, error.code || "config_operation_failed");
|
|
3385
3591
|
}
|
|
3386
|
-
function
|
|
3592
|
+
function record4(value2) {
|
|
3387
3593
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
3388
3594
|
}
|
|
3389
3595
|
|
|
@@ -3850,15 +4056,15 @@ function readWranglerConfig(path) {
|
|
|
3850
4056
|
return null;
|
|
3851
4057
|
}
|
|
3852
4058
|
}
|
|
3853
|
-
function stripJsonComments(
|
|
4059
|
+
function stripJsonComments(text3) {
|
|
3854
4060
|
let result = "";
|
|
3855
4061
|
let inString = false;
|
|
3856
|
-
for (let i = 0; i <
|
|
3857
|
-
const ch =
|
|
4062
|
+
for (let i = 0; i < text3.length; i++) {
|
|
4063
|
+
const ch = text3[i];
|
|
3858
4064
|
if (inString) {
|
|
3859
4065
|
result += ch;
|
|
3860
4066
|
if (ch === "\\") {
|
|
3861
|
-
result +=
|
|
4067
|
+
result += text3[i + 1] ?? "";
|
|
3862
4068
|
i++;
|
|
3863
4069
|
} else if (ch === '"') {
|
|
3864
4070
|
inString = false;
|
|
@@ -3870,14 +4076,14 @@ function stripJsonComments(text2) {
|
|
|
3870
4076
|
result += ch;
|
|
3871
4077
|
continue;
|
|
3872
4078
|
}
|
|
3873
|
-
if (ch === "/" &&
|
|
3874
|
-
while (i <
|
|
4079
|
+
if (ch === "/" && text3[i + 1] === "/") {
|
|
4080
|
+
while (i < text3.length && text3[i] !== "\n") i++;
|
|
3875
4081
|
result += "\n";
|
|
3876
4082
|
continue;
|
|
3877
4083
|
}
|
|
3878
|
-
if (ch === "/" &&
|
|
4084
|
+
if (ch === "/" && text3[i + 1] === "*") {
|
|
3879
4085
|
i += 2;
|
|
3880
|
-
while (i <
|
|
4086
|
+
while (i < text3.length && !(text3[i] === "*" && text3[i + 1] === "/")) i++;
|
|
3881
4087
|
i++;
|
|
3882
4088
|
continue;
|
|
3883
4089
|
}
|
|
@@ -3916,7 +4122,7 @@ async function wranglerRuntimeTarget(run, opts) {
|
|
|
3916
4122
|
throw new Error(`wrangler is not logged in \u2014 run "wrangler login" (a browser step for the human)`);
|
|
3917
4123
|
}
|
|
3918
4124
|
const discovered = [...new Set(`${whoami.stdout}
|
|
3919
|
-
${whoami.stderr}`.match(/\b[a-f0-9]{32}\b/gi)?.map((
|
|
4125
|
+
${whoami.stderr}`.match(/\b[a-f0-9]{32}\b/gi)?.map((id2) => id2.toLowerCase()) ?? [])];
|
|
3920
4126
|
const accountId = configuredAccount.toLowerCase() || (discovered.length === 1 ? discovered[0] : "");
|
|
3921
4127
|
if (!/^[a-f0-9]{32}$/.test(accountId) || configuredAccount && discovered.length > 0 && !discovered.includes(accountId)) {
|
|
3922
4128
|
throw new Error("Wrangler account is ambiguous or does not match account_id in the config");
|
|
@@ -4196,6 +4402,67 @@ function isRecord7(value2) {
|
|
|
4196
4402
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
4197
4403
|
}
|
|
4198
4404
|
|
|
4405
|
+
// src/secret-contract.ts
|
|
4406
|
+
var APP_SOURCE = "app";
|
|
4407
|
+
function resolveSecretContract(cfg) {
|
|
4408
|
+
const byName = /* @__PURE__ */ new Map();
|
|
4409
|
+
const declarations = [
|
|
4410
|
+
...(cfg.secrets ?? []).map((secret) => ({ source: APP_SOURCE, secret })),
|
|
4411
|
+
...(cfg.integrations ?? []).flatMap(
|
|
4412
|
+
(integration) => (integration.secrets ?? []).map((secret) => ({ source: integration.id, secret }))
|
|
4413
|
+
)
|
|
4414
|
+
];
|
|
4415
|
+
for (const { source, secret } of declarations) {
|
|
4416
|
+
const existing = byName.get(secret.name);
|
|
4417
|
+
if (!existing) {
|
|
4418
|
+
byName.set(secret.name, { ...secret, required: secret.required !== false, sources: [source] });
|
|
4419
|
+
continue;
|
|
4420
|
+
}
|
|
4421
|
+
existing.sources.push(source);
|
|
4422
|
+
existing.required = existing.required || secret.required !== false;
|
|
4423
|
+
existing.pattern ??= secret.pattern;
|
|
4424
|
+
}
|
|
4425
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
4426
|
+
}
|
|
4427
|
+
function secretContractWarnings(contract, cfg) {
|
|
4428
|
+
const warnings = [];
|
|
4429
|
+
const declaredPatterns = /* @__PURE__ */ new Map();
|
|
4430
|
+
for (const integration of cfg.integrations ?? []) {
|
|
4431
|
+
for (const secret of integration.secrets ?? []) {
|
|
4432
|
+
if (!secret.pattern) continue;
|
|
4433
|
+
const seen = declaredPatterns.get(secret.name) ?? /* @__PURE__ */ new Map();
|
|
4434
|
+
seen.set(integration.id, secret.pattern);
|
|
4435
|
+
declaredPatterns.set(secret.name, seen);
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4438
|
+
for (const secret of cfg.secrets ?? []) {
|
|
4439
|
+
if (!secret.pattern) continue;
|
|
4440
|
+
const seen = declaredPatterns.get(secret.name) ?? /* @__PURE__ */ new Map();
|
|
4441
|
+
seen.set(APP_SOURCE, secret.pattern);
|
|
4442
|
+
declaredPatterns.set(secret.name, seen);
|
|
4443
|
+
}
|
|
4444
|
+
for (const [name, seen] of declaredPatterns) {
|
|
4445
|
+
const distinct = [...new Set(seen.values())];
|
|
4446
|
+
if (distinct.length > 1) {
|
|
4447
|
+
const detail = [...seen].map(([source, pattern]) => `${source} expects "${pattern}"`).join(", ");
|
|
4448
|
+
warnings.push(`secret "${name}" has conflicting patterns \u2014 ${detail}; one of them will reject a valid value`);
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4451
|
+
if (contract.length > 0 && !cfg.services.includes("db")) {
|
|
4452
|
+
const names = contract.map((secret) => secret.name).join(", ");
|
|
4453
|
+
warnings.push(`secrets are declared (${names}) but the db service is off \u2014 nothing can read the tenant vault`);
|
|
4454
|
+
}
|
|
4455
|
+
return warnings;
|
|
4456
|
+
}
|
|
4457
|
+
function formatSecretContract(contract) {
|
|
4458
|
+
return contract.map((secret) => {
|
|
4459
|
+
const flags = [secret.required ? "required" : "optional"];
|
|
4460
|
+
if (secret.reserved) flags.push("reserved");
|
|
4461
|
+
if (secret.pattern) flags.push(`${secret.pattern}\u2026`);
|
|
4462
|
+
return ` ${secret.name} (${flags.join(", ")}) \u2014 ${secret.sources.join(", ")}`;
|
|
4463
|
+
});
|
|
4464
|
+
}
|
|
4465
|
+
|
|
4199
4466
|
// src/doctor.ts
|
|
4200
4467
|
async function doctor(options) {
|
|
4201
4468
|
const out = options.stdout ?? console;
|
|
@@ -4212,6 +4479,9 @@ async function doctor(options) {
|
|
|
4212
4479
|
out.log(`schema: ${schema ? `${entities.length} entities` : "none"}`);
|
|
4213
4480
|
out.log(`rules: ${rules ? `${Object.keys(rules).length} namespaces` : "none"}`);
|
|
4214
4481
|
out.log(`ai: ${cfg.services.includes("ai") ? cfg.ai?.provider ? `byok/${cfg.ai.provider}` : "hosted" : "not enabled"}`);
|
|
4482
|
+
const contract = resolveSecretContract(cfg);
|
|
4483
|
+
out.log(`secrets: ${contract.length ? `${contract.length} declared` : "none declared"}`);
|
|
4484
|
+
for (const line of formatSecretContract(contract)) out.log(line);
|
|
4215
4485
|
if (cfg.services.includes("calendar")) {
|
|
4216
4486
|
const calendar = cfg.envs.map((env) => {
|
|
4217
4487
|
const resolved = calendarServiceConfig(cfg, env);
|
|
@@ -4232,6 +4502,7 @@ async function doctor(options) {
|
|
|
4232
4502
|
}
|
|
4233
4503
|
}
|
|
4234
4504
|
warnings.push(...integrationWarnings(database.integrations, schema, rules));
|
|
4505
|
+
warnings.push(...secretContractWarnings(contract, cfg));
|
|
4235
4506
|
if (cfg.services.includes("ai") && cfg.ai?.mode === "byok" && !cfg.ai.provider) {
|
|
4236
4507
|
warnings.push("ai.mode is byok but ai.provider is not set");
|
|
4237
4508
|
}
|
|
@@ -4326,9 +4597,9 @@ function initProject(options) {
|
|
|
4326
4597
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
4327
4598
|
out.log("updated .gitignore for local odla credentials");
|
|
4328
4599
|
}
|
|
4329
|
-
function writeIfMissing(path,
|
|
4600
|
+
function writeIfMissing(path, text3) {
|
|
4330
4601
|
if ((0, import_node_fs12.existsSync)(path)) return;
|
|
4331
|
-
(0, import_node_fs12.writeFileSync)(path,
|
|
4602
|
+
(0, import_node_fs12.writeFileSync)(path, text3);
|
|
4332
4603
|
}
|
|
4333
4604
|
function configTemplate(input) {
|
|
4334
4605
|
const calendar = input.services.includes("calendar") ? ` calendar: {
|
|
@@ -4538,13 +4809,13 @@ async function secretsSetClerkKey(options) {
|
|
|
4538
4809
|
body: JSON.stringify({ value: value2 })
|
|
4539
4810
|
});
|
|
4540
4811
|
if (!res.ok) {
|
|
4541
|
-
const
|
|
4542
|
-
throw new Error(`store Clerk secret key failed (${res.status}): ${
|
|
4812
|
+
const text3 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
|
|
4813
|
+
throw new Error(`store Clerk secret key failed (${res.status}): ${text3 || "request failed"}`);
|
|
4543
4814
|
}
|
|
4544
4815
|
out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
|
|
4545
4816
|
}
|
|
4546
|
-
function scrubValue(
|
|
4547
|
-
return redactSecrets(
|
|
4817
|
+
function scrubValue(text3, value2) {
|
|
4818
|
+
return redactSecrets(text3).split(value2).join("[value redacted]");
|
|
4548
4819
|
}
|
|
4549
4820
|
async function resolveVaultWrite(options) {
|
|
4550
4821
|
const out = options.stdout ?? console;
|
|
@@ -4558,6 +4829,71 @@ async function resolveVaultWrite(options) {
|
|
|
4558
4829
|
return { cfg, tenantId: (0, import_apps10.tenantIdFor)(cfg.app.id, env), value: value2, doFetch, out };
|
|
4559
4830
|
}
|
|
4560
4831
|
|
|
4832
|
+
// src/secrets-status.ts
|
|
4833
|
+
var import_apps11 = require("@odla-ai/apps");
|
|
4834
|
+
async function secretsStatus(options) {
|
|
4835
|
+
const out = options.stdout ?? console;
|
|
4836
|
+
const doFetch = options.fetch ?? fetch;
|
|
4837
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
4838
|
+
if (!cfg.envs.includes(options.env)) {
|
|
4839
|
+
throw new Error(`env "${options.env}" is not in config envs (${cfg.envs.join(", ")})`);
|
|
4840
|
+
}
|
|
4841
|
+
const tenantId = (0, import_apps11.tenantIdFor)(cfg.app.id, options.env);
|
|
4842
|
+
const contract = resolveSecretContract(cfg);
|
|
4843
|
+
const token = await getDeveloperToken(cfg, options, doFetch, out);
|
|
4844
|
+
const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/secrets`, {
|
|
4845
|
+
headers: { authorization: `Bearer ${token}` }
|
|
4846
|
+
});
|
|
4847
|
+
if (!res.ok) {
|
|
4848
|
+
const detail = (await res.text().catch(() => "")).slice(0, 300);
|
|
4849
|
+
throw new Error(`list secrets for ${tenantId} failed (${res.status}): ${detail || "request failed"}`);
|
|
4850
|
+
}
|
|
4851
|
+
const body = await res.json();
|
|
4852
|
+
const stored = new Set((body.secrets ?? []).map((entry) => String(entry.name)));
|
|
4853
|
+
const report4 = buildReport(cfg.app.id, options.env, tenantId, contract, stored);
|
|
4854
|
+
if (options.json) out.log(JSON.stringify(report4, null, 2));
|
|
4855
|
+
else printReport(report4, out);
|
|
4856
|
+
return report4;
|
|
4857
|
+
}
|
|
4858
|
+
function buildReport(appId, env, tenant, contract, stored) {
|
|
4859
|
+
const declared = new Set(contract.map((secret) => secret.name));
|
|
4860
|
+
const rows = contract.map((secret) => ({
|
|
4861
|
+
name: secret.name,
|
|
4862
|
+
state: secret.reserved ? "reserved" : stored.has(secret.name) ? "set" : "missing",
|
|
4863
|
+
required: secret.required,
|
|
4864
|
+
sources: secret.sources,
|
|
4865
|
+
description: secret.description
|
|
4866
|
+
}));
|
|
4867
|
+
for (const name of [...stored].sort()) {
|
|
4868
|
+
if (!declared.has(name)) rows.push({ name, state: "undeclared", required: false, sources: [] });
|
|
4869
|
+
}
|
|
4870
|
+
const ok = rows.every((row) => row.state !== "missing" || !row.required);
|
|
4871
|
+
return { app: appId, env, tenant, secrets: rows, ok };
|
|
4872
|
+
}
|
|
4873
|
+
function printReport(report4, out) {
|
|
4874
|
+
out.log(`${report4.app} (${report4.tenant})`);
|
|
4875
|
+
if (report4.secrets.length === 0) {
|
|
4876
|
+
out.log(" no secrets declared and none stored");
|
|
4877
|
+
return;
|
|
4878
|
+
}
|
|
4879
|
+
for (const row of report4.secrets) {
|
|
4880
|
+
const label = row.state === "missing" && !row.required ? "missing (optional)" : row.state;
|
|
4881
|
+
const suffix = row.sources.length ? ` \u2014 ${row.sources.join(", ")}` : "";
|
|
4882
|
+
out.log(` ${label.padEnd(18)} ${row.name}${suffix}`);
|
|
4883
|
+
}
|
|
4884
|
+
const missing = report4.secrets.filter((row) => row.state === "missing" && row.required);
|
|
4885
|
+
if (missing.length) {
|
|
4886
|
+
out.log("");
|
|
4887
|
+
for (const row of missing) {
|
|
4888
|
+
out.log(`${row.name} is required but not set \u2014 "odla-ai secrets set ${row.name} --env ${report4.env} --stdin"`);
|
|
4889
|
+
}
|
|
4890
|
+
}
|
|
4891
|
+
if (report4.secrets.some((row) => row.state === "reserved")) {
|
|
4892
|
+
out.log("");
|
|
4893
|
+
out.log('"reserved" slots are never enumerated by the vault; presence cannot be confirmed here.');
|
|
4894
|
+
}
|
|
4895
|
+
}
|
|
4896
|
+
|
|
4561
4897
|
// src/skill.ts
|
|
4562
4898
|
var import_node_fs13 = require("fs");
|
|
4563
4899
|
var import_node_os2 = require("os");
|
|
@@ -4613,8 +4949,8 @@ alwaysApply: false
|
|
|
4613
4949
|
|
|
4614
4950
|
${PROJECT_INSTRUCTIONS}
|
|
4615
4951
|
`;
|
|
4616
|
-
function claudeAdapter(skill,
|
|
4617
|
-
const match =
|
|
4952
|
+
function claudeAdapter(skill, canonical2) {
|
|
4953
|
+
const match = canonical2.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
4618
4954
|
if (!match) throw new Error(`bundled skill ${skill} has no YAML frontmatter`);
|
|
4619
4955
|
const lines = match[1].split(/\r?\n/);
|
|
4620
4956
|
const frontmatter = [];
|
|
@@ -4684,8 +5020,8 @@ function installSkill(options = {}) {
|
|
|
4684
5020
|
for (const harness of harnesses) rememberTarget(harness, sharedRoot);
|
|
4685
5021
|
if (harnesses.includes("claude")) {
|
|
4686
5022
|
for (const skill of skillNames(files)) {
|
|
4687
|
-
const
|
|
4688
|
-
plan((0, import_node_path13.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill,
|
|
5023
|
+
const canonical2 = (0, import_node_fs13.readFileSync)((0, import_node_path13.join)(sourceDir, skill, "SKILL.md"), "utf8");
|
|
5024
|
+
plan((0, import_node_path13.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical2));
|
|
4689
5025
|
}
|
|
4690
5026
|
rememberTarget("claude", claudeRoot);
|
|
4691
5027
|
}
|
|
@@ -4961,7 +5297,7 @@ function assertCalendarHealthy(status, expected) {
|
|
|
4961
5297
|
if (!status.writable) throw new Error('calendar grant does not cover booking writes; run "odla-ai calendar connect" to re-consent');
|
|
4962
5298
|
const hasEventsScope = status.grantedScopes.some((scope) => scope === GOOGLE_CALENDAR_EVENTS_SCOPE);
|
|
4963
5299
|
if (!hasEventsScope) throw new Error("calendar connection is missing calendar.events consent");
|
|
4964
|
-
const missing = expected.availabilityCalendars.filter((
|
|
5300
|
+
const missing = expected.availabilityCalendars.filter((id2) => !status.calendars.includes(id2));
|
|
4965
5301
|
if (missing.length) throw new Error(`calendar connection is missing configured calendars: ${missing.join(", ")}`);
|
|
4966
5302
|
}
|
|
4967
5303
|
async function getJson(doFetch, url, bearer) {
|
|
@@ -5027,9 +5363,22 @@ async function secretsCommand(parsed, deps) {
|
|
|
5027
5363
|
await (sub === "set" ? secretsSet(options) : secretsSetClerkKey(options));
|
|
5028
5364
|
return;
|
|
5029
5365
|
}
|
|
5366
|
+
if (sub === "status") {
|
|
5367
|
+
assertArgs(parsed, ["config", "env", "token", "email", "json"], 2);
|
|
5368
|
+
await secretsStatus({
|
|
5369
|
+
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
5370
|
+
env: requiredString(parsed.options.env, "--env"),
|
|
5371
|
+
json: parsed.options.json === true,
|
|
5372
|
+
token: stringOpt(parsed.options.token),
|
|
5373
|
+
email: stringOpt(parsed.options.email),
|
|
5374
|
+
fetch: deps.fetch,
|
|
5375
|
+
stdout: deps.stdout
|
|
5376
|
+
});
|
|
5377
|
+
return;
|
|
5378
|
+
}
|
|
5030
5379
|
if (sub !== "push") {
|
|
5031
5380
|
throw new Error(
|
|
5032
|
-
`unknown secrets subcommand "${sub ?? ""}". Try "odla-ai secrets push --env dev", "odla-ai secrets set <name> --env dev --stdin", or "odla-ai secrets set-clerk-key --env dev --stdin".`
|
|
5381
|
+
`unknown secrets subcommand "${sub ?? ""}". Try "odla-ai secrets push --env dev", "odla-ai secrets status --env dev", "odla-ai secrets set <name> --env dev --stdin", or "odla-ai secrets set-clerk-key --env dev --stdin".`
|
|
5033
5382
|
);
|
|
5034
5383
|
}
|
|
5035
5384
|
assertArgs(parsed, ["config", "env", "dry-run", "yes"], 2);
|
|
@@ -5348,8 +5697,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
5348
5697
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
5349
5698
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
5350
5699
|
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
5351
|
-
const entries = inventory.flatMap((
|
|
5352
|
-
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(
|
|
5700
|
+
const entries = inventory.flatMap((record11) => {
|
|
5701
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record11);
|
|
5353
5702
|
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
5354
5703
|
});
|
|
5355
5704
|
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
@@ -5553,7 +5902,7 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
|
|
|
5553
5902
|
}
|
|
5554
5903
|
}
|
|
5555
5904
|
|
|
5556
|
-
// ../harness/dist/chunk-
|
|
5905
|
+
// ../harness/dist/chunk-ANNX7VGK.js
|
|
5557
5906
|
var import_crypto = require("crypto");
|
|
5558
5907
|
var import_promises5 = require("fs/promises");
|
|
5559
5908
|
var import_path5 = require("path");
|
|
@@ -5597,8 +5946,8 @@ function normalize(value2) {
|
|
|
5597
5946
|
if (Array.isArray(value2)) return value2.map(normalize);
|
|
5598
5947
|
if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
|
|
5599
5948
|
if (typeof value2 === "object") {
|
|
5600
|
-
const
|
|
5601
|
-
return Object.fromEntries(Object.keys(
|
|
5949
|
+
const record11 = value2;
|
|
5950
|
+
return Object.fromEntries(Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, normalize(record11[key])]));
|
|
5602
5951
|
}
|
|
5603
5952
|
throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
|
|
5604
5953
|
}
|
|
@@ -5613,9 +5962,9 @@ function dependenciesOf(values, influence = "data") {
|
|
|
5613
5962
|
result.push({ ref, influence, promptSafetyAtUse: value2.label.promptSafety });
|
|
5614
5963
|
}
|
|
5615
5964
|
}
|
|
5616
|
-
const
|
|
5617
|
-
for (const dep of result)
|
|
5618
|
-
return [...
|
|
5965
|
+
const unique4 = /* @__PURE__ */ new Map();
|
|
5966
|
+
for (const dep of result) unique4.set(`${dep.ref.kind}\0${dep.ref.id}\0${dep.influence}\0${dep.promptSafetyAtUse}`, dep);
|
|
5967
|
+
return [...unique4.values()];
|
|
5619
5968
|
}
|
|
5620
5969
|
|
|
5621
5970
|
// ../camel/dist/chunk-4DQ6BIHP.js
|
|
@@ -5672,7 +6021,7 @@ function copyRef(ref) {
|
|
|
5672
6021
|
function normalizeReaders(readers) {
|
|
5673
6022
|
if (readers.kind === "public") return Object.freeze({ kind: "public" });
|
|
5674
6023
|
const principalIds = [...new Set(readers.principalIds)].sort();
|
|
5675
|
-
if (principalIds.some((
|
|
6024
|
+
if (principalIds.some((id2) => !id2)) throw new CamelError("reader_mismatch", "Reader principal IDs must be non-empty.");
|
|
5676
6025
|
return Object.freeze({ kind: "principals", principalIds: Object.freeze(principalIds) });
|
|
5677
6026
|
}
|
|
5678
6027
|
|
|
@@ -5682,7 +6031,7 @@ var SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
|
5682
6031
|
var ID = /^[A-Za-z0-9._:-]{1,160}$/;
|
|
5683
6032
|
async function digestCodeVerificationReceipt(fields) {
|
|
5684
6033
|
validate(fields);
|
|
5685
|
-
const
|
|
6034
|
+
const canonical2 = {
|
|
5686
6035
|
schemaVersion: fields.schemaVersion,
|
|
5687
6036
|
verificationId: fields.verificationId,
|
|
5688
6037
|
trustedBaseCommitSha: fields.trustedBaseCommitSha,
|
|
@@ -5709,7 +6058,7 @@ async function digestCodeVerificationReceipt(fields) {
|
|
|
5709
6058
|
changedTestsRequireReview: fields.changedTestsRequireReview,
|
|
5710
6059
|
outcome: fields.outcome
|
|
5711
6060
|
};
|
|
5712
|
-
return `sha256:${await sha256Hex(canonicalJson2(
|
|
6061
|
+
return `sha256:${await sha256Hex(canonicalJson2(canonical2))}`;
|
|
5713
6062
|
}
|
|
5714
6063
|
function validate(fields) {
|
|
5715
6064
|
if (fields.schemaVersion !== 1 || typeof fields.verificationId !== "string" || !ID.test(fields.verificationId) || typeof fields.trustedBaseCommitSha !== "string" || !SHA.test(fields.trustedBaseCommitSha) || !DIGEST2.test(fields.trustedBaseDigest) || !DIGEST2.test(fields.patchDigest) || !DIGEST2.test(fields.candidateDigest) || !DIGEST2.test(fields.sourceDigest) || !DIGEST2.test(fields.policyDigest) || !DIGEST2.test(fields.changedTestSetDigest) || !Number.isSafeInteger(fields.changedTestCount) || fields.changedTestCount < 0 || fields.changedTestCount > 1e4 || fields.changedTestsRequireReview !== fields.changedTestCount > 0 || fields.recipes.length < 1 || fields.recipes.length > 64) {
|
|
@@ -5890,7 +6239,7 @@ function validateSnapshot(snapshot, limits) {
|
|
|
5890
6239
|
}
|
|
5891
6240
|
}
|
|
5892
6241
|
|
|
5893
|
-
// ../harness/dist/chunk-
|
|
6242
|
+
// ../harness/dist/chunk-ANNX7VGK.js
|
|
5894
6243
|
var import_child_process4 = require("child_process");
|
|
5895
6244
|
var import_promises6 = require("fs/promises");
|
|
5896
6245
|
var import_path6 = require("path");
|
|
@@ -5924,7 +6273,7 @@ async function createConversionRegistry(config) {
|
|
|
5924
6273
|
if (await conversionPolicyDigest(definition) !== policy.digest) throw new CamelError("state_conflict", "Conversion policy digest mismatch.");
|
|
5925
6274
|
if (policy.output.kind === "registered_id") {
|
|
5926
6275
|
const registry = config.registeredIds?.[policy.output.registryId];
|
|
5927
|
-
const validValues = registry && Object.entries(registry.values).every(([candidate,
|
|
6276
|
+
const validValues = registry && Object.entries(registry.values).every(([candidate, id2]) => candidate.length > 0 && typeof id2 === "string" && id2.length > 0);
|
|
5928
6277
|
if (!registry || !validValues || registry.digest !== policy.output.registryDigest || await registeredIdRegistryDigest(registry.values) !== registry.digest) {
|
|
5929
6278
|
throw new CamelError("state_conflict", "Registered-ID registry digest mismatch.");
|
|
5930
6279
|
}
|
|
@@ -5932,63 +6281,63 @@ async function createConversionRegistry(config) {
|
|
|
5932
6281
|
policies.set(policy.conversionId, Object.freeze(policy));
|
|
5933
6282
|
}
|
|
5934
6283
|
const outputCounts = /* @__PURE__ */ new Map();
|
|
5935
|
-
const get = (
|
|
5936
|
-
const policy = policies.get(
|
|
6284
|
+
const get = (id2, kind) => {
|
|
6285
|
+
const policy = policies.get(id2);
|
|
5937
6286
|
if (!policy || policy.output.kind !== kind) throw new CamelError("conversion_rejected", "Conversion policy is missing or has the wrong output kind.");
|
|
5938
6287
|
return policy;
|
|
5939
6288
|
};
|
|
5940
|
-
const checked = (source,
|
|
5941
|
-
const policy = get(
|
|
6289
|
+
const checked = (source, id2, kind) => {
|
|
6290
|
+
const policy = get(id2, kind);
|
|
5942
6291
|
if (utf8Length(source.value) > policy.maximumSourceBytes) throw new CamelError("limit_exceeded", "Unsafe conversion input exceeds its byte bound.");
|
|
5943
6292
|
return policy;
|
|
5944
6293
|
};
|
|
5945
|
-
const
|
|
6294
|
+
const emit4 = (source, policy, value2) => convert(source, policy, value2, outputCounts);
|
|
5946
6295
|
const operations = Object.freeze({
|
|
5947
|
-
boolean: async (value2,
|
|
5948
|
-
const policy = checked(value2,
|
|
5949
|
-
return
|
|
6296
|
+
boolean: async (value2, id2) => {
|
|
6297
|
+
const policy = checked(value2, id2, "boolean");
|
|
6298
|
+
return emit4(value2, policy, requireBoolean(value2.value));
|
|
5950
6299
|
},
|
|
5951
|
-
integer: async (value2,
|
|
5952
|
-
const policy = checked(value2,
|
|
5953
|
-
return
|
|
6300
|
+
integer: async (value2, id2) => {
|
|
6301
|
+
const policy = checked(value2, id2, "integer");
|
|
6302
|
+
return emit4(value2, policy, boundedInteger(value2.value, policy.output));
|
|
5954
6303
|
},
|
|
5955
|
-
finiteNumber: async (value2,
|
|
5956
|
-
const policy = checked(value2,
|
|
5957
|
-
return
|
|
6304
|
+
finiteNumber: async (value2, id2) => {
|
|
6305
|
+
const policy = checked(value2, id2, "finite_number");
|
|
6306
|
+
return emit4(value2, policy, boundedNumber(value2.value, policy.output));
|
|
5958
6307
|
},
|
|
5959
|
-
enum: async (value2,
|
|
5960
|
-
const policy = checked(value2,
|
|
5961
|
-
return
|
|
6308
|
+
enum: async (value2, id2) => {
|
|
6309
|
+
const policy = checked(value2, id2, "enum");
|
|
6310
|
+
return emit4(value2, policy, enumMember(value2.value, policy.output));
|
|
5962
6311
|
},
|
|
5963
|
-
date: async (value2,
|
|
5964
|
-
const policy = checked(value2,
|
|
5965
|
-
return
|
|
6312
|
+
date: async (value2, id2) => {
|
|
6313
|
+
const policy = checked(value2, id2, "date");
|
|
6314
|
+
return emit4(value2, policy, canonicalDate(value2.value, policy.output));
|
|
5966
6315
|
},
|
|
5967
|
-
registeredId: async (value2,
|
|
5968
|
-
const policy = checked(value2,
|
|
6316
|
+
registeredId: async (value2, id2) => {
|
|
6317
|
+
const policy = checked(value2, id2, "registered_id");
|
|
5969
6318
|
const registry = config.registeredIds?.[policy.output.registryId];
|
|
5970
6319
|
const output = typeof value2.value === "string" ? registry?.values[value2.value] : void 0;
|
|
5971
6320
|
if (!output) throw new CamelError("conversion_rejected", "Registered-ID conversion rejected the candidate.");
|
|
5972
|
-
return
|
|
6321
|
+
return emit4(value2, policy, output);
|
|
5973
6322
|
},
|
|
5974
|
-
digest: async (value2,
|
|
5975
|
-
const policy = checked(value2,
|
|
6323
|
+
digest: async (value2, id2) => {
|
|
6324
|
+
const policy = checked(value2, id2, "digest");
|
|
5976
6325
|
if (!(value2.value instanceof Uint8Array)) throw new CamelError("conversion_rejected", "Digest conversion requires bytes.");
|
|
5977
|
-
return
|
|
6326
|
+
return emit4(value2, policy, await sha256Hex(value2.value));
|
|
5978
6327
|
},
|
|
5979
|
-
measure: async (value2, metric,
|
|
5980
|
-
const policy = checked(value2,
|
|
6328
|
+
measure: async (value2, metric, id2) => {
|
|
6329
|
+
const policy = checked(value2, id2, "integer");
|
|
5981
6330
|
const measured = measure(value2.value, metric);
|
|
5982
|
-
return
|
|
6331
|
+
return emit4(value2, policy, boundedInteger(measured, policy.output));
|
|
5983
6332
|
},
|
|
5984
|
-
test: async (value2, predicateId,
|
|
5985
|
-
const policy = checked(value2,
|
|
6333
|
+
test: async (value2, predicateId, id2) => {
|
|
6334
|
+
const policy = checked(value2, id2, "boolean");
|
|
5986
6335
|
const predicate = config.predicates?.[predicateId];
|
|
5987
6336
|
if (!predicate) throw new CamelError("conversion_rejected", "Predicate is not registered.");
|
|
5988
|
-
return
|
|
6337
|
+
return emit4(value2, policy, evaluatePredicate(value2.value, predicate, config.registeredIds));
|
|
5989
6338
|
}
|
|
5990
6339
|
});
|
|
5991
|
-
return Object.freeze({ operations, policy: (
|
|
6340
|
+
return Object.freeze({ operations, policy: (id2) => policies.get(id2) ?? missingPolicy() });
|
|
5992
6341
|
}
|
|
5993
6342
|
async function convert(source, policy, value2, counts) {
|
|
5994
6343
|
const sourceKey = sourceIdentity(source);
|
|
@@ -6031,8 +6380,8 @@ function boundedInteger(value2, spec) {
|
|
|
6031
6380
|
}
|
|
6032
6381
|
function boundedNumber(value2, spec) {
|
|
6033
6382
|
if (spec.kind !== "finite_number" || typeof value2 !== "number" || !Number.isFinite(value2) || value2 < spec.minimum || value2 > spec.maximum) throw new CamelError("conversion_rejected", "Finite-number conversion rejected the structured value.");
|
|
6034
|
-
const
|
|
6035
|
-
if (/e/i.test(
|
|
6383
|
+
const text3 = String(value2);
|
|
6384
|
+
if (/e/i.test(text3) || (text3.split(".")[1]?.length ?? 0) > spec.maximumDecimalPlaces) throw new CamelError("conversion_rejected", "Finite-number conversion rejected a non-canonical decimal.");
|
|
6036
6385
|
return value2;
|
|
6037
6386
|
}
|
|
6038
6387
|
function enumMember(value2, spec) {
|
|
@@ -6083,10 +6432,10 @@ function createCamelIngress(constants2 = []) {
|
|
|
6083
6432
|
const ingress = {
|
|
6084
6433
|
userInstruction: (value2, input) => createSafeInternal(value2, "user_instruction", metadata("user_instruction", input.id, input.readers)),
|
|
6085
6434
|
systemPolicy: (value2, input) => createSafeInternal(value2, "system_policy", metadata("system_policy", input.id, input.readers)),
|
|
6086
|
-
control: (
|
|
6087
|
-
const item = byId.get(
|
|
6435
|
+
control: (id2) => {
|
|
6436
|
+
const item = byId.get(id2);
|
|
6088
6437
|
if (!item) throw new CamelError("permission_denied", "Unknown control constant.");
|
|
6089
|
-
return createSafeInternal(item.value, "harness_constant", metadata("harness",
|
|
6438
|
+
return createSafeInternal(item.value, "harness_constant", metadata("harness", id2, item.readers));
|
|
6090
6439
|
},
|
|
6091
6440
|
external: (value2, label) => createUnsafeInternal(value2, label),
|
|
6092
6441
|
quarantinedOutput: (value2, input) => {
|
|
@@ -6110,9 +6459,9 @@ function assertNoUnsafeConstant(value2, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
6110
6459
|
}
|
|
6111
6460
|
for (const child of Object.values(value2)) assertNoUnsafeConstant(child, seen);
|
|
6112
6461
|
}
|
|
6113
|
-
function metadata(kind,
|
|
6114
|
-
if (!
|
|
6115
|
-
return { readers, provenance: [{ kind, id }] };
|
|
6462
|
+
function metadata(kind, id2, readers) {
|
|
6463
|
+
if (!id2) throw new CamelError("state_conflict", "Provenance IDs must be non-empty.");
|
|
6464
|
+
return { readers, provenance: [{ kind, id: id2 }] };
|
|
6116
6465
|
}
|
|
6117
6466
|
|
|
6118
6467
|
// ../camel/dist/policy.js
|
|
@@ -6176,7 +6525,7 @@ function isControlOwned(value2) {
|
|
|
6176
6525
|
return value2.label.safeBasis === "system_policy" || value2.label.safeBasis === "harness_constant";
|
|
6177
6526
|
}
|
|
6178
6527
|
function copyRegistries(registries) {
|
|
6179
|
-
return Object.freeze(Object.fromEntries(Object.entries(registries).map(([
|
|
6528
|
+
return Object.freeze(Object.fromEntries(Object.entries(registries).map(([id2, registry]) => [id2, Object.freeze({ digest: registry.digest, values: Object.freeze([...registry.values]) })])));
|
|
6180
6529
|
}
|
|
6181
6530
|
function validateUnsafeSelector(path, value2, tool) {
|
|
6182
6531
|
const policy = tool.unsafeSelectorPolicy;
|
|
@@ -6188,20 +6537,20 @@ function validateUnsafeSelector(path, value2, tool) {
|
|
|
6188
6537
|
return void 0;
|
|
6189
6538
|
}
|
|
6190
6539
|
function looksLikeDestination(value2) {
|
|
6191
|
-
const
|
|
6192
|
-
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(
|
|
6540
|
+
const text3 = value2.trim();
|
|
6541
|
+
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text3) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text3);
|
|
6193
6542
|
}
|
|
6194
6543
|
|
|
6195
|
-
// ../harness/dist/chunk-
|
|
6544
|
+
// ../harness/dist/chunk-ANNX7VGK.js
|
|
6196
6545
|
var import_promises10 = require("fs/promises");
|
|
6197
6546
|
var import_promises11 = require("fs/promises");
|
|
6198
6547
|
var import_path10 = require("path");
|
|
6199
6548
|
|
|
6200
6549
|
// ../graph/dist/chunk-PS2SO4UP.js
|
|
6201
6550
|
var nodeId = (kind, name) => `${kind}:${name}`;
|
|
6202
|
-
function parseNodeId(
|
|
6203
|
-
const at =
|
|
6204
|
-
return at < 0 ? { kind: "", name:
|
|
6551
|
+
function parseNodeId(id2) {
|
|
6552
|
+
const at = id2.indexOf(":");
|
|
6553
|
+
return at < 0 ? { kind: "", name: id2 } : { kind: id2.slice(0, at), name: id2.slice(at + 1) };
|
|
6205
6554
|
}
|
|
6206
6555
|
var GraphBuilder = class {
|
|
6207
6556
|
byId = /* @__PURE__ */ new Map();
|
|
@@ -6209,14 +6558,14 @@ var GraphBuilder = class {
|
|
|
6209
6558
|
seen = /* @__PURE__ */ new Set();
|
|
6210
6559
|
/** Add or enrich a node. Later attributes win; the kind never changes. */
|
|
6211
6560
|
node(kind, name, attrs) {
|
|
6212
|
-
const
|
|
6213
|
-
const existing = this.byId.get(
|
|
6561
|
+
const id2 = nodeId(kind, name);
|
|
6562
|
+
const existing = this.byId.get(id2);
|
|
6214
6563
|
if (existing) {
|
|
6215
|
-
if (attrs) this.byId.set(
|
|
6216
|
-
return
|
|
6564
|
+
if (attrs) this.byId.set(id2, { ...existing, attrs: { ...existing.attrs, ...attrs } });
|
|
6565
|
+
return id2;
|
|
6217
6566
|
}
|
|
6218
|
-
this.byId.set(
|
|
6219
|
-
return
|
|
6567
|
+
this.byId.set(id2, { id: id2, kind, name, ...attrs ? { attrs } : {} });
|
|
6568
|
+
return id2;
|
|
6220
6569
|
}
|
|
6221
6570
|
/**
|
|
6222
6571
|
* Add a directed edge, minting either endpoint if it is not known yet.
|
|
@@ -6226,10 +6575,10 @@ var GraphBuilder = class {
|
|
|
6226
6575
|
* by how often someone repeated an import.
|
|
6227
6576
|
*/
|
|
6228
6577
|
edge(from, kind, to, attrs) {
|
|
6229
|
-
for (const
|
|
6230
|
-
if (!this.byId.has(
|
|
6231
|
-
const parsed = parseNodeId(
|
|
6232
|
-
this.byId.set(
|
|
6578
|
+
for (const id2 of [from, to]) {
|
|
6579
|
+
if (!this.byId.has(id2)) {
|
|
6580
|
+
const parsed = parseNodeId(id2);
|
|
6581
|
+
this.byId.set(id2, { id: id2, kind: parsed.kind, name: parsed.name });
|
|
6233
6582
|
}
|
|
6234
6583
|
}
|
|
6235
6584
|
const key = `${from} ${kind} ${to}`;
|
|
@@ -6262,18 +6611,18 @@ function nodesOfKind(graph, kind) {
|
|
|
6262
6611
|
|
|
6263
6612
|
// ../graph/dist/index.js
|
|
6264
6613
|
var follows = (kinds, edge) => !kinds || kinds.includes(edge.kind);
|
|
6265
|
-
function incident(graph,
|
|
6614
|
+
function incident(graph, id2, traversal = {}) {
|
|
6266
6615
|
const direction = traversal.direction ?? "out";
|
|
6267
|
-
const forward = direction === "out" || direction === "both" ? graph.out.get(
|
|
6268
|
-
const backward = direction === "in" || direction === "both" ? graph.in.get(
|
|
6616
|
+
const forward = direction === "out" || direction === "both" ? graph.out.get(id2) ?? [] : [];
|
|
6617
|
+
const backward = direction === "in" || direction === "both" ? graph.in.get(id2) ?? [] : [];
|
|
6269
6618
|
return [...forward, ...backward].filter((edge) => follows(traversal.kinds, edge));
|
|
6270
6619
|
}
|
|
6271
6620
|
var otherEnd = (edge, from) => edge.from === from ? edge.to : edge.from;
|
|
6272
|
-
function neighbors(graph,
|
|
6621
|
+
function neighbors(graph, id2, traversal = {}) {
|
|
6273
6622
|
const seen = /* @__PURE__ */ new Set();
|
|
6274
|
-
for (const edge of incident(graph,
|
|
6275
|
-
const other = otherEnd(edge,
|
|
6276
|
-
if (other !==
|
|
6623
|
+
for (const edge of incident(graph, id2, traversal)) {
|
|
6624
|
+
const other = otherEnd(edge, id2);
|
|
6625
|
+
if (other !== id2) seen.add(other);
|
|
6277
6626
|
}
|
|
6278
6627
|
return [...seen];
|
|
6279
6628
|
}
|
|
@@ -6357,9 +6706,9 @@ async function extractImports(builder, input) {
|
|
|
6357
6706
|
const sources = input.paths.filter(isSourcePath);
|
|
6358
6707
|
const known = new Set(sources);
|
|
6359
6708
|
for (const path of sources) {
|
|
6360
|
-
let
|
|
6709
|
+
let text3;
|
|
6361
6710
|
try {
|
|
6362
|
-
|
|
6711
|
+
text3 = await input.read(path);
|
|
6363
6712
|
} catch {
|
|
6364
6713
|
continue;
|
|
6365
6714
|
}
|
|
@@ -6367,13 +6716,13 @@ async function extractImports(builder, input) {
|
|
|
6367
6716
|
const file = builder.node(FILE, path, pkg ? { pkg } : void 0);
|
|
6368
6717
|
if (pkg) builder.edge(builder.node(PACKAGE, pkg), CONTAINS, file);
|
|
6369
6718
|
const specifiers = /* @__PURE__ */ new Set();
|
|
6370
|
-
for (const match of
|
|
6371
|
-
for (const match of
|
|
6719
|
+
for (const match of text3.matchAll(IMPORT_FROM)) specifiers.add(match[1]);
|
|
6720
|
+
for (const match of text3.matchAll(BARE_IMPORT)) specifiers.add(match[1]);
|
|
6372
6721
|
for (const specifier of specifiers) {
|
|
6373
6722
|
const resolved = resolveImport(path, specifier, known);
|
|
6374
6723
|
if (resolved) builder.edge(file, IMPORTS, nodeId(FILE, resolved));
|
|
6375
6724
|
}
|
|
6376
|
-
for (const name of exportedNames(
|
|
6725
|
+
for (const name of exportedNames(text3)) {
|
|
6377
6726
|
builder.edge(file, EXPORTS, builder.node(SYMBOL, name));
|
|
6378
6727
|
}
|
|
6379
6728
|
}
|
|
@@ -6414,16 +6763,16 @@ async function extractData(builder, input) {
|
|
|
6414
6763
|
};
|
|
6415
6764
|
for (const path of input.paths) {
|
|
6416
6765
|
if (!SOURCE_FILE.test(path) || input.ignore?.(path)) continue;
|
|
6417
|
-
let
|
|
6766
|
+
let text3;
|
|
6418
6767
|
try {
|
|
6419
|
-
|
|
6768
|
+
text3 = await input.read(path);
|
|
6420
6769
|
} catch {
|
|
6421
6770
|
continue;
|
|
6422
6771
|
}
|
|
6423
|
-
for (const statement of
|
|
6772
|
+
for (const statement of text3.matchAll(STATEMENT)) {
|
|
6424
6773
|
const verb = statement[1].toUpperCase().replace(/\s+/g, " ");
|
|
6425
6774
|
const start = statement.index ?? 0;
|
|
6426
|
-
const rest =
|
|
6775
|
+
const rest = text3.slice(start + statement[0].length, start + STATEMENT_WINDOW);
|
|
6427
6776
|
if (verb === "SELECT") {
|
|
6428
6777
|
for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
|
|
6429
6778
|
continue;
|
|
@@ -6439,16 +6788,16 @@ async function extractData(builder, input) {
|
|
|
6439
6788
|
for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
|
|
6440
6789
|
}
|
|
6441
6790
|
}
|
|
6442
|
-
for (const match of
|
|
6443
|
-
touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(
|
|
6791
|
+
for (const match of text3.matchAll(NS_CONST)) {
|
|
6792
|
+
touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(text3, match.index ?? 0));
|
|
6444
6793
|
}
|
|
6445
|
-
for (const match of
|
|
6446
|
-
touch(path, match[1], NAMESPACE, accessFor(
|
|
6794
|
+
for (const match of text3.matchAll(NS_LITERAL)) {
|
|
6795
|
+
touch(path, match[1], NAMESPACE, accessFor(text3, match.index ?? 0));
|
|
6447
6796
|
}
|
|
6448
6797
|
}
|
|
6449
6798
|
}
|
|
6450
|
-
function accessFor(
|
|
6451
|
-
const window =
|
|
6799
|
+
function accessFor(text3, index) {
|
|
6800
|
+
const window = text3.slice(Math.max(0, index - 160), index + 40);
|
|
6452
6801
|
return /\b(?:transact|update|delete|create|insert|Ops)\b/.test(window) ? WRITES : READS;
|
|
6453
6802
|
}
|
|
6454
6803
|
async function buildCodeGraph(input) {
|
|
@@ -6460,7 +6809,7 @@ async function buildCodeGraph(input) {
|
|
|
6460
6809
|
return builder.build();
|
|
6461
6810
|
}
|
|
6462
6811
|
|
|
6463
|
-
// ../harness/dist/chunk-
|
|
6812
|
+
// ../harness/dist/chunk-ANNX7VGK.js
|
|
6464
6813
|
var import_crypto4 = require("crypto");
|
|
6465
6814
|
async function digestStagedWorkspace(root, limits) {
|
|
6466
6815
|
const files = [];
|
|
@@ -6579,13 +6928,13 @@ function createCodeRuntimeControlClient(options) {
|
|
|
6579
6928
|
if (!Number.isSafeInteger(modelRequestTimeoutMs) || modelRequestTimeoutMs < 3e4 || modelRequestTimeoutMs > 30 * 6e4) {
|
|
6580
6929
|
throw new TypeError("modelRequestTimeoutMs must be an integer from 30000 to 1800000");
|
|
6581
6930
|
}
|
|
6582
|
-
const
|
|
6931
|
+
const request3 = options.fetch ?? fetch;
|
|
6583
6932
|
const call2 = async (path, body, timeoutMs = requestTimeoutMs) => {
|
|
6584
6933
|
const timeout = AbortSignal.timeout(timeoutMs);
|
|
6585
6934
|
const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
|
|
6586
6935
|
let response2;
|
|
6587
6936
|
try {
|
|
6588
|
-
response2 = await
|
|
6937
|
+
response2 = await request3(`${endpoint}${path}`, {
|
|
6589
6938
|
method: "POST",
|
|
6590
6939
|
headers: { authorization: `Bearer ${options.token}`, "content-type": "application/json" },
|
|
6591
6940
|
body: JSON.stringify(body),
|
|
@@ -6598,7 +6947,7 @@ function createCodeRuntimeControlClient(options) {
|
|
|
6598
6947
|
}
|
|
6599
6948
|
const value2 = await response2.json().catch(() => null);
|
|
6600
6949
|
if (!response2.ok) {
|
|
6601
|
-
const problem =
|
|
6950
|
+
const problem = record5(record5(value2)?.error);
|
|
6602
6951
|
throw new CodeRuntimeControlError(
|
|
6603
6952
|
typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
|
|
6604
6953
|
response2.status,
|
|
@@ -6620,12 +6969,12 @@ function createCodeRuntimeControlClient(options) {
|
|
|
6620
6969
|
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
|
|
6621
6970
|
),
|
|
6622
6971
|
infer: async (sessionId, inference) => {
|
|
6623
|
-
const value2 =
|
|
6972
|
+
const value2 = record5(await call2(
|
|
6624
6973
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
|
|
6625
6974
|
inference,
|
|
6626
6975
|
modelRequestTimeoutMs
|
|
6627
6976
|
));
|
|
6628
|
-
if (!value2 || value2.requestId !== inference.requestId || !
|
|
6977
|
+
if (!value2 || value2.requestId !== inference.requestId || !record5(value2.response) || !record5(value2.receipt)) {
|
|
6629
6978
|
throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
|
|
6630
6979
|
}
|
|
6631
6980
|
return value2;
|
|
@@ -6647,6 +6996,16 @@ function createCodeRuntimeControlClient(options) {
|
|
|
6647
6996
|
}
|
|
6648
6997
|
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
|
|
6649
6998
|
},
|
|
6999
|
+
recallMemories: async (sessionId, subjects, limit) => {
|
|
7000
|
+
const response2 = await call2(
|
|
7001
|
+
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/recall`,
|
|
7002
|
+
{ subjects: [...subjects], limit }
|
|
7003
|
+
);
|
|
7004
|
+
return Array.isArray(response2.memories) ? response2.memories : [];
|
|
7005
|
+
},
|
|
7006
|
+
rememberMemory: async (sessionId, memory) => {
|
|
7007
|
+
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/remember`, memory);
|
|
7008
|
+
},
|
|
6650
7009
|
reportSessionFailure: async (sessionId, message2) => {
|
|
6651
7010
|
if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
|
|
6652
7011
|
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
|
|
@@ -6683,12 +7042,12 @@ function validateHeartbeat(version, capabilities) {
|
|
|
6683
7042
|
}
|
|
6684
7043
|
}
|
|
6685
7044
|
function parseSnapshot(value2) {
|
|
6686
|
-
const root =
|
|
6687
|
-
const host =
|
|
7045
|
+
const root = record5(value2);
|
|
7046
|
+
const host = record5(root?.host);
|
|
6688
7047
|
if (!host || typeof host.hostId !== "string" || typeof host.runtimeVersion !== "string" || !Number.isSafeInteger(host.lastSeenAt) || host.revokedAt !== null || !Array.isArray(root?.bindings) || root.bindings.length > 1024 || !Array.isArray(root?.commands) || root.commands.length > 64) throw invalid("heartbeat");
|
|
6689
7048
|
const bindingIds = /* @__PURE__ */ new Set();
|
|
6690
7049
|
const bindings = root.bindings.map((item) => {
|
|
6691
|
-
const binding =
|
|
7050
|
+
const binding = record5(item);
|
|
6692
7051
|
if (!binding || typeof binding.bindingId !== "string" || typeof binding.appId !== "string" || binding.env !== "dev" && binding.env !== "prod" || typeof binding.offerId !== "string" || binding.hostId !== host.hostId || !Number.isSafeInteger(binding.generation) || Number(binding.generation) < 1 || binding.revokedAt !== null || bindingIds.has(binding.bindingId)) {
|
|
6693
7052
|
throw invalid("binding");
|
|
6694
7053
|
}
|
|
@@ -6698,10 +7057,10 @@ function parseSnapshot(value2) {
|
|
|
6698
7057
|
const commandIds = /* @__PURE__ */ new Set();
|
|
6699
7058
|
const commandSequences = /* @__PURE__ */ new Set();
|
|
6700
7059
|
const commands = root.commands.map((item) => {
|
|
6701
|
-
const command =
|
|
7060
|
+
const command = record5(item);
|
|
6702
7061
|
const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
|
|
6703
7062
|
const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
|
|
6704
|
-
if (!command || typeof command.commandId !== "string" || !/^ccmd_[0-9a-f]{32}$/.test(command.commandId) || typeof command.instanceId !== "string" || typeof command.sessionId !== "string" || !/^csess_[0-9a-f]{32}$/.test(command.sessionId) || typeof command.appId !== "string" || command.env !== "dev" && command.env !== "prod" || command.hostId !== host.hostId || !binding || binding.appId !== command.appId || binding.generation !== command.bindingGeneration || !Number.isSafeInteger(command.sequence) || Number(command.sequence) < 1 || commandIds.has(command.commandId) || commandSequences.has(sequenceKey) || !["start", "prompt", "checkpoint_stop", "resume"].includes(String(command.kind)) || !
|
|
7063
|
+
if (!command || typeof command.commandId !== "string" || !/^ccmd_[0-9a-f]{32}$/.test(command.commandId) || typeof command.instanceId !== "string" || typeof command.sessionId !== "string" || !/^csess_[0-9a-f]{32}$/.test(command.sessionId) || typeof command.appId !== "string" || command.env !== "dev" && command.env !== "prod" || command.hostId !== host.hostId || !binding || binding.appId !== command.appId || binding.generation !== command.bindingGeneration || !Number.isSafeInteger(command.sequence) || Number(command.sequence) < 1 || commandIds.has(command.commandId) || commandSequences.has(sequenceKey) || !["start", "prompt", "checkpoint_stop", "resume"].includes(String(command.kind)) || !record5(command.payload) || !Number.isSafeInteger(command.createdAt)) throw invalid("command");
|
|
6705
7064
|
commandIds.add(command.commandId);
|
|
6706
7065
|
commandSequences.add(sequenceKey);
|
|
6707
7066
|
return command;
|
|
@@ -6709,10 +7068,10 @@ function parseSnapshot(value2) {
|
|
|
6709
7068
|
return { host, bindings, commands };
|
|
6710
7069
|
}
|
|
6711
7070
|
async function parseSource(value2) {
|
|
6712
|
-
const snapshot =
|
|
7071
|
+
const snapshot = record5(record5(value2)?.snapshot);
|
|
6713
7072
|
if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
|
|
6714
7073
|
const files = snapshot.files.map((value22) => {
|
|
6715
|
-
const file =
|
|
7074
|
+
const file = record5(value22);
|
|
6716
7075
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
|
|
6717
7076
|
return { path: file.path, content: file.content };
|
|
6718
7077
|
});
|
|
@@ -6721,11 +7080,11 @@ async function parseSource(value2) {
|
|
|
6721
7080
|
const aliases = /* @__PURE__ */ new Set();
|
|
6722
7081
|
const references = [];
|
|
6723
7082
|
for (const item of referencesValue) {
|
|
6724
|
-
const reference =
|
|
7083
|
+
const reference = record5(item);
|
|
6725
7084
|
if (!reference || typeof reference.alias !== "string" || !/^[a-z][a-z0-9-]{0,39}$/.test(reference.alias) || aliases.has(reference.alias) || reference.alias === "primary" || typeof reference.repository !== "string" || typeof reference.commitSha !== "string" || typeof reference.treeDigest !== "string" || !Array.isArray(reference.files)) throw invalid("reference source");
|
|
6726
7085
|
aliases.add(reference.alias);
|
|
6727
7086
|
const referenceFiles = reference.files.map((entry) => {
|
|
6728
|
-
const file =
|
|
7087
|
+
const file = record5(entry);
|
|
6729
7088
|
if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
|
|
6730
7089
|
return { path: file.path, content: file.content };
|
|
6731
7090
|
});
|
|
@@ -6740,18 +7099,18 @@ async function parseSource(value2) {
|
|
|
6740
7099
|
return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
|
|
6741
7100
|
}
|
|
6742
7101
|
function parseReview(value2) {
|
|
6743
|
-
const review =
|
|
7102
|
+
const review = record5(record5(value2)?.review);
|
|
6744
7103
|
if (!review || !["approved", "rejected"].includes(String(review.verdict)) || typeof review.reviewDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(review.reviewDigest) || typeof review.provider !== "string" || !review.provider || typeof review.model !== "string" || !review.model || !Number.isSafeInteger(review.policyVersion) || Number(review.policyVersion) < 1) throw invalid("review");
|
|
6745
7104
|
return review;
|
|
6746
7105
|
}
|
|
6747
7106
|
function parseCandidate(value2) {
|
|
6748
|
-
const candidate =
|
|
7107
|
+
const candidate = record5(record5(value2)?.candidate);
|
|
6749
7108
|
if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
|
|
6750
7109
|
throw invalid("candidate");
|
|
6751
7110
|
}
|
|
6752
7111
|
return { candidateId: candidate.candidateId, status: candidate.status };
|
|
6753
7112
|
}
|
|
6754
|
-
var
|
|
7113
|
+
var record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
6755
7114
|
var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
|
|
6756
7115
|
var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
|
|
6757
7116
|
var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
|
|
@@ -6847,8 +7206,8 @@ function gitApply(cwd, patch2, check) {
|
|
|
6847
7206
|
});
|
|
6848
7207
|
let stderr = "";
|
|
6849
7208
|
child.stderr.setEncoding("utf8");
|
|
6850
|
-
child.stderr.on("data", (
|
|
6851
|
-
if (stderr.length < 4e3) stderr +=
|
|
7209
|
+
child.stderr.on("data", (text3) => {
|
|
7210
|
+
if (stderr.length < 4e3) stderr += text3.slice(0, 4e3);
|
|
6852
7211
|
});
|
|
6853
7212
|
child.once("error", reject);
|
|
6854
7213
|
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr.trim().slice(0, 500)))));
|
|
@@ -7716,7 +8075,7 @@ async function runCodeAgentAttempt(options) {
|
|
|
7716
8075
|
}
|
|
7717
8076
|
}
|
|
7718
8077
|
async function handleCodeRuntimeInference(input) {
|
|
7719
|
-
const { command, metadata: metadata2, request:
|
|
8078
|
+
const { command, metadata: metadata2, request: request3, state: state2 } = input;
|
|
7720
8079
|
if (state2.tokens >= metadata2.maxTokensPerInteraction) {
|
|
7721
8080
|
if (!state2.noticeEmitted) {
|
|
7722
8081
|
state2.noticeEmitted = true;
|
|
@@ -7729,7 +8088,7 @@ async function handleCodeRuntimeInference(input) {
|
|
|
7729
8088
|
return {
|
|
7730
8089
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
7731
8090
|
type: "inference.response",
|
|
7732
|
-
requestId:
|
|
8091
|
+
requestId: request3.requestId,
|
|
7733
8092
|
response: {
|
|
7734
8093
|
id: `budget:${command.commandId}`,
|
|
7735
8094
|
provider: "openai",
|
|
@@ -7743,9 +8102,9 @@ async function handleCodeRuntimeInference(input) {
|
|
|
7743
8102
|
}
|
|
7744
8103
|
const startedAt = Date.now();
|
|
7745
8104
|
const response2 = await input.control.infer(command.sessionId, {
|
|
7746
|
-
requestId:
|
|
8105
|
+
requestId: request3.requestId,
|
|
7747
8106
|
interactionId: command.commandId,
|
|
7748
|
-
call:
|
|
8107
|
+
call: request3.call
|
|
7749
8108
|
});
|
|
7750
8109
|
state2.tokens += response2.receipt.inputTokens + response2.receipt.outputTokens;
|
|
7751
8110
|
await input.event({
|
|
@@ -7762,14 +8121,14 @@ async function handleCodeRuntimeInference(input) {
|
|
|
7762
8121
|
return {
|
|
7763
8122
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
7764
8123
|
type: "inference.response",
|
|
7765
|
-
requestId:
|
|
8124
|
+
requestId: request3.requestId,
|
|
7766
8125
|
response: response2.response
|
|
7767
8126
|
};
|
|
7768
8127
|
}
|
|
7769
8128
|
function createCodeRuntimeInference(options) {
|
|
7770
8129
|
let seq = 0;
|
|
7771
8130
|
return {
|
|
7772
|
-
chat: async (
|
|
8131
|
+
chat: async (request3) => {
|
|
7773
8132
|
const requestId = `${options.command.commandId}:${++seq}`;
|
|
7774
8133
|
const answer = await handleCodeRuntimeInference({
|
|
7775
8134
|
command: options.command,
|
|
@@ -7781,7 +8140,7 @@ function createCodeRuntimeInference(options) {
|
|
|
7781
8140
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
7782
8141
|
type: "inference.request",
|
|
7783
8142
|
requestId,
|
|
7784
|
-
call:
|
|
8143
|
+
call: request3
|
|
7785
8144
|
}
|
|
7786
8145
|
});
|
|
7787
8146
|
if (answer.type !== "inference.response") throw new TypeError("brokered inference returned the wrong frame");
|
|
@@ -7987,9 +8346,9 @@ async function safePrefix(base, paths, prefix) {
|
|
|
7987
8346
|
function descriptor(name, effect, argumentRoles) {
|
|
7988
8347
|
return { name, version: 1, effect, inputSchema: { type: "object" }, argumentRoles, policyId: `odla.code.${name}.v1` };
|
|
7989
8348
|
}
|
|
7990
|
-
async function conversionPolicy(
|
|
8349
|
+
async function conversionPolicy(id2, output) {
|
|
7991
8350
|
const definition = {
|
|
7992
|
-
conversionId:
|
|
8351
|
+
conversionId: id2,
|
|
7993
8352
|
version: 1,
|
|
7994
8353
|
output,
|
|
7995
8354
|
maximumSourceBytes: 1e6,
|
|
@@ -7998,18 +8357,18 @@ async function conversionPolicy(id, output) {
|
|
|
7998
8357
|
};
|
|
7999
8358
|
return { ...definition, digest: await conversionPolicyDigest(definition) };
|
|
8000
8359
|
}
|
|
8001
|
-
async function registeredPolicy(
|
|
8360
|
+
async function registeredPolicy(id2, registryId, values) {
|
|
8002
8361
|
const mapping = Object.fromEntries(values.map((value2) => [value2, value2]));
|
|
8003
|
-
return conversionPolicy(
|
|
8362
|
+
return conversionPolicy(id2, {
|
|
8004
8363
|
kind: "registered_id",
|
|
8005
8364
|
registryId,
|
|
8006
8365
|
registryDigest: await registeredIdRegistryDigest(mapping)
|
|
8007
8366
|
});
|
|
8008
8367
|
}
|
|
8009
8368
|
async function conversionRegistry(policies, values) {
|
|
8010
|
-
const registeredIds = Object.fromEntries(await Promise.all(Object.entries(values).map(async ([
|
|
8369
|
+
const registeredIds = Object.fromEntries(await Promise.all(Object.entries(values).map(async ([id2, entries]) => {
|
|
8011
8370
|
const mapping = Object.fromEntries(entries.map((value2) => [value2, value2]));
|
|
8012
|
-
return [
|
|
8371
|
+
return [id2, { values: mapping, digest: await registeredIdRegistryDigest(mapping) }];
|
|
8013
8372
|
})));
|
|
8014
8373
|
return createConversionRegistry({ policies, registeredIds });
|
|
8015
8374
|
}
|
|
@@ -8063,10 +8422,10 @@ function decision(input, policy, approvalConsumed, tool, actionDigest) {
|
|
|
8063
8422
|
actionDigest: actionDigest ?? (policy.outcome === "require_approval" ? policy.actionDigest : "")
|
|
8064
8423
|
};
|
|
8065
8424
|
}
|
|
8066
|
-
function policyContext(context,
|
|
8425
|
+
function policyContext(context, request3, options, extra) {
|
|
8067
8426
|
return {
|
|
8068
8427
|
lease: context.lease,
|
|
8069
|
-
request:
|
|
8428
|
+
request: request3,
|
|
8070
8429
|
workspaceId: `workspace:${context.lease.task.attemptId}`,
|
|
8071
8430
|
readers: { kind: "principals", principalIds: [options.readerId] },
|
|
8072
8431
|
...extra
|
|
@@ -8085,8 +8444,8 @@ function optionalInteger(value2) {
|
|
|
8085
8444
|
if (!Number.isSafeInteger(value2) || value2 < 1) throw new TypeError("line bounds must be positive integers");
|
|
8086
8445
|
return value2;
|
|
8087
8446
|
}
|
|
8088
|
-
function response(
|
|
8089
|
-
return { requestId:
|
|
8447
|
+
function response(request3, ok, content2, details) {
|
|
8448
|
+
return { requestId: request3.requestId, ok, content: content2, ...details ? { details } : {} };
|
|
8090
8449
|
}
|
|
8091
8450
|
var cache = /* @__PURE__ */ new Map();
|
|
8092
8451
|
function workspaceGraphs(workspaceDir, paths) {
|
|
@@ -8102,7 +8461,7 @@ function workspaceGraphs(workspaceDir, paths) {
|
|
|
8102
8461
|
cache.set(workspaceDir, built);
|
|
8103
8462
|
return built;
|
|
8104
8463
|
}
|
|
8105
|
-
var shortId = (
|
|
8464
|
+
var shortId = (id2) => id2.slice(id2.indexOf(":") + 1);
|
|
8106
8465
|
function renderOverview(graphs, prefix) {
|
|
8107
8466
|
const rows = rollup(graphs.graph, FILE, prefix === void 0 ? {} : { prefix });
|
|
8108
8467
|
if (rows.length === 0) return prefix ? `No source under "${prefix}".` : "No source files.";
|
|
@@ -8111,19 +8470,19 @@ function renderOverview(graphs, prefix) {
|
|
|
8111
8470
|
return [`${total} source files. Directories, largest first \u2014 read one with sandbox.list --prefix.`, ...lines].join("\n");
|
|
8112
8471
|
}
|
|
8113
8472
|
function renderWhereIs(graphs, symbol) {
|
|
8114
|
-
const sites = neighbors(graphs.graph, nodeId(SYMBOL, symbol), { direction: "in", kinds: ["exports"] }).map((
|
|
8115
|
-
path: shortId(
|
|
8116
|
-
pkg: neighbors(graphs.graph,
|
|
8117
|
-
dependents: incident(graphs.graph,
|
|
8473
|
+
const sites = neighbors(graphs.graph, nodeId(SYMBOL, symbol), { direction: "in", kinds: ["exports"] }).map((id2) => ({
|
|
8474
|
+
path: shortId(id2),
|
|
8475
|
+
pkg: neighbors(graphs.graph, id2, { direction: "in", kinds: ["contains"] })[0],
|
|
8476
|
+
dependents: incident(graphs.graph, id2, { direction: "in", kinds: [IMPORTS] }).length
|
|
8118
8477
|
})).sort((left, right) => right.dependents - left.dependents || left.path.localeCompare(right.path));
|
|
8119
8478
|
if (sites.length === 0) return `No exported symbol named "${symbol}". Try sandbox.search for a textual match.`;
|
|
8120
8479
|
return sites.slice(0, 20).map((site) => `${site.path}${site.pkg ? ` [${shortId(site.pkg)}]` : ""} ${site.dependents} dependents`).join("\n");
|
|
8121
8480
|
}
|
|
8122
8481
|
function renderWhoImports(graphs, path) {
|
|
8123
|
-
const
|
|
8124
|
-
const importers = neighbors(graphs.graph,
|
|
8482
|
+
const id2 = nodeId(FILE, path);
|
|
8483
|
+
const importers = neighbors(graphs.graph, id2, { direction: "in", kinds: [IMPORTS] });
|
|
8125
8484
|
if (importers.length === 0) {
|
|
8126
|
-
return graphs.graph.nodes.has(
|
|
8485
|
+
return graphs.graph.nodes.has(id2) ? `Nothing imports ${path}. It is a leaf.` : `${path} is not a source file in this workspace.`;
|
|
8127
8486
|
}
|
|
8128
8487
|
return importers.slice(0, 40).map(shortId).sort().join("\n");
|
|
8129
8488
|
}
|
|
@@ -8146,11 +8505,11 @@ var GRAPH_TOOLS = /* @__PURE__ */ new Set([
|
|
|
8146
8505
|
"sandbox.who_imports",
|
|
8147
8506
|
"sandbox.who_touches"
|
|
8148
8507
|
]);
|
|
8149
|
-
async function read(context,
|
|
8150
|
-
exactKeys(
|
|
8151
|
-
const path = stringField(
|
|
8152
|
-
const startLine = optionalInteger(
|
|
8153
|
-
const endLine = optionalInteger(
|
|
8508
|
+
async function read(context, request3, options, policy) {
|
|
8509
|
+
exactKeys(request3.input, ["path", "startLine", "endLine"]);
|
|
8510
|
+
const path = stringField(request3.input, "path");
|
|
8511
|
+
const startLine = optionalInteger(request3.input.startLine) ?? 1;
|
|
8512
|
+
const endLine = optionalInteger(request3.input.endLine) ?? startLine + (options.maxReadLines ?? 2e3) - 1;
|
|
8154
8513
|
if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
|
|
8155
8514
|
throw new TypeError("requested line range exceeds its bound");
|
|
8156
8515
|
}
|
|
@@ -8158,8 +8517,8 @@ async function read(context, request2, options, policy) {
|
|
|
8158
8517
|
if (!paths.includes(path)) {
|
|
8159
8518
|
throw new TypeError(`no such file in the staged workspace: "${path}". Use sandbox.overview, sandbox.where_is or sandbox.search to find the correct path.`);
|
|
8160
8519
|
}
|
|
8161
|
-
const allowed = await policy.read(policyContext(context,
|
|
8162
|
-
if (!allowed) return response(
|
|
8520
|
+
const allowed = await policy.read(policyContext(context, request3, options, { paths, path, startLine, endLine }));
|
|
8521
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8163
8522
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
8164
8523
|
const info = await (0, import_promises10.stat)(target);
|
|
8165
8524
|
if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
|
|
@@ -8172,74 +8531,74 @@ async function read(context, request2, options, policy) {
|
|
|
8172
8531
|
if (Buffer.byteLength(content2) > (options.maxReadBytes ?? 128 * 1024)) {
|
|
8173
8532
|
throw new TypeError("read result exceeds its byte bound");
|
|
8174
8533
|
}
|
|
8175
|
-
return response(
|
|
8534
|
+
return response(request3, true, content2, { path, startLine, endLine: Math.min(endLine, lines.length) });
|
|
8176
8535
|
}
|
|
8177
|
-
async function list(context,
|
|
8178
|
-
exactKeys(
|
|
8179
|
-
const raw =
|
|
8536
|
+
async function list(context, request3, options, policy) {
|
|
8537
|
+
exactKeys(request3.input, ["prefix", "maxEntries"]);
|
|
8538
|
+
const raw = request3.input.prefix;
|
|
8180
8539
|
const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
8181
|
-
const maxEntries = optionalInteger(
|
|
8540
|
+
const maxEntries = optionalInteger(request3.input.maxEntries) ?? 1e3;
|
|
8182
8541
|
if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
|
|
8183
8542
|
const paths = await registeredFiles(context.workspaceDir, 2e4);
|
|
8184
|
-
const allowed = await policy.list(policyContext(context,
|
|
8185
|
-
if (!allowed) return response(
|
|
8543
|
+
const allowed = await policy.list(policyContext(context, request3, options, { paths, ...prefix ? { prefix } : {} }));
|
|
8544
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8186
8545
|
const entries = listWorkspace(paths, { ...prefix ? { prefix } : {}, maxEntries });
|
|
8187
8546
|
if (!entries.length) {
|
|
8188
|
-
return response(
|
|
8547
|
+
return response(request3, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
|
|
8189
8548
|
}
|
|
8190
8549
|
const truncated = entries.length < paths.length && entries.length === maxEntries;
|
|
8191
8550
|
const hint = !prefix && paths.length > 500 ? `
|
|
8192
8551
|
\u2026 ${paths.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
|
|
8193
8552
|
return response(
|
|
8194
|
-
|
|
8553
|
+
request3,
|
|
8195
8554
|
true,
|
|
8196
8555
|
`${entries.join("\n")}${truncated ? `
|
|
8197
8556
|
\u2026 truncated at ${maxEntries} entries` : ""}${hint}`,
|
|
8198
8557
|
{ count: entries.length, truncated }
|
|
8199
8558
|
);
|
|
8200
8559
|
}
|
|
8201
|
-
async function search(context,
|
|
8202
|
-
exactKeys(
|
|
8203
|
-
const query = stringField(
|
|
8560
|
+
async function search(context, request3, options, policy) {
|
|
8561
|
+
exactKeys(request3.input, ["query", "prefix", "maxResults", "caseSensitive"]);
|
|
8562
|
+
const query = stringField(request3.input, "query");
|
|
8204
8563
|
if (query.length > 512) throw new TypeError("search query exceeds its bound");
|
|
8205
|
-
const raw =
|
|
8564
|
+
const raw = request3.input.prefix;
|
|
8206
8565
|
const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
8207
|
-
const maxResults = optionalInteger(
|
|
8566
|
+
const maxResults = optionalInteger(request3.input.maxResults) ?? 100;
|
|
8208
8567
|
if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
|
|
8209
|
-
const caseSensitive =
|
|
8568
|
+
const caseSensitive = request3.input.caseSensitive === void 0 ? true : request3.input.caseSensitive === true;
|
|
8210
8569
|
const paths = await registeredFiles(context.workspaceDir, 2e4);
|
|
8211
|
-
const allowed = await policy.search(policyContext(context,
|
|
8212
|
-
if (!allowed) return response(
|
|
8570
|
+
const allowed = await policy.search(policyContext(context, request3, options, { paths, query, ...prefix ? { prefix } : {} }));
|
|
8571
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8213
8572
|
const matches = await searchWorkspace(context.workspaceDir, paths, {
|
|
8214
8573
|
query,
|
|
8215
8574
|
maxResults,
|
|
8216
8575
|
caseSensitive,
|
|
8217
8576
|
...prefix ? { prefix } : {}
|
|
8218
8577
|
});
|
|
8219
|
-
if (!matches.length) return response(
|
|
8220
|
-
return response(
|
|
8578
|
+
if (!matches.length) return response(request3, true, `No match for "${query}".`, { count: 0 });
|
|
8579
|
+
return response(request3, true, matches.map((match) => `${match.path}:${match.line}: ${match.text}`).join("\n"), {
|
|
8221
8580
|
count: matches.length
|
|
8222
8581
|
});
|
|
8223
8582
|
}
|
|
8224
|
-
async function graphQuery(context,
|
|
8225
|
-
exactKeys(
|
|
8226
|
-
const raw =
|
|
8583
|
+
async function graphQuery(context, request3, options, policy) {
|
|
8584
|
+
exactKeys(request3.input, ["query"]);
|
|
8585
|
+
const raw = request3.input.query;
|
|
8227
8586
|
const query = typeof raw === "string" ? raw : "";
|
|
8228
8587
|
if (query.length > 512) throw new TypeError("query exceeds its bound");
|
|
8229
|
-
const allowed = await policy.graph(policyContext(context,
|
|
8230
|
-
tool:
|
|
8588
|
+
const allowed = await policy.graph(policyContext(context, request3, options, {
|
|
8589
|
+
tool: request3.tool,
|
|
8231
8590
|
selector: query
|
|
8232
8591
|
}));
|
|
8233
|
-
if (!allowed) return response(
|
|
8592
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8234
8593
|
const paths = await registeredFiles(context.workspaceDir, 2e4);
|
|
8235
8594
|
const graphs = await workspaceGraphs(context.workspaceDir, paths);
|
|
8236
|
-
if (
|
|
8237
|
-
return response(
|
|
8595
|
+
if (request3.tool === "sandbox.overview") {
|
|
8596
|
+
return response(request3, true, renderOverview(graphs, query || void 0));
|
|
8238
8597
|
}
|
|
8239
|
-
if (!query) throw new TypeError(`${
|
|
8240
|
-
if (
|
|
8241
|
-
if (
|
|
8242
|
-
return response(
|
|
8598
|
+
if (!query) throw new TypeError(`${request3.tool} requires a query`);
|
|
8599
|
+
if (request3.tool === "sandbox.where_is") return response(request3, true, renderWhereIs(graphs, query));
|
|
8600
|
+
if (request3.tool === "sandbox.who_imports") return response(request3, true, renderWhoImports(graphs, query));
|
|
8601
|
+
return response(request3, true, renderWhoTouches(graphs, query));
|
|
8243
8602
|
}
|
|
8244
8603
|
function createCodeToolBroker(options) {
|
|
8245
8604
|
validateOptions(options);
|
|
@@ -8247,24 +8606,24 @@ function createCodeToolBroker(options) {
|
|
|
8247
8606
|
const policy = createCodePolicyGate(options);
|
|
8248
8607
|
let tail = Promise.resolve();
|
|
8249
8608
|
return {
|
|
8250
|
-
execute(context,
|
|
8251
|
-
const result = tail.then(() =>
|
|
8609
|
+
execute(context, request3) {
|
|
8610
|
+
const result = tail.then(() => route2(context, request3, options, recipes, policy));
|
|
8252
8611
|
tail = result.then(() => void 0, () => void 0);
|
|
8253
8612
|
return result;
|
|
8254
8613
|
}
|
|
8255
8614
|
};
|
|
8256
8615
|
}
|
|
8257
|
-
async function
|
|
8616
|
+
async function route2(context, request3, options, recipes, policy) {
|
|
8258
8617
|
try {
|
|
8259
8618
|
if (context.signal?.aborted) throw new TypeError("tool request was cancelled");
|
|
8260
|
-
if (
|
|
8261
|
-
if (
|
|
8262
|
-
if (
|
|
8263
|
-
if (GRAPH_TOOLS.has(
|
|
8264
|
-
if (
|
|
8265
|
-
return await recipe(context,
|
|
8619
|
+
if (request3.tool === "sandbox.read") return await read(context, request3, options, policy);
|
|
8620
|
+
if (request3.tool === "sandbox.list") return await list(context, request3, options, policy);
|
|
8621
|
+
if (request3.tool === "sandbox.search") return await search(context, request3, options, policy);
|
|
8622
|
+
if (GRAPH_TOOLS.has(request3.tool)) return await graphQuery(context, request3, options, policy);
|
|
8623
|
+
if (request3.tool === "sandbox.apply_patch") return await patch(context, request3, options, policy);
|
|
8624
|
+
return await recipe(context, request3, options, recipes, policy);
|
|
8266
8625
|
} catch (reason) {
|
|
8267
|
-
return response(
|
|
8626
|
+
return response(request3, false, toolFailureMessage(reason));
|
|
8268
8627
|
}
|
|
8269
8628
|
}
|
|
8270
8629
|
function toolFailureMessage(reason) {
|
|
@@ -8276,34 +8635,34 @@ function toolFailureMessage(reason) {
|
|
|
8276
8635
|
if (code === "EACCES" || code === "EPERM") return "that path is not readable through this tool";
|
|
8277
8636
|
return "tool failed closed";
|
|
8278
8637
|
}
|
|
8279
|
-
async function patch(context,
|
|
8280
|
-
exactKeys(
|
|
8281
|
-
const value2 = stringField(
|
|
8638
|
+
async function patch(context, request3, options, policy) {
|
|
8639
|
+
exactKeys(request3.input, ["patch"]);
|
|
8640
|
+
const value2 = stringField(request3.input, "patch");
|
|
8282
8641
|
const paths = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
|
|
8283
8642
|
if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
|
|
8284
8643
|
throw new TypeError("patch targets a read-only reference source");
|
|
8285
8644
|
}
|
|
8286
|
-
const allowed = await policy.patch(policyContext(context,
|
|
8287
|
-
if (!allowed) return response(
|
|
8645
|
+
const allowed = await policy.patch(policyContext(context, request3, options, { patch: value2 }));
|
|
8646
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8288
8647
|
await applyCodePatch(context.workspaceDir, value2, paths);
|
|
8289
|
-
return response(
|
|
8648
|
+
return response(request3, true, `Applied patch to ${paths.length} file(s).`, { paths });
|
|
8290
8649
|
}
|
|
8291
|
-
async function recipe(context,
|
|
8292
|
-
exactKeys(
|
|
8293
|
-
const recipeId = stringField(
|
|
8650
|
+
async function recipe(context, request3, options, recipes, policy) {
|
|
8651
|
+
exactKeys(request3.input, ["recipeId"]);
|
|
8652
|
+
const recipeId = stringField(request3.input, "recipeId");
|
|
8294
8653
|
const digestLimits = {
|
|
8295
8654
|
maxFiles: options.maxRecipeWorkspaceFiles ?? 2e4,
|
|
8296
8655
|
maxBytes: options.maxRecipeWorkspaceBytes ?? 512 * 1024 * 1024
|
|
8297
8656
|
};
|
|
8298
8657
|
const sourceDigest = await digestStagedWorkspace(context.workspaceDir, digestLimits);
|
|
8299
|
-
const allowed = await policy.recipe(policyContext(context,
|
|
8658
|
+
const allowed = await policy.recipe(policyContext(context, request3, options, {
|
|
8300
8659
|
recipeIds: [...recipes.keys()].sort(),
|
|
8301
8660
|
recipeId,
|
|
8302
8661
|
sourceDigest
|
|
8303
8662
|
}));
|
|
8304
|
-
if (!allowed) return response(
|
|
8663
|
+
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8305
8664
|
const selected = recipes.get(recipeId);
|
|
8306
|
-
if (!selected) return response(
|
|
8665
|
+
if (!selected) return response(request3, false, "build recipe is not registered");
|
|
8307
8666
|
const staged = await stageWorkspace(context.workspaceDir, {
|
|
8308
8667
|
maxFiles: digestLimits.maxFiles,
|
|
8309
8668
|
maxBytes: digestLimits.maxBytes
|
|
@@ -8320,7 +8679,7 @@ async function recipe(context, request2, options, recipes, policy) {
|
|
|
8320
8679
|
const output = [result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
8321
8680
|
const ok = result.exitCode === 0 && !result.outputLimitExceeded && !result.timedOut;
|
|
8322
8681
|
const status = result.timedOut ? "timed out" : result.outputLimitExceeded ? "exceeded output limit" : ok ? "passed" : `failed with exit ${result.exitCode}`;
|
|
8323
|
-
return response(
|
|
8682
|
+
return response(request3, ok, `Recipe ${recipeId} ${status}.${output ? `
|
|
8324
8683
|
${output}` : ""}`, {
|
|
8325
8684
|
recipeId,
|
|
8326
8685
|
exitCode: result.exitCode,
|
|
@@ -8341,13 +8700,36 @@ function validateOptions(options) {
|
|
|
8341
8700
|
throw new TypeError("Code tool broker read-only prefix is invalid");
|
|
8342
8701
|
}
|
|
8343
8702
|
}
|
|
8703
|
+
var MAX_MEMORY_BODY = 4e3;
|
|
8704
|
+
function validateMemory(memory) {
|
|
8705
|
+
if (!memory.subject.includes(":")) {
|
|
8706
|
+
throw new TypeError(`memory subject must be a graph node id, got "${memory.subject}"`);
|
|
8707
|
+
}
|
|
8708
|
+
const body = memory.body.trim();
|
|
8709
|
+
if (!body) throw new TypeError("a memory needs a body");
|
|
8710
|
+
if (body.length > MAX_MEMORY_BODY) throw new TypeError("memory body exceeds its bound");
|
|
8711
|
+
if (!memory.authorId.trim()) throw new TypeError("a memory needs an author");
|
|
8712
|
+
}
|
|
8713
|
+
function hazardFromAttempt(input) {
|
|
8714
|
+
const body = [
|
|
8715
|
+
`Attempt ${input.attempt} at "${input.goal.slice(0, 200)}" failed its proof.`,
|
|
8716
|
+
input.feedback.replace(/\s+/g, " ").slice(0, MAX_MEMORY_BODY - 300)
|
|
8717
|
+
].join(" ");
|
|
8718
|
+
return input.touched.slice(0, 10).map((path) => ({
|
|
8719
|
+
subject: path.includes(":") ? path : `file:${path}`,
|
|
8720
|
+
kind: "hazard",
|
|
8721
|
+
body,
|
|
8722
|
+
evidence: { kind: "gate", ref: input.verificationId },
|
|
8723
|
+
authorId: input.authorId
|
|
8724
|
+
}));
|
|
8725
|
+
}
|
|
8344
8726
|
async function runGoal(spec, attempt) {
|
|
8345
8727
|
assertBudget(spec.budget);
|
|
8346
8728
|
const now = spec.now ?? Date.now;
|
|
8347
8729
|
const startedAt = now();
|
|
8348
8730
|
const attempts = [];
|
|
8349
8731
|
const boardErrors = [];
|
|
8350
|
-
const
|
|
8732
|
+
const emit4 = async (event) => {
|
|
8351
8733
|
if (!spec.onEvent) return;
|
|
8352
8734
|
try {
|
|
8353
8735
|
await spec.onEvent(event);
|
|
@@ -8360,7 +8742,7 @@ async function runGoal(spec, attempt) {
|
|
|
8360
8742
|
let costKnown = false;
|
|
8361
8743
|
const finish2 = async (stoppedReason) => {
|
|
8362
8744
|
const met = stoppedReason === "proof_passed";
|
|
8363
|
-
await
|
|
8745
|
+
await emit4(met ? { type: "goal_met", attempts: attempts.length, tokens, ...costKnown ? { costUsd } : {} } : {
|
|
8364
8746
|
type: "goal_abandoned",
|
|
8365
8747
|
reason: stoppedReason,
|
|
8366
8748
|
attempts: attempts.length,
|
|
@@ -8381,7 +8763,7 @@ async function runGoal(spec, attempt) {
|
|
|
8381
8763
|
if (spec.signal?.aborted) return finish2("cancelled");
|
|
8382
8764
|
if (spec.budget.deadline !== void 0 && now() >= spec.budget.deadline) return finish2("deadline");
|
|
8383
8765
|
const prompt = index === 1 ? openingPrompt(spec) : retryPrompt(spec, attempts.at(-1));
|
|
8384
|
-
await
|
|
8766
|
+
await emit4({ type: "attempt_started", attempt: index, prompt });
|
|
8385
8767
|
const outcome = await attempt({
|
|
8386
8768
|
attempt: index,
|
|
8387
8769
|
prompt,
|
|
@@ -8401,7 +8783,7 @@ async function runGoal(spec, attempt) {
|
|
|
8401
8783
|
...outcome.error === void 0 ? {} : { error: outcome.error }
|
|
8402
8784
|
});
|
|
8403
8785
|
if (outcome.gatePassed) return finish2("proof_passed");
|
|
8404
|
-
await
|
|
8786
|
+
await emit4({
|
|
8405
8787
|
type: "attempt_failed",
|
|
8406
8788
|
attempt: index,
|
|
8407
8789
|
feedback: outcome.feedback,
|
|
@@ -8450,7 +8832,7 @@ function createCodeRuntimeToolBroker(input, lease, role) {
|
|
|
8450
8832
|
readerId: `code-session:${lease.task.taskId}`,
|
|
8451
8833
|
readOnlyPrefixes: [".odla-references"]
|
|
8452
8834
|
});
|
|
8453
|
-
return role === "coding" ? broker : { execute: (context,
|
|
8835
|
+
return role === "coding" ? broker : { execute: (context, request3) => request3.tool === "sandbox.read" ? broker.execute(context, request3) : Promise.resolve({ requestId: request3.requestId, ok: false, content: "review sessions are read-only" }) };
|
|
8454
8836
|
}
|
|
8455
8837
|
var POSITIVE = (value2) => Number.isFinite(value2) && Number(value2) > 0 ? Number(value2) : void 0;
|
|
8456
8838
|
function codeGoalSpec(payload) {
|
|
@@ -8532,6 +8914,9 @@ function pursueRuntimeGoal(input) {
|
|
|
8532
8914
|
return { gatePassed: false, feedback: "", tokens: outcome.tokens, error: outcome.error };
|
|
8533
8915
|
}
|
|
8534
8916
|
const verdict = await input.gate(attempt);
|
|
8917
|
+
if (!verdict.passed && input.memory) {
|
|
8918
|
+
await rememberFailure(input, attempt, verdict.feedback);
|
|
8919
|
+
}
|
|
8535
8920
|
return {
|
|
8536
8921
|
gatePassed: verdict.passed,
|
|
8537
8922
|
feedback: verdict.feedback,
|
|
@@ -8542,6 +8927,25 @@ function pursueRuntimeGoal(input) {
|
|
|
8542
8927
|
}
|
|
8543
8928
|
);
|
|
8544
8929
|
}
|
|
8930
|
+
async function rememberFailure(input, attempt, feedback) {
|
|
8931
|
+
if (!input.memory || !feedback.trim()) return;
|
|
8932
|
+
try {
|
|
8933
|
+
const touched = await input.touched?.(attempt) ?? [];
|
|
8934
|
+
if (touched.length === 0) return;
|
|
8935
|
+
for (const memory of hazardFromAttempt({
|
|
8936
|
+
goal: input.spec.goal,
|
|
8937
|
+
attempt,
|
|
8938
|
+
feedback,
|
|
8939
|
+
touched,
|
|
8940
|
+
verificationId: `goal-${attempt}`,
|
|
8941
|
+
authorId: input.memory.authorId
|
|
8942
|
+
})) {
|
|
8943
|
+
validateMemory(memory);
|
|
8944
|
+
await input.memory.store.remember(memory);
|
|
8945
|
+
}
|
|
8946
|
+
} catch {
|
|
8947
|
+
}
|
|
8948
|
+
}
|
|
8545
8949
|
function goalEventLine(event) {
|
|
8546
8950
|
if (event.type === "attempt_started") return `Goal attempt ${event.attempt} starting.`;
|
|
8547
8951
|
if (event.type === "attempt_failed") return `Attempt ${event.attempt} did not satisfy the proof.`;
|
|
@@ -8816,18 +9220,18 @@ var CodePiRuntimeEngine = class {
|
|
|
8816
9220
|
/** Report every brokered effect as it starts and finishes. */
|
|
8817
9221
|
#observed(command, active, broker) {
|
|
8818
9222
|
return {
|
|
8819
|
-
execute: async (context,
|
|
9223
|
+
execute: async (context, request3) => {
|
|
8820
9224
|
const startedAt = Date.now();
|
|
8821
9225
|
await this.#event(
|
|
8822
9226
|
command,
|
|
8823
|
-
{ type: "tool", phase: "started", tool:
|
|
9227
|
+
{ type: "tool", phase: "started", tool: request3.tool },
|
|
8824
9228
|
active.conversationRefs
|
|
8825
9229
|
).catch(() => void 0);
|
|
8826
|
-
const response2 = await broker.execute(context,
|
|
9230
|
+
const response2 = await broker.execute(context, request3);
|
|
8827
9231
|
await this.#event(command, {
|
|
8828
9232
|
type: "tool",
|
|
8829
9233
|
phase: "completed",
|
|
8830
|
-
tool:
|
|
9234
|
+
tool: request3.tool,
|
|
8831
9235
|
ok: response2.ok,
|
|
8832
9236
|
durationMs: Date.now() - startedAt
|
|
8833
9237
|
}, active.conversationRefs).catch(() => void 0);
|
|
@@ -8967,7 +9371,7 @@ async function waitForHostedPoll(milliseconds, signal) {
|
|
|
8967
9371
|
}
|
|
8968
9372
|
function isValidHostedSecurityPlan(value2, env) {
|
|
8969
9373
|
if (!value2 || value2.env !== env || !/^sha256:[a-f0-9]{64}$/.test(value2.planDigest) || value2.consentContract !== "odla.hosted-security-consent.v1" || value2.reportProjection !== "odla.hosted-security-report.v1" || value2.redactionContract !== "odla.best-effort-credential-pattern-redaction.v1" || typeof value2.promptBundle !== "string" || value2.promptBundle.length < 1 || value2.promptBundle.length > 100 || typeof value2.ready !== "boolean" || typeof value2.independent !== "boolean" || value2.sourceDisclosure !== "redacted" || value2.targetExecution !== false || !Number.isSafeInteger(value2.reportRetentionDays) || value2.reportRetentionDays < 1) return false;
|
|
8970
|
-
const validRoute = (
|
|
9374
|
+
const validRoute = (route3, purpose) => !!route3 && route3.purpose === purpose && typeof route3.enabled === "boolean" && typeof route3.credentialReady === "boolean" && typeof route3.provider === "string" && route3.provider.length > 0 && route3.provider.length <= 100 && typeof route3.model === "string" && route3.model.length > 0 && route3.model.length <= 200 && Number.isSafeInteger(route3.policyVersion) && route3.policyVersion >= 1 && Number.isSafeInteger(route3.maxCallsPerRun) && route3.maxCallsPerRun >= 1 && Number.isSafeInteger(route3.maxInputBytes) && route3.maxInputBytes >= 1 && Number.isSafeInteger(route3.maxOutputTokens) && route3.maxOutputTokens >= 1;
|
|
8971
9375
|
return validRoute(value2.routes?.discovery, "security.discovery") && validRoute(value2.routes?.validation, "security.validation");
|
|
8972
9376
|
}
|
|
8973
9377
|
function hostedSecurityCredential(value2) {
|
|
@@ -9316,20 +9720,20 @@ async function runCodeRuntime(input) {
|
|
|
9316
9720
|
}
|
|
9317
9721
|
}
|
|
9318
9722
|
function parseConnection(value2, appId, appEnv) {
|
|
9319
|
-
const root =
|
|
9320
|
-
const host =
|
|
9321
|
-
const offer =
|
|
9322
|
-
const binding =
|
|
9723
|
+
const root = record6(value2);
|
|
9724
|
+
const host = record6(root?.host);
|
|
9725
|
+
const offer = record6(root?.offer);
|
|
9726
|
+
const binding = record6(root?.binding);
|
|
9323
9727
|
if (!root || typeof root.token !== "string" || !/^odla_code_host_[0-9a-f]{64}$/.test(root.token) || typeof root.resumed !== "boolean" || !host || !/^chost_[0-9a-f]{32}$/.test(String(host.hostId)) || typeof host.name !== "string" || !offer || !Number.isSafeInteger(offer.slots) || !binding || typeof binding.appId !== "string" || !binding.appId || appId && binding.appId !== appId || binding.env !== appEnv || !Number.isSafeInteger(binding.generation)) {
|
|
9324
9728
|
throw new Error("connect Code host returned an invalid response");
|
|
9325
9729
|
}
|
|
9326
9730
|
return root;
|
|
9327
9731
|
}
|
|
9328
9732
|
function apiFailure(action2, status, value2) {
|
|
9329
|
-
const message2 =
|
|
9733
|
+
const message2 = record6(record6(value2)?.error)?.message;
|
|
9330
9734
|
return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
|
|
9331
9735
|
}
|
|
9332
|
-
function
|
|
9736
|
+
function record6(value2) {
|
|
9333
9737
|
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
9334
9738
|
}
|
|
9335
9739
|
|
|
@@ -9548,9 +9952,9 @@ async function credentialCommand(parsed, deps = {}) {
|
|
|
9548
9952
|
}, doFetch, out, { optionalProjectCapabilities: ["app.manage"] });
|
|
9549
9953
|
const base = `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials`;
|
|
9550
9954
|
if (action2 === "revoke") {
|
|
9551
|
-
const
|
|
9552
|
-
if (!
|
|
9553
|
-
const response3 = await doFetch(`${base}/${encodeURIComponent(
|
|
9955
|
+
const id2 = parsed.positionals[2];
|
|
9956
|
+
if (!id2) throw new Error("credentials revoke requires the exact receipt id from credentials list");
|
|
9957
|
+
const response3 = await doFetch(`${base}/${encodeURIComponent(id2)}`, {
|
|
9554
9958
|
method: "DELETE",
|
|
9555
9959
|
headers: { authorization: `Bearer ${token}` }
|
|
9556
9960
|
});
|
|
@@ -9658,6 +10062,12 @@ Usage:
|
|
|
9658
10062
|
odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
|
|
9659
10063
|
odla-ai context remove <name> --yes [--json]
|
|
9660
10064
|
odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
|
|
10065
|
+
odla-ai monitor plan [--config odla.config.mjs] [--env prod] [--json]
|
|
10066
|
+
odla-ai monitor apply [--config odla.config.mjs] [--env prod] [--json] [--yes]
|
|
10067
|
+
odla-ai monitor run <probe-id> [--app <id>] [--env prod] [--json]
|
|
10068
|
+
odla-ai monitor status [--app <id>] [--context <name>] [--env prod] [--json]
|
|
10069
|
+
odla-ai monitor incidents [--app <id>] [--env prod] [--limit 100] [--runs] [--json]
|
|
10070
|
+
odla-ai monitor report [--app <id>] [--env prod] [--period daily|weekly] [--json]
|
|
9661
10071
|
odla-ai platform status [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
|
|
9662
10072
|
odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
|
|
9663
10073
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
@@ -9795,6 +10205,9 @@ Commands:
|
|
|
9795
10205
|
canary, collector ingest/scheduler trust, Cloudflare-owned
|
|
9796
10206
|
runtime metrics, and a machine verdict.
|
|
9797
10207
|
--json keeps auth progress on stderr for unattended agents.
|
|
10208
|
+
monitor Reconcile checked-in Kitesurf routes, rolling SLOs, spike/trend
|
|
10209
|
+
policies, and email digests; run probes manually and expose
|
|
10210
|
+
stable status, incident, and report JSON to agents and CI.
|
|
9798
10211
|
platform Read canonical fleet health, releases, provider load/freshness,
|
|
9799
10212
|
explicit unknowns, and next actions through a read-only grant.
|
|
9800
10213
|
provision Register services, compose integrations, persist credentials, optionally push secrets.
|
|
@@ -9806,7 +10219,9 @@ Commands:
|
|
|
9806
10219
|
copilot, gemini, or agents (repeatable or comma-separated).
|
|
9807
10220
|
secrets Push configured db/o11y secrets into the Worker via wrangler
|
|
9808
10221
|
stdin; set stores a tenant-vault secret and set-clerk-key the
|
|
9809
|
-
reserved Clerk secret key, write-only from stdin or an env var
|
|
10222
|
+
reserved Clerk secret key, write-only from stdin or an env var;
|
|
10223
|
+
status compares the secrets the config declares against the
|
|
10224
|
+
names the environment's vault holds (--json for a report).
|
|
9810
10225
|
version Print the CLI version.
|
|
9811
10226
|
|
|
9812
10227
|
Safety:
|
|
@@ -9996,7 +10411,7 @@ async function discussList(ctx, parsed) {
|
|
|
9996
10411
|
}
|
|
9997
10412
|
});
|
|
9998
10413
|
}
|
|
9999
|
-
async function discussRead(ctx,
|
|
10414
|
+
async function discussRead(ctx, id2, parsed) {
|
|
10000
10415
|
const requestedLimit = stringOpt(parsed.options.limit);
|
|
10001
10416
|
const requestedOffset = stringOpt(parsed.options.offset);
|
|
10002
10417
|
if (requestedLimit !== void 0 || requestedOffset !== void 0) {
|
|
@@ -10004,7 +10419,7 @@ async function discussRead(ctx, id, parsed) {
|
|
|
10004
10419
|
limit: requestedLimit ?? "200",
|
|
10005
10420
|
offset: requestedOffset ?? "0"
|
|
10006
10421
|
});
|
|
10007
|
-
const page2 = await request(ctx, "GET", `/topics/${encodeURIComponent(
|
|
10422
|
+
const page2 = await request(ctx, "GET", `/topics/${encodeURIComponent(id2)}?${query}`);
|
|
10008
10423
|
emit(
|
|
10009
10424
|
ctx,
|
|
10010
10425
|
page2,
|
|
@@ -10024,7 +10439,7 @@ async function discussRead(ctx, id, parsed) {
|
|
|
10024
10439
|
const page2 = await request(
|
|
10025
10440
|
ctx,
|
|
10026
10441
|
"GET",
|
|
10027
|
-
`/topics/${encodeURIComponent(
|
|
10442
|
+
`/topics/${encodeURIComponent(id2)}?limit=200&offset=${offset}`
|
|
10028
10443
|
);
|
|
10029
10444
|
topic = page2.topic;
|
|
10030
10445
|
for (const post of page2.posts) posts.set(post.id, post);
|
|
@@ -10066,20 +10481,20 @@ async function discussPost(ctx, parsed) {
|
|
|
10066
10481
|
});
|
|
10067
10482
|
emit(ctx, created, () => ctx.out.log(`opened topic ${created.id}`));
|
|
10068
10483
|
}
|
|
10069
|
-
async function discussReply(ctx,
|
|
10484
|
+
async function discussReply(ctx, id2, parsed) {
|
|
10070
10485
|
const created = await request(
|
|
10071
10486
|
ctx,
|
|
10072
10487
|
"POST",
|
|
10073
|
-
`/topics/${encodeURIComponent(
|
|
10488
|
+
`/topics/${encodeURIComponent(id2)}/replies`,
|
|
10074
10489
|
{ ...content(parsed), mutationId: writeMutationId(parsed) }
|
|
10075
10490
|
);
|
|
10076
10491
|
emit(ctx, created, () => ctx.out.log(`replied ${created.id}`));
|
|
10077
10492
|
}
|
|
10078
|
-
async function discussResolve(ctx,
|
|
10493
|
+
async function discussResolve(ctx, id2, resolved, parsed) {
|
|
10079
10494
|
const result = await request(
|
|
10080
10495
|
ctx,
|
|
10081
10496
|
"PATCH",
|
|
10082
|
-
`/topics/${encodeURIComponent(
|
|
10497
|
+
`/topics/${encodeURIComponent(id2)}`,
|
|
10083
10498
|
{ resolved, mutationId: writeMutationId(parsed) }
|
|
10084
10499
|
);
|
|
10085
10500
|
emit(ctx, result, () => ctx.out.log(`${resolved ? "resolved" : "reopened"} ${result.id}`));
|
|
@@ -10353,9 +10768,9 @@ var ALLOWED = [
|
|
|
10353
10768
|
"context",
|
|
10354
10769
|
"open"
|
|
10355
10770
|
];
|
|
10356
|
-
function requireId(
|
|
10357
|
-
if (!
|
|
10358
|
-
return
|
|
10771
|
+
function requireId(id2, action2) {
|
|
10772
|
+
if (!id2) throw new Error(`"discuss ${action2}" needs a topic id`);
|
|
10773
|
+
return id2;
|
|
10359
10774
|
}
|
|
10360
10775
|
async function buildContext(parsed, deps) {
|
|
10361
10776
|
const context = await resolveOperatorContext(parsed, {
|
|
@@ -10390,7 +10805,7 @@ async function buildContext(parsed, deps) {
|
|
|
10390
10805
|
async function discussCommand(parsed, deps = {}) {
|
|
10391
10806
|
assertArgs(parsed, ALLOWED, 3);
|
|
10392
10807
|
const action2 = parsed.positionals[1];
|
|
10393
|
-
const
|
|
10808
|
+
const id2 = parsed.positionals[2];
|
|
10394
10809
|
if (!action2) throw new Error('"discuss" needs an action. Run "odla-ai help".');
|
|
10395
10810
|
const ctx = await buildContext(parsed, deps);
|
|
10396
10811
|
switch (action2) {
|
|
@@ -10400,17 +10815,17 @@ async function discussCommand(parsed, deps = {}) {
|
|
|
10400
10815
|
case "topics":
|
|
10401
10816
|
return discussList(ctx, parsed);
|
|
10402
10817
|
case "read":
|
|
10403
|
-
return discussRead(ctx, requireId(
|
|
10818
|
+
return discussRead(ctx, requireId(id2, "read"), parsed);
|
|
10404
10819
|
case "post":
|
|
10405
10820
|
return discussPost(ctx, parsed);
|
|
10406
10821
|
case "reply":
|
|
10407
|
-
return discussReply(ctx, requireId(
|
|
10822
|
+
return discussReply(ctx, requireId(id2, "reply"), parsed);
|
|
10408
10823
|
case "resolve":
|
|
10409
|
-
return discussResolve(ctx, requireId(
|
|
10824
|
+
return discussResolve(ctx, requireId(id2, "resolve"), parsed.options.reopen !== true, parsed);
|
|
10410
10825
|
case "who":
|
|
10411
10826
|
return discussWho(ctx, parsed);
|
|
10412
10827
|
case "watch": {
|
|
10413
|
-
const result = await discussWatch(ctx,
|
|
10828
|
+
const result = await discussWatch(ctx, id2, parsed);
|
|
10414
10829
|
if (!result.found) throw new WatchTimeoutError(result.cursor);
|
|
10415
10830
|
return;
|
|
10416
10831
|
}
|
|
@@ -10481,8 +10896,8 @@ function collectFields(parsed, allowClear) {
|
|
|
10481
10896
|
if (allowClear) out[spec.key] = null;
|
|
10482
10897
|
continue;
|
|
10483
10898
|
}
|
|
10484
|
-
const
|
|
10485
|
-
out[spec.key] = spec.num ? Number(
|
|
10899
|
+
const text3 = stringOpt(value2);
|
|
10900
|
+
out[spec.key] = spec.num ? Number(text3) : text3;
|
|
10486
10901
|
}
|
|
10487
10902
|
return out;
|
|
10488
10903
|
}
|
|
@@ -10495,17 +10910,17 @@ function collectEntityFields(entity, parsed, allowClear) {
|
|
|
10495
10910
|
if (entity === "task" && fields.column === "ready") fields.column = "todo";
|
|
10496
10911
|
return fields;
|
|
10497
10912
|
}
|
|
10498
|
-
function statusCol(entity,
|
|
10499
|
-
if (entity === "bug") return `${
|
|
10913
|
+
function statusCol(entity, record11) {
|
|
10914
|
+
if (entity === "bug") return `${record11.status ?? ""}/${record11.severity ?? ""}`;
|
|
10500
10915
|
if (entity === "task") {
|
|
10501
|
-
const state2 =
|
|
10502
|
-
return
|
|
10916
|
+
const state2 = record11.column === "todo" ? "ready" : String(record11.column ?? "");
|
|
10917
|
+
return record11.revision ? `${state2}; r${record11.revision}` : state2;
|
|
10503
10918
|
}
|
|
10504
|
-
return String(
|
|
10919
|
+
return String(record11.status ?? "");
|
|
10505
10920
|
}
|
|
10506
|
-
function referenceMarkup(entity,
|
|
10507
|
-
const label = (
|
|
10508
|
-
return `@[${label}](pm:${entity}/${
|
|
10921
|
+
function referenceMarkup(entity, record11) {
|
|
10922
|
+
const label = (record11.title?.trim() || `${entity} ${record11.id}`).replaceAll("]", ")");
|
|
10923
|
+
return `@[${label}](pm:${entity}/${record11.id})`;
|
|
10509
10924
|
}
|
|
10510
10925
|
var STUDIO_SECTION = {
|
|
10511
10926
|
goal: "goals",
|
|
@@ -10513,19 +10928,19 @@ var STUDIO_SECTION = {
|
|
|
10513
10928
|
decision: "decisions",
|
|
10514
10929
|
bug: "bugs"
|
|
10515
10930
|
};
|
|
10516
|
-
function studioRecordUrl(ctx, entity,
|
|
10931
|
+
function studioRecordUrl(ctx, entity, id2) {
|
|
10517
10932
|
return new URL(
|
|
10518
|
-
`/studio/pm/${STUDIO_SECTION[entity]}/${encodeURIComponent(
|
|
10933
|
+
`/studio/pm/${STUDIO_SECTION[entity]}/${encodeURIComponent(id2)}`,
|
|
10519
10934
|
ctx.platformUrl
|
|
10520
10935
|
).href;
|
|
10521
10936
|
}
|
|
10522
|
-
function studioRecordLink(ctx, entity,
|
|
10523
|
-
const label = (
|
|
10524
|
-
return `[${label}](${studioRecordUrl(ctx, entity,
|
|
10937
|
+
function studioRecordLink(ctx, entity, record11) {
|
|
10938
|
+
const label = (record11.title?.trim() || `${entity} ${record11.id}`).replaceAll("]", ")");
|
|
10939
|
+
return `[${label}](${studioRecordUrl(ctx, entity, record11.id)})`;
|
|
10525
10940
|
}
|
|
10526
|
-
function printRecord(ctx, entity,
|
|
10941
|
+
function printRecord(ctx, entity, record11) {
|
|
10527
10942
|
ctx.out.log(
|
|
10528
|
-
`${
|
|
10943
|
+
`${record11.id} [${statusCol(entity, record11)}] ${record11.appId} ${studioRecordLink(ctx, entity, record11)}`
|
|
10529
10944
|
);
|
|
10530
10945
|
}
|
|
10531
10946
|
function emit2(ctx, value2, human) {
|
|
@@ -10579,52 +10994,52 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
10579
10994
|
input,
|
|
10580
10995
|
mutationId: writeMutationId2(parsed)
|
|
10581
10996
|
});
|
|
10582
|
-
const
|
|
10583
|
-
emit2(ctx, res, () => ctx.out.log(`created ${entity}: ${studioRecordLink(ctx, entity,
|
|
10997
|
+
const record11 = { id: res.id, appId, title: String(input.title) };
|
|
10998
|
+
emit2(ctx, res, () => ctx.out.log(`created ${entity}: ${studioRecordLink(ctx, entity, record11)}`));
|
|
10584
10999
|
}
|
|
10585
|
-
async function pmGet(ctx, entity,
|
|
10586
|
-
const { record:
|
|
10587
|
-
emit2(ctx,
|
|
11000
|
+
async function pmGet(ctx, entity, id2) {
|
|
11001
|
+
const { record: record11 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id2)}`);
|
|
11002
|
+
emit2(ctx, record11, () => printRecord(ctx, entity, record11));
|
|
10588
11003
|
}
|
|
10589
|
-
async function pmReference(ctx, entity,
|
|
10590
|
-
const { record:
|
|
11004
|
+
async function pmReference(ctx, entity, id2) {
|
|
11005
|
+
const { record: record11 } = await pmRequest(
|
|
10591
11006
|
ctx,
|
|
10592
11007
|
"GET",
|
|
10593
|
-
`/${entity}/${encodeURIComponent(
|
|
11008
|
+
`/${entity}/${encodeURIComponent(id2)}`
|
|
10594
11009
|
);
|
|
10595
|
-
const markup = referenceMarkup(entity,
|
|
10596
|
-
emit2(ctx, { kind: `pm:${entity}`, id:
|
|
11010
|
+
const markup = referenceMarkup(entity, record11);
|
|
11011
|
+
emit2(ctx, { kind: `pm:${entity}`, id: record11.id, label: record11.title ?? "", markup }, () => {
|
|
10597
11012
|
ctx.out.log(markup);
|
|
10598
11013
|
});
|
|
10599
11014
|
}
|
|
10600
|
-
async function pmSet(ctx, entity,
|
|
11015
|
+
async function pmSet(ctx, entity, id2, parsed) {
|
|
10601
11016
|
const patch2 = collectEntityFields(entity, parsed, true);
|
|
10602
11017
|
if (Object.keys(patch2).length === 0)
|
|
10603
11018
|
throw new Error("pm set needs at least one field flag (e.g. --status doing, --assignee me, --no-assignee)");
|
|
10604
|
-
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(
|
|
11019
|
+
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id2)}`, {
|
|
10605
11020
|
patch: patch2,
|
|
10606
11021
|
mutationId: writeMutationId2(parsed)
|
|
10607
11022
|
});
|
|
10608
11023
|
emit2(ctx, res, () => {
|
|
10609
|
-
if (!res.record) return ctx.out.log(`updated ${entity} ${
|
|
11024
|
+
if (!res.record) return ctx.out.log(`updated ${entity} ${id2}`);
|
|
10610
11025
|
ctx.out.log(`${entity}: ${studioRecordLink(ctx, entity, res.record)} \u2192 ${statusCol(entity, res.record)}`);
|
|
10611
11026
|
});
|
|
10612
11027
|
}
|
|
10613
|
-
async function pmDone(ctx, entity,
|
|
11028
|
+
async function pmDone(ctx, entity, id2, parsed) {
|
|
10614
11029
|
const decisionId = stringOpt(parsed.options.decision);
|
|
10615
11030
|
if (decisionId && entity !== "bug") throw new Error("--decision is only valid when completing a bug");
|
|
10616
11031
|
const patch2 = { ...DONE[entity], ...decisionId ? { decisionId } : {} };
|
|
10617
|
-
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(
|
|
11032
|
+
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id2)}`, {
|
|
10618
11033
|
patch: patch2,
|
|
10619
11034
|
mutationId: writeMutationId2(parsed)
|
|
10620
11035
|
});
|
|
10621
11036
|
emit2(ctx, res, () => {
|
|
10622
|
-
const label = res.record ? studioRecordLink(ctx, entity, res.record) :
|
|
11037
|
+
const label = res.record ? studioRecordLink(ctx, entity, res.record) : id2;
|
|
10623
11038
|
const state2 = res.record ? statusCol(entity, res.record) : "done";
|
|
10624
11039
|
ctx.out.log(`${entity}: ${label} \u2192 ${state2}`);
|
|
10625
11040
|
});
|
|
10626
11041
|
}
|
|
10627
|
-
async function pmTaskLifecycle(ctx,
|
|
11042
|
+
async function pmTaskLifecycle(ctx, id2, action2, parsed) {
|
|
10628
11043
|
const rawRevision = stringOpt(parsed.options["expected-revision"]);
|
|
10629
11044
|
const expectedRevision = Number(rawRevision);
|
|
10630
11045
|
if (!rawRevision || !Number.isSafeInteger(expectedRevision) || expectedRevision < 1) {
|
|
@@ -10634,7 +11049,7 @@ async function pmTaskLifecycle(ctx, id, action2, parsed) {
|
|
|
10634
11049
|
const res = action2 === "ready" ? await pmRequest(
|
|
10635
11050
|
ctx,
|
|
10636
11051
|
"PATCH",
|
|
10637
|
-
`/task/${encodeURIComponent(
|
|
11052
|
+
`/task/${encodeURIComponent(id2)}`,
|
|
10638
11053
|
{
|
|
10639
11054
|
patch: {
|
|
10640
11055
|
...collectEntityFields("task", parsed, true),
|
|
@@ -10646,12 +11061,12 @@ async function pmTaskLifecycle(ctx, id, action2, parsed) {
|
|
|
10646
11061
|
) : await pmRequest(
|
|
10647
11062
|
ctx,
|
|
10648
11063
|
"POST",
|
|
10649
|
-
`/task/${encodeURIComponent(
|
|
11064
|
+
`/task/${encodeURIComponent(id2)}/${action2}`,
|
|
10650
11065
|
{ expectedRevision, mutationId }
|
|
10651
11066
|
);
|
|
10652
11067
|
emit2(ctx, res, () => {
|
|
10653
11068
|
const state2 = res.record ? statusCol("task", res.record) : action2;
|
|
10654
|
-
const label = res.record ? studioRecordLink(ctx, "task", res.record) :
|
|
11069
|
+
const label = res.record ? studioRecordLink(ctx, "task", res.record) : id2;
|
|
10655
11070
|
ctx.out.log(`task: ${label} \u2192 ${state2}`);
|
|
10656
11071
|
});
|
|
10657
11072
|
}
|
|
@@ -10680,9 +11095,9 @@ async function pmNext(ctx, parsed) {
|
|
|
10680
11095
|
const result = {
|
|
10681
11096
|
appId,
|
|
10682
11097
|
projectId,
|
|
10683
|
-
openGoals: goals.filter((
|
|
10684
|
-
doing: tasks.filter((
|
|
10685
|
-
ready: tasks.filter((
|
|
11098
|
+
openGoals: goals.filter((record11) => record11.status === "open"),
|
|
11099
|
+
doing: tasks.filter((record11) => record11.column === "doing"),
|
|
11100
|
+
ready: tasks.filter((record11) => record11.column === "todo")
|
|
10686
11101
|
};
|
|
10687
11102
|
emit2(ctx, result, () => {
|
|
10688
11103
|
ctx.out.log(`${appId}: goal-aligned work intake (read only)`);
|
|
@@ -10693,10 +11108,10 @@ async function pmNext(ctx, parsed) {
|
|
|
10693
11108
|
]) {
|
|
10694
11109
|
ctx.out.log(`${label}:`);
|
|
10695
11110
|
if (!records.length) ctx.out.log("- (none)");
|
|
10696
|
-
else for (const
|
|
11111
|
+
else for (const record11 of records) printRecord(
|
|
10697
11112
|
ctx,
|
|
10698
11113
|
label === "open goals" ? "goal" : "task",
|
|
10699
|
-
|
|
11114
|
+
record11
|
|
10700
11115
|
);
|
|
10701
11116
|
}
|
|
10702
11117
|
if (!result.openGoals.length) {
|
|
@@ -10720,9 +11135,9 @@ async function pmHandoff(ctx, parsed) {
|
|
|
10720
11135
|
const handoff = {
|
|
10721
11136
|
appId,
|
|
10722
11137
|
projectId,
|
|
10723
|
-
unmetGoals: goals.filter((
|
|
10724
|
-
activeTasks: tasks.filter((
|
|
10725
|
-
openBugs: bugs.filter((
|
|
11138
|
+
unmetGoals: goals.filter((record11) => record11.status !== "met"),
|
|
11139
|
+
activeTasks: tasks.filter((record11) => record11.column !== "done"),
|
|
11140
|
+
openBugs: bugs.filter((record11) => record11.status !== "fixed" && record11.status !== "wontfix")
|
|
10726
11141
|
};
|
|
10727
11142
|
const result = {
|
|
10728
11143
|
...handoff,
|
|
@@ -10741,45 +11156,45 @@ async function pmHandoff(ctx, parsed) {
|
|
|
10741
11156
|
]) {
|
|
10742
11157
|
ctx.out.log(`${label}:`);
|
|
10743
11158
|
if (!records.length) ctx.out.log("- (none)");
|
|
10744
|
-
else for (const
|
|
11159
|
+
else for (const record11 of records) printRecord(
|
|
10745
11160
|
ctx,
|
|
10746
11161
|
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
10747
|
-
|
|
11162
|
+
record11
|
|
10748
11163
|
);
|
|
10749
11164
|
}
|
|
10750
11165
|
});
|
|
10751
11166
|
}
|
|
10752
|
-
async function pmRemove(ctx, entity,
|
|
10753
|
-
await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(
|
|
10754
|
-
ctx.out.log(`deleted ${entity} ${
|
|
11167
|
+
async function pmRemove(ctx, entity, id2) {
|
|
11168
|
+
await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id2)}`);
|
|
11169
|
+
ctx.out.log(`deleted ${entity} ${id2}`);
|
|
10755
11170
|
}
|
|
10756
11171
|
|
|
10757
11172
|
// src/pm-links.ts
|
|
10758
|
-
async function pmLink(ctx, entity,
|
|
10759
|
-
const { record:
|
|
11173
|
+
async function pmLink(ctx, entity, id2) {
|
|
11174
|
+
const { record: record11 } = await pmRequest(
|
|
10760
11175
|
ctx,
|
|
10761
11176
|
"GET",
|
|
10762
|
-
`/${entity}/${encodeURIComponent(
|
|
11177
|
+
`/${entity}/${encodeURIComponent(id2)}`
|
|
10763
11178
|
);
|
|
10764
|
-
const url = studioRecordUrl(ctx, entity,
|
|
10765
|
-
const markdown = studioRecordLink(ctx, entity,
|
|
10766
|
-
emit2(ctx, { kind: entity, id:
|
|
11179
|
+
const url = studioRecordUrl(ctx, entity, record11.id);
|
|
11180
|
+
const markdown = studioRecordLink(ctx, entity, record11);
|
|
11181
|
+
emit2(ctx, { kind: entity, id: record11.id, label: record11.title ?? "", url, markdown }, () => {
|
|
10767
11182
|
ctx.out.log(markdown);
|
|
10768
11183
|
});
|
|
10769
11184
|
}
|
|
10770
11185
|
|
|
10771
11186
|
// src/pm-comments.ts
|
|
10772
|
-
async function pmComment(ctx, entity,
|
|
11187
|
+
async function pmComment(ctx, entity, id2, parsed) {
|
|
10773
11188
|
const body = stringOpt(parsed.options.body);
|
|
10774
11189
|
if (!body) throw new Error('pm comment needs --body "..."');
|
|
10775
|
-
await pmRequest(ctx, "POST", `/${entity}/${encodeURIComponent(
|
|
11190
|
+
await pmRequest(ctx, "POST", `/${entity}/${encodeURIComponent(id2)}/comments`, {
|
|
10776
11191
|
body,
|
|
10777
11192
|
mutationId: writeMutationId2(parsed)
|
|
10778
11193
|
});
|
|
10779
|
-
ctx.out.log(`commented on ${entity} ${
|
|
11194
|
+
ctx.out.log(`commented on ${entity} ${id2}`);
|
|
10780
11195
|
}
|
|
10781
|
-
async function pmComments(ctx, entity,
|
|
10782
|
-
const { messages } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(
|
|
11196
|
+
async function pmComments(ctx, entity, id2) {
|
|
11197
|
+
const { messages } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id2)}/comments`);
|
|
10783
11198
|
emit2(ctx, messages, () => {
|
|
10784
11199
|
if (messages.length === 0) ctx.out.log("(no comments)");
|
|
10785
11200
|
else for (const message2 of messages) {
|
|
@@ -10797,12 +11212,12 @@ function fieldLine(change) {
|
|
|
10797
11212
|
const before = change.before.length > 60 ? `${change.before.slice(0, 60)}\u2026` : change.before;
|
|
10798
11213
|
return `${change.field} (was: ${before.replace(/\s+/g, " ")})`;
|
|
10799
11214
|
}
|
|
10800
|
-
async function pmHistory(ctx, entity,
|
|
11215
|
+
async function pmHistory(ctx, entity, id2, parsed) {
|
|
10801
11216
|
const limit = numberOpt(parsed.options.limit, "--limit");
|
|
10802
11217
|
const page2 = await pmRequest(
|
|
10803
11218
|
ctx,
|
|
10804
11219
|
"GET",
|
|
10805
|
-
`/${entity}/${encodeURIComponent(
|
|
11220
|
+
`/${entity}/${encodeURIComponent(id2)}/history${limit === void 0 ? "" : `?limit=${limit}`}`
|
|
10806
11221
|
);
|
|
10807
11222
|
emit2(ctx, page2, () => {
|
|
10808
11223
|
if (!page2.entries.length) {
|
|
@@ -10890,16 +11305,16 @@ async function page(ctx, appId, cursor) {
|
|
|
10890
11305
|
}
|
|
10891
11306
|
return data;
|
|
10892
11307
|
}
|
|
10893
|
-
function recordState(
|
|
10894
|
-
if (
|
|
10895
|
-
return String(
|
|
11308
|
+
function recordState(record11) {
|
|
11309
|
+
if (record11.column) return record11.column === "todo" ? "ready" : record11.column;
|
|
11310
|
+
return String(record11.status ?? "");
|
|
10896
11311
|
}
|
|
10897
11312
|
function eventRecord(event) {
|
|
10898
11313
|
return event.payload.payload;
|
|
10899
11314
|
}
|
|
10900
11315
|
function eventLabel(event) {
|
|
10901
|
-
const
|
|
10902
|
-
if (
|
|
11316
|
+
const record11 = eventRecord(event);
|
|
11317
|
+
if (record11) return String(record11.title ?? event.payload.entityId);
|
|
10903
11318
|
const body = event.payload.message?.body?.replace(/\s+/g, " ").trim();
|
|
10904
11319
|
return body || event.payload.entityId;
|
|
10905
11320
|
}
|
|
@@ -10907,10 +11322,10 @@ function report2(ctx, parsed, result) {
|
|
|
10907
11322
|
if (ctx.json) ctx.out.log(JSON.stringify(result, null, 2));
|
|
10908
11323
|
else if (parsed.options.jsonl !== true && result.found) {
|
|
10909
11324
|
for (const event of result.events ?? []) {
|
|
10910
|
-
const
|
|
10911
|
-
const state2 =
|
|
11325
|
+
const record11 = eventRecord(event);
|
|
11326
|
+
const state2 = record11 ? recordState(record11) : "comment";
|
|
10912
11327
|
ctx.out.log(
|
|
10913
|
-
`${event.id} ${event.type} ${state2}${
|
|
11328
|
+
`${event.id} ${event.type} ${state2}${record11?.revision ? `; r${record11.revision}` : ""} ${eventLabel(event)}`
|
|
10914
11329
|
);
|
|
10915
11330
|
}
|
|
10916
11331
|
}
|
|
@@ -10984,8 +11399,8 @@ async function pmWatch(ctx, parsed) {
|
|
|
10984
11399
|
}
|
|
10985
11400
|
firstSuccess = false;
|
|
10986
11401
|
const matching = current.events.filter((event) => {
|
|
10987
|
-
const
|
|
10988
|
-
const state2 =
|
|
11402
|
+
const record11 = eventRecord(event);
|
|
11403
|
+
const state2 = record11 ? recordState(record11).toLowerCase() : "";
|
|
10989
11404
|
return (!entity || event.payload.entityKind === entity) && (!action2 || event.payload.action === action2) && (!wantedState || state2 === wantedState || wantedState === "todo" && state2 === "ready") && (!by || event.actor.id === by) && (!self || event.actor.id !== self);
|
|
10990
11405
|
});
|
|
10991
11406
|
for (const event of matching) {
|
|
@@ -11073,9 +11488,9 @@ async function pmProjectAdd(ctx, parsed) {
|
|
|
11073
11488
|
});
|
|
11074
11489
|
emit2(ctx, result, () => ctx.out.log(`created project: ${result.project.name} (${result.project.id})`));
|
|
11075
11490
|
}
|
|
11076
|
-
async function pmProjectUse(ctx,
|
|
11491
|
+
async function pmProjectUse(ctx, id2) {
|
|
11077
11492
|
if (!ctx.rootDir) throw new Error("pm project use needs a local project directory");
|
|
11078
|
-
const { project } = await pmRequest(ctx, "GET", `/project/${encodeURIComponent(
|
|
11493
|
+
const { project } = await pmRequest(ctx, "GET", `/project/${encodeURIComponent(id2)}`);
|
|
11079
11494
|
if (project.status !== "active") throw new Error(`project ${project.name} is ${project.status}, not active`);
|
|
11080
11495
|
writePmProjectContext(ctx.rootDir, { appId: project.appId, projectId: project.id });
|
|
11081
11496
|
emit2(ctx, project, () => ctx.out.log(`using ${project.appId} / ${project.name} (${project.id}) in this worktree`));
|
|
@@ -11143,9 +11558,9 @@ function allowedOptions(entity, action2) {
|
|
|
11143
11558
|
const entityOptions = action2 === "list" || action2 === "add" || action2 === "set" || action2 === "done" ? ENTITY_OPTIONS[entity][action2] : [];
|
|
11144
11559
|
return [...COMMON_OPTIONS, ...ACTION_OPTIONS[action2], ...entityOptions];
|
|
11145
11560
|
}
|
|
11146
|
-
function requireId2(
|
|
11147
|
-
if (!
|
|
11148
|
-
return
|
|
11561
|
+
function requireId2(id2, action2) {
|
|
11562
|
+
if (!id2) throw new Error(`"pm ... ${action2}" needs an item id`);
|
|
11563
|
+
return id2;
|
|
11149
11564
|
}
|
|
11150
11565
|
async function buildContext2(parsed, deps) {
|
|
11151
11566
|
const context = await resolveOperatorContext(parsed, {
|
|
@@ -11236,34 +11651,34 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
11236
11651
|
throw new Error(`pm ${action2} is only valid for tasks`);
|
|
11237
11652
|
}
|
|
11238
11653
|
const ctx = await buildContext2(parsed, deps);
|
|
11239
|
-
const
|
|
11654
|
+
const id2 = parsed.positionals[3];
|
|
11240
11655
|
switch (action2) {
|
|
11241
11656
|
case "list":
|
|
11242
11657
|
return pmList(ctx, entity, parsed);
|
|
11243
11658
|
case "add":
|
|
11244
11659
|
return pmAdd(ctx, entity, parsed);
|
|
11245
11660
|
case "get":
|
|
11246
|
-
return pmGet(ctx, entity, requireId2(
|
|
11661
|
+
return pmGet(ctx, entity, requireId2(id2, action2));
|
|
11247
11662
|
case "set":
|
|
11248
|
-
return pmSet(ctx, entity, requireId2(
|
|
11663
|
+
return pmSet(ctx, entity, requireId2(id2, action2), parsed);
|
|
11249
11664
|
case "done":
|
|
11250
|
-
return pmDone(ctx, entity, requireId2(
|
|
11665
|
+
return pmDone(ctx, entity, requireId2(id2, action2), parsed);
|
|
11251
11666
|
case "comment":
|
|
11252
|
-
return pmComment(ctx, entity, requireId2(
|
|
11667
|
+
return pmComment(ctx, entity, requireId2(id2, action2), parsed);
|
|
11253
11668
|
case "comments":
|
|
11254
|
-
return pmComments(ctx, entity, requireId2(
|
|
11669
|
+
return pmComments(ctx, entity, requireId2(id2, action2));
|
|
11255
11670
|
case "history":
|
|
11256
|
-
return pmHistory(ctx, entity, requireId2(
|
|
11671
|
+
return pmHistory(ctx, entity, requireId2(id2, action2), parsed);
|
|
11257
11672
|
case "rm":
|
|
11258
|
-
return pmRemove(ctx, entity, requireId2(
|
|
11673
|
+
return pmRemove(ctx, entity, requireId2(id2, action2));
|
|
11259
11674
|
case "link":
|
|
11260
|
-
return pmLink(ctx, entity, requireId2(
|
|
11675
|
+
return pmLink(ctx, entity, requireId2(id2, action2));
|
|
11261
11676
|
case "ref":
|
|
11262
|
-
return pmReference(ctx, entity, requireId2(
|
|
11677
|
+
return pmReference(ctx, entity, requireId2(id2, action2));
|
|
11263
11678
|
case "ready":
|
|
11264
11679
|
case "claim":
|
|
11265
11680
|
case "release":
|
|
11266
|
-
return pmTaskLifecycle(ctx, requireId2(
|
|
11681
|
+
return pmTaskLifecycle(ctx, requireId2(id2, action2), action2, parsed);
|
|
11267
11682
|
}
|
|
11268
11683
|
}
|
|
11269
11684
|
|
|
@@ -11366,17 +11781,17 @@ async function platformStatus(parsed, deps) {
|
|
|
11366
11781
|
}
|
|
11367
11782
|
}
|
|
11368
11783
|
function isPlatformStatus(value2) {
|
|
11369
|
-
if (!
|
|
11370
|
-
if (!
|
|
11371
|
-
if (!
|
|
11784
|
+
if (!record7(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
|
|
11785
|
+
if (!record7(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
|
|
11786
|
+
if (!record7(value2.catalog) || !record7(value2.summary)) return false;
|
|
11372
11787
|
return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
|
|
11373
11788
|
}
|
|
11374
11789
|
function apiMessage(value2) {
|
|
11375
|
-
if (!
|
|
11376
|
-
const error =
|
|
11790
|
+
if (!record7(value2)) return "request failed";
|
|
11791
|
+
const error = record7(value2.error) ? value2.error : value2;
|
|
11377
11792
|
return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
|
|
11378
11793
|
}
|
|
11379
|
-
function
|
|
11794
|
+
function record7(value2) {
|
|
11380
11795
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
11381
11796
|
}
|
|
11382
11797
|
|
|
@@ -11417,7 +11832,7 @@ function statusVerdict(reads) {
|
|
|
11417
11832
|
severity: "degraded"
|
|
11418
11833
|
});
|
|
11419
11834
|
}
|
|
11420
|
-
const performance =
|
|
11835
|
+
const performance = record8(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
|
|
11421
11836
|
if (performance?.status === "unavailable") {
|
|
11422
11837
|
reasons.push({
|
|
11423
11838
|
source: "liveSync",
|
|
@@ -11498,7 +11913,7 @@ function statusVerdict(reads) {
|
|
|
11498
11913
|
reasons
|
|
11499
11914
|
};
|
|
11500
11915
|
}
|
|
11501
|
-
function
|
|
11916
|
+
function record8(value2) {
|
|
11502
11917
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
11503
11918
|
}
|
|
11504
11919
|
function numeric2(value2) {
|
|
@@ -11526,7 +11941,7 @@ function printO11yStatus(status, out) {
|
|
|
11526
11941
|
out.log(
|
|
11527
11942
|
`o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
|
|
11528
11943
|
);
|
|
11529
|
-
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(
|
|
11944
|
+
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record9) : [];
|
|
11530
11945
|
const requests = routes.reduce(
|
|
11531
11946
|
(total, row) => total + numeric3(row.requests),
|
|
11532
11947
|
0
|
|
@@ -11538,39 +11953,39 @@ function printO11yStatus(status, out) {
|
|
|
11538
11953
|
out.log(
|
|
11539
11954
|
`application ${status.application.httpStatus} ${requests} requests ${errors} errors`
|
|
11540
11955
|
);
|
|
11541
|
-
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(
|
|
11956
|
+
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record9) : [];
|
|
11542
11957
|
out.log(
|
|
11543
11958
|
`application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
|
|
11544
11959
|
(row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
|
|
11545
11960
|
).join(", ") : "none observed"}`
|
|
11546
11961
|
);
|
|
11547
11962
|
out.log(liveSyncLine(status.liveSync));
|
|
11548
|
-
const canaryDurations =
|
|
11963
|
+
const canaryDurations = record9(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
|
|
11549
11964
|
out.log(
|
|
11550
11965
|
`canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
|
|
11551
11966
|
);
|
|
11552
|
-
const collectorIngest =
|
|
11553
|
-
const collectorStorage =
|
|
11967
|
+
const collectorIngest = record9(status.collector.body.ingest) ? status.collector.body.ingest : {};
|
|
11968
|
+
const collectorStorage = record9(collectorIngest.storage) ? collectorIngest.storage : {};
|
|
11554
11969
|
out.log(
|
|
11555
11970
|
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
11556
11971
|
);
|
|
11557
|
-
const providerMetrics =
|
|
11558
|
-
const providerCapacity =
|
|
11559
|
-
const workerMemory =
|
|
11972
|
+
const providerMetrics = record9(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
11973
|
+
const providerCapacity = record9(status.provider.body.capacity) ? status.provider.body.capacity : {};
|
|
11974
|
+
const workerMemory = record9(providerCapacity.memory) ? providerCapacity.memory : {};
|
|
11560
11975
|
out.log(
|
|
11561
11976
|
`cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors ${optionalBytes(workerMemory.headroomBytes)} isolate memory headroom`
|
|
11562
11977
|
);
|
|
11563
11978
|
for (const line of providerCapacityLines(status.providerCapacity)) {
|
|
11564
11979
|
out.log(line);
|
|
11565
11980
|
}
|
|
11566
|
-
const coverage =
|
|
11567
|
-
const coverageCounts =
|
|
11568
|
-
const coverageBudget =
|
|
11981
|
+
const coverage = record9(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
|
|
11982
|
+
const coverageCounts = record9(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
|
|
11983
|
+
const coverageBudget = record9(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
|
|
11569
11984
|
out.log(
|
|
11570
11985
|
`request-coverage ${status.providerReconciliation.httpStatus} ${String(status.providerReconciliation.body.status ?? status.providerReconciliation.body.error ?? "unavailable")} ${optionalPercent(coverage.applicationCoverage)} application/provider ${numeric3(coverageCounts.applicationRequests)}/${numeric3(coverageCounts.providerRequests)} requests \xB1${optionalPercent(coverageBudget.maxRelativeError)} budget`
|
|
11571
11986
|
);
|
|
11572
11987
|
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
11573
|
-
const providerFreshness =
|
|
11988
|
+
const providerFreshness = record9(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
11574
11989
|
out.log(
|
|
11575
11990
|
`cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
|
|
11576
11991
|
);
|
|
@@ -11579,17 +11994,17 @@ function printO11yStatus(status, out) {
|
|
|
11579
11994
|
);
|
|
11580
11995
|
}
|
|
11581
11996
|
function providerCapacityLines(read3) {
|
|
11582
|
-
const resources =
|
|
11583
|
-
const durableObjects =
|
|
11584
|
-
const periodic =
|
|
11585
|
-
const storage =
|
|
11586
|
-
const d1 =
|
|
11587
|
-
const d1Activity =
|
|
11588
|
-
const d1Storage =
|
|
11589
|
-
const d1Latency =
|
|
11590
|
-
const r2 =
|
|
11591
|
-
const r2Operations =
|
|
11592
|
-
const r2Storage =
|
|
11997
|
+
const resources = record9(read3.body.resources) ? read3.body.resources : {};
|
|
11998
|
+
const durableObjects = record9(resources.durableObjects) ? resources.durableObjects : {};
|
|
11999
|
+
const periodic = record9(durableObjects.periodic) ? durableObjects.periodic : {};
|
|
12000
|
+
const storage = record9(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
|
|
12001
|
+
const d1 = record9(resources.d1) ? resources.d1 : {};
|
|
12002
|
+
const d1Activity = record9(d1.activity) ? d1.activity : {};
|
|
12003
|
+
const d1Storage = record9(d1.storage) ? d1.storage : {};
|
|
12004
|
+
const d1Latency = record9(d1Activity.latency) ? d1Activity.latency : {};
|
|
12005
|
+
const r2 = record9(resources.r2) ? resources.r2 : {};
|
|
12006
|
+
const r2Operations = record9(r2.operations) ? r2.operations : {};
|
|
12007
|
+
const r2Storage = record9(r2.storage) ? r2.storage : {};
|
|
11593
12008
|
const status = String(
|
|
11594
12009
|
read3.body.status ?? read3.body.error ?? "unavailable"
|
|
11595
12010
|
);
|
|
@@ -11600,11 +12015,11 @@ function providerCapacityLines(read3) {
|
|
|
11600
12015
|
];
|
|
11601
12016
|
}
|
|
11602
12017
|
function liveSyncLine(read3) {
|
|
11603
|
-
const performance =
|
|
11604
|
-
const commitToSend =
|
|
12018
|
+
const performance = record9(read3.body.performance) ? read3.body.performance : {};
|
|
12019
|
+
const commitToSend = record9(performance.commitToSend) ? performance.commitToSend : {};
|
|
11605
12020
|
return `live-sync ${read3.httpStatus} ${String(read3.body.status ?? read3.body.error ?? "unavailable")} ${numeric3(read3.body.activeConnections)} active ${optionalNumeric(commitToSend.p95)} commit-to-send p95 ${numeric3(performance.sendFailures)} send failures`;
|
|
11606
12021
|
}
|
|
11607
|
-
function
|
|
12022
|
+
function record9(value2) {
|
|
11608
12023
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
11609
12024
|
}
|
|
11610
12025
|
function numeric3(value2) {
|
|
@@ -11775,21 +12190,294 @@ function statusMinutes(value2) {
|
|
|
11775
12190
|
}
|
|
11776
12191
|
async function read2(url, headers, doFetch) {
|
|
11777
12192
|
const response2 = await doFetch(url, { headers });
|
|
11778
|
-
const
|
|
12193
|
+
const text3 = await response2.text();
|
|
11779
12194
|
let body = {};
|
|
11780
|
-
if (
|
|
12195
|
+
if (text3) {
|
|
11781
12196
|
try {
|
|
11782
|
-
const value2 = JSON.parse(
|
|
12197
|
+
const value2 = JSON.parse(text3);
|
|
11783
12198
|
body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
|
|
11784
12199
|
} catch {
|
|
11785
|
-
body = { message:
|
|
12200
|
+
body = { message: text3.slice(0, 300) };
|
|
11786
12201
|
}
|
|
11787
12202
|
}
|
|
11788
12203
|
return { httpStatus: response2.status, body };
|
|
11789
12204
|
}
|
|
11790
12205
|
|
|
12206
|
+
// src/monitoring-config.ts
|
|
12207
|
+
var import_node_crypto4 = require("crypto");
|
|
12208
|
+
function monitoringWireConfig(cfg, env) {
|
|
12209
|
+
const monitoring = cfg.o11y?.monitoring;
|
|
12210
|
+
if (!monitoring) throw new Error("o11y.monitoring is not configured");
|
|
12211
|
+
if (!cfg.services.includes("o11y")) throw new Error('o11y.monitoring requires "o11y" in services');
|
|
12212
|
+
const authoredLink = cfg.links?.[env];
|
|
12213
|
+
if (!authoredLink) throw new Error(`links.${env} is required for live monitoring`);
|
|
12214
|
+
const baseUrl = new URL(authoredLink).toString();
|
|
12215
|
+
const selectedProbes = (monitoring.probes ?? []).filter((probe) => !probe.envs || probe.envs.includes(env));
|
|
12216
|
+
const probeIds = new Set(selectedProbes.map((probe) => probe.id));
|
|
12217
|
+
const selectedSlos = monitoring.slos.filter(
|
|
12218
|
+
(slo) => slo.indicator.type === "o11y-metric" || slo.indicator.probes.some((id2) => probeIds.has(id2))
|
|
12219
|
+
);
|
|
12220
|
+
if (selectedSlos.length === 0) throw new Error(`o11y.monitoring has no SLOs for env "${env}"`);
|
|
12221
|
+
for (const slo of selectedSlos) {
|
|
12222
|
+
if (slo.indicator.type !== "probe-success") continue;
|
|
12223
|
+
const unavailable = slo.indicator.probes.filter((id2) => !probeIds.has(id2));
|
|
12224
|
+
if (unavailable.length) throw new Error(`SLO "${slo.id}" mixes probes unavailable in env "${env}": ${unavailable.join(", ")}`);
|
|
12225
|
+
}
|
|
12226
|
+
const payload = {
|
|
12227
|
+
environment: env,
|
|
12228
|
+
baseUrl,
|
|
12229
|
+
probes: selectedProbes.map(normalizeProbe),
|
|
12230
|
+
slos: selectedSlos.map(normalizeSlo),
|
|
12231
|
+
...notification(cfg.o11y.monitoring.notifications?.[env])
|
|
12232
|
+
};
|
|
12233
|
+
const revision = `sha256:${(0, import_node_crypto4.createHash)("sha256").update(canonical(payload)).digest("hex")}`;
|
|
12234
|
+
return { revision, ...payload };
|
|
12235
|
+
}
|
|
12236
|
+
function normalizeProbe(probe) {
|
|
12237
|
+
return {
|
|
12238
|
+
id: probe.id,
|
|
12239
|
+
route: probe.route,
|
|
12240
|
+
cadenceMinutes: durationMinutes(probe.every),
|
|
12241
|
+
timeoutMs: probe.timeout ?? 2e4,
|
|
12242
|
+
...probe.ready?.selector ? { readySelector: probe.ready.selector } : {},
|
|
12243
|
+
expect: {
|
|
12244
|
+
status: probe.expect.status,
|
|
12245
|
+
...probe.expect.titleIncludes ? { titleIncludes: probe.expect.titleIncludes } : {},
|
|
12246
|
+
textIncludes: probe.expect.textIncludes ?? [],
|
|
12247
|
+
accessibility: probe.expect.accessibility ?? []
|
|
12248
|
+
},
|
|
12249
|
+
enabled: probe.enabled !== false
|
|
12250
|
+
};
|
|
12251
|
+
}
|
|
12252
|
+
function normalizeSlo(slo) {
|
|
12253
|
+
return {
|
|
12254
|
+
id: slo.id,
|
|
12255
|
+
name: slo.name ?? slo.id,
|
|
12256
|
+
indicator: normalizeIndicator(slo.indicator),
|
|
12257
|
+
target: slo.target,
|
|
12258
|
+
windowMinutes: durationMinutes(slo.window),
|
|
12259
|
+
spike: {
|
|
12260
|
+
badChecks: slo.alerts?.spike?.badChecks ?? 2,
|
|
12261
|
+
withinChecks: slo.alerts?.spike?.withinChecks ?? 3,
|
|
12262
|
+
recoverAfter: slo.alerts?.spike?.recoverAfter ?? 2
|
|
12263
|
+
},
|
|
12264
|
+
trend: {
|
|
12265
|
+
burnRate: slo.alerts?.trend?.burnRate ?? 1,
|
|
12266
|
+
shortMinutes: durationMinutes(slo.alerts?.trend?.shortWindow ?? "6h"),
|
|
12267
|
+
longMinutes: durationMinutes(slo.alerts?.trend?.longWindow ?? "3d"),
|
|
12268
|
+
minBadChecks: slo.alerts?.trend?.minBadChecks ?? 2
|
|
12269
|
+
},
|
|
12270
|
+
enabled: slo.enabled !== false
|
|
12271
|
+
};
|
|
12272
|
+
}
|
|
12273
|
+
function normalizeIndicator(indicator) {
|
|
12274
|
+
if (indicator.type === "probe-success") {
|
|
12275
|
+
return { type: "probe-success", probes: [...new Set(indicator.probes)] };
|
|
12276
|
+
}
|
|
12277
|
+
return {
|
|
12278
|
+
type: "o11y-metric",
|
|
12279
|
+
metric: indicator.metric,
|
|
12280
|
+
comparator: indicator.comparator,
|
|
12281
|
+
threshold: indicator.threshold,
|
|
12282
|
+
cadenceMinutes: durationMinutes(indicator.every),
|
|
12283
|
+
observationWindowMinutes: durationMinutes(indicator.observationWindow),
|
|
12284
|
+
...indicator.route ? { route: indicator.route } : {}
|
|
12285
|
+
};
|
|
12286
|
+
}
|
|
12287
|
+
function notification(policy) {
|
|
12288
|
+
if (!policy) return {};
|
|
12289
|
+
return {
|
|
12290
|
+
notifications: {
|
|
12291
|
+
email: [...new Set(policy.email.map((email) => email.trim().toLowerCase()))],
|
|
12292
|
+
timezone: policy.timezone,
|
|
12293
|
+
daily: policy.daily === void 0 ? "08:00" : policy.daily,
|
|
12294
|
+
weekly: policy.weekly === void 0 ? { day: "monday", at: "08:00" } : policy.weekly
|
|
12295
|
+
}
|
|
12296
|
+
};
|
|
12297
|
+
}
|
|
12298
|
+
function durationMinutes(value2) {
|
|
12299
|
+
const match = /^(\d+)(m|h|d)$/.exec(value2);
|
|
12300
|
+
if (!match) throw new Error(`unsupported duration ${value2}`);
|
|
12301
|
+
const amount = Number(match[1]);
|
|
12302
|
+
return amount * (match[2] === "d" ? 1440 : match[2] === "h" ? 60 : 1);
|
|
12303
|
+
}
|
|
12304
|
+
function canonical(value2) {
|
|
12305
|
+
if (Array.isArray(value2)) return `[${value2.map(canonical).join(",")}]`;
|
|
12306
|
+
if (value2 && typeof value2 === "object") {
|
|
12307
|
+
return `{${Object.entries(value2).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`).join(",")}}`;
|
|
12308
|
+
}
|
|
12309
|
+
return JSON.stringify(value2);
|
|
12310
|
+
}
|
|
12311
|
+
|
|
12312
|
+
// src/monitor-command.ts
|
|
12313
|
+
var OPTIONS = [
|
|
12314
|
+
"config",
|
|
12315
|
+
"context",
|
|
12316
|
+
"platform",
|
|
12317
|
+
"token",
|
|
12318
|
+
"email",
|
|
12319
|
+
"json",
|
|
12320
|
+
"app",
|
|
12321
|
+
"env",
|
|
12322
|
+
"open",
|
|
12323
|
+
"yes",
|
|
12324
|
+
"period",
|
|
12325
|
+
"limit",
|
|
12326
|
+
"runs"
|
|
12327
|
+
];
|
|
12328
|
+
async function monitorCommand(parsed, deps = {}) {
|
|
12329
|
+
assertArgs(parsed, OPTIONS, 3);
|
|
12330
|
+
const action2 = parsed.positionals[1] ?? "status";
|
|
12331
|
+
if (!["plan", "apply", "run", "status", "incidents", "report"].includes(action2)) {
|
|
12332
|
+
throw new Error(`unknown monitor action "${action2}". Try "odla-ai monitor status --json".`);
|
|
12333
|
+
}
|
|
12334
|
+
const context = await resolveOperatorContext(parsed, {
|
|
12335
|
+
allowMissingConfig: action2 !== "plan" && action2 !== "apply",
|
|
12336
|
+
requireApp: true
|
|
12337
|
+
});
|
|
12338
|
+
if ((action2 === "plan" || action2 === "apply") && context.config.status !== "loaded") {
|
|
12339
|
+
throw new Error(`monitor ${action2} requires odla.config.mjs`);
|
|
12340
|
+
}
|
|
12341
|
+
const env = context.environment.value ?? context.cfg.envs[0] ?? "prod";
|
|
12342
|
+
const appId = context.app.value;
|
|
12343
|
+
const doFetch = deps.fetch ?? fetch;
|
|
12344
|
+
const out = deps.stdout ?? console;
|
|
12345
|
+
const token = await getDeveloperToken(
|
|
12346
|
+
context.cfg,
|
|
12347
|
+
{
|
|
12348
|
+
configPath: context.cfg.configPath,
|
|
12349
|
+
token: stringOpt(parsed.options.token),
|
|
12350
|
+
email: stringOpt(parsed.options.email),
|
|
12351
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
12352
|
+
openApprovalUrl: deps.openUrl
|
|
12353
|
+
},
|
|
12354
|
+
doFetch,
|
|
12355
|
+
out,
|
|
12356
|
+
action2 === "apply" || action2 === "run" ? { optionalProjectCapabilities: ["app.manage"] } : {}
|
|
12357
|
+
);
|
|
12358
|
+
const base = `${context.cfg.platformUrl}/o11y/${encodeURIComponent(appId)}/monitoring`;
|
|
12359
|
+
const headers = { authorization: `Bearer ${token}`, "content-type": "application/json" };
|
|
12360
|
+
const jsonOutput = parsed.options.json === true;
|
|
12361
|
+
if (action2 === "plan" || action2 === "apply") {
|
|
12362
|
+
const desired = monitoringWireConfig(context.cfg, env);
|
|
12363
|
+
const live = await request2(`${base}?env=${encodeURIComponent(env)}`, { headers }, doFetch);
|
|
12364
|
+
const currentRevision = record10(live.config) ? string(live.config.revision) : null;
|
|
12365
|
+
const changed = currentRevision !== desired.revision;
|
|
12366
|
+
const plan = {
|
|
12367
|
+
schemaVersion: 1,
|
|
12368
|
+
appId,
|
|
12369
|
+
env,
|
|
12370
|
+
currentRevision,
|
|
12371
|
+
desiredRevision: desired.revision,
|
|
12372
|
+
changed,
|
|
12373
|
+
probes: desired.probes.map((probe) => ({ id: probe.id, route: probe.route, cadenceMinutes: probe.cadenceMinutes })),
|
|
12374
|
+
slos: desired.slos.map((slo) => ({ id: slo.id, indicator: slo.indicator, target: slo.target, windowMinutes: slo.windowMinutes })),
|
|
12375
|
+
notifications: desired.notifications ? { recipients: desired.notifications.email.length, timezone: desired.notifications.timezone, daily: desired.notifications.daily, weekly: desired.notifications.weekly } : null
|
|
12376
|
+
};
|
|
12377
|
+
if (action2 === "plan") {
|
|
12378
|
+
emit3(plan, jsonOutput, out, () => {
|
|
12379
|
+
out.log(`monitor plan ${appId}/${env}: ${changed ? "changes pending" : "in sync"}`);
|
|
12380
|
+
out.log(`revision ${currentRevision ?? "not configured"} -> ${desired.revision}`);
|
|
12381
|
+
for (const probe of desired.probes) out.log(`probe ${probe.id} ${probe.route} every ${probe.cadenceMinutes}m`);
|
|
12382
|
+
for (const slo of desired.slos) out.log(`slo ${slo.id} ${slo.indicator.type} ${(slo.target * 100).toFixed(3)}% ${slo.windowMinutes}m`);
|
|
12383
|
+
});
|
|
12384
|
+
return;
|
|
12385
|
+
}
|
|
12386
|
+
if ((env === "prod" || env === "production") && parsed.options.yes !== true) {
|
|
12387
|
+
throw new Error(`refusing to apply live monitoring for "${env}" without --yes; run monitor plan first`);
|
|
12388
|
+
}
|
|
12389
|
+
if (!changed) {
|
|
12390
|
+
emit3({ ...plan, applied: false }, jsonOutput, out, () => out.log(`monitor apply ${appId}/${env}: already in sync`));
|
|
12391
|
+
return;
|
|
12392
|
+
}
|
|
12393
|
+
const applied = await request2(`${base}?env=${encodeURIComponent(env)}`, {
|
|
12394
|
+
method: "PUT",
|
|
12395
|
+
headers,
|
|
12396
|
+
body: JSON.stringify(desired)
|
|
12397
|
+
}, doFetch);
|
|
12398
|
+
emit3({ schemaVersion: 1, appId, env, ...applied }, jsonOutput, out, () => out.log(`monitor apply ${appId}/${env}: ${applied.changed === true ? "applied" : "unchanged"} ${desired.revision}`));
|
|
12399
|
+
return;
|
|
12400
|
+
}
|
|
12401
|
+
if (action2 === "run") {
|
|
12402
|
+
const probeId = parsed.positionals[2];
|
|
12403
|
+
if (!probeId) throw new Error("monitor run requires a probe id");
|
|
12404
|
+
const result2 = await request2(`${base}/probes/${encodeURIComponent(probeId)}/run?env=${encodeURIComponent(env)}`, {
|
|
12405
|
+
method: "POST",
|
|
12406
|
+
headers
|
|
12407
|
+
}, doFetch);
|
|
12408
|
+
emit3(result2, jsonOutput, out, () => {
|
|
12409
|
+
const run = record10(result2.run) ? result2.run : {};
|
|
12410
|
+
out.log(`monitor run ${appId}/${env}/${probeId}: ${string(run.outcome) ?? "unknown"}${run.failure_code ? ` (${String(run.failure_code)})` : ""}`);
|
|
12411
|
+
});
|
|
12412
|
+
return;
|
|
12413
|
+
}
|
|
12414
|
+
let path = action2;
|
|
12415
|
+
if (action2 === "report") {
|
|
12416
|
+
const period = stringOpt(parsed.options.period) ?? "daily";
|
|
12417
|
+
if (period !== "daily" && period !== "weekly") throw new Error("--period must be daily or weekly");
|
|
12418
|
+
path = `report?period=${period}`;
|
|
12419
|
+
} else if (action2 === "incidents") {
|
|
12420
|
+
const params = new URLSearchParams({ limit: String(numberOpt(parsed.options.limit, "--limit") ?? 100) });
|
|
12421
|
+
if (boolOpt(parsed.options.runs) === true) params.set("runs", "true");
|
|
12422
|
+
path = `incidents?${params}`;
|
|
12423
|
+
}
|
|
12424
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
12425
|
+
const result = await request2(`${base}/${path}${separator}env=${encodeURIComponent(env)}`, { headers }, doFetch);
|
|
12426
|
+
emit3(result, jsonOutput, out, () => printRead(action2, appId, env, result, out));
|
|
12427
|
+
}
|
|
12428
|
+
async function request2(url, init, doFetch) {
|
|
12429
|
+
const response2 = await doFetch(url, init);
|
|
12430
|
+
const text3 = await response2.text();
|
|
12431
|
+
let body = {};
|
|
12432
|
+
try {
|
|
12433
|
+
const parsed = text3 ? JSON.parse(text3) : {};
|
|
12434
|
+
body = record10(parsed) ? parsed : { value: parsed };
|
|
12435
|
+
} catch {
|
|
12436
|
+
body = { message: text3.slice(0, 500) };
|
|
12437
|
+
}
|
|
12438
|
+
if (!response2.ok) {
|
|
12439
|
+
const error = record10(body.error) ? body.error : body;
|
|
12440
|
+
throw new Error(string(error.message) ?? string(error.code) ?? `monitor request failed (${response2.status})`);
|
|
12441
|
+
}
|
|
12442
|
+
return body;
|
|
12443
|
+
}
|
|
12444
|
+
function printRead(action2, appId, env, result, out) {
|
|
12445
|
+
if (action2 === "status") {
|
|
12446
|
+
out.log(`monitor status ${appId}/${env}: ${String(result.overall ?? (result.configured === false ? "not configured" : "unknown"))}`);
|
|
12447
|
+
const slos = Array.isArray(result.slos) ? result.slos.filter(record10) : [];
|
|
12448
|
+
for (const slo of slos) out.log(`slo ${String(slo.id)} ${String(slo.state)} ${percent(slo.observed)} observed ${percent(slo.budgetRemaining)} budget remaining`);
|
|
12449
|
+
const incidents = Array.isArray(result.openIncidents) ? result.openIncidents.length : 0;
|
|
12450
|
+
const gaps = Array.isArray(result.monitoringGaps) ? result.monitoringGaps.length : 0;
|
|
12451
|
+
out.log(`open incidents ${incidents}`);
|
|
12452
|
+
out.log(`monitoring gaps ${gaps}`);
|
|
12453
|
+
return;
|
|
12454
|
+
}
|
|
12455
|
+
if (action2 === "incidents") {
|
|
12456
|
+
const incidents = Array.isArray(result.incidents) ? result.incidents.filter(record10) : [];
|
|
12457
|
+
out.log(`monitor incidents ${appId}/${env}: ${incidents.length}`);
|
|
12458
|
+
for (const incident2 of incidents) out.log(`${String(incident2.state)} ${String(incident2.kind)} ${String(incident2.slo_id)} ${new Date(Number(incident2.opened_at)).toISOString()}`);
|
|
12459
|
+
return;
|
|
12460
|
+
}
|
|
12461
|
+
out.log(`monitor report ${appId}/${env}: ${String(result.period)} ${String(result.overall)}`);
|
|
12462
|
+
const probes = Array.isArray(result.probes) ? result.probes.filter(record10) : [];
|
|
12463
|
+
for (const probe of probes) out.log(`probe ${String(probe.id)} ${Number(probe.good)} good ${Number(probe.bad)} bad ${Number(probe.unknown)} unknown`);
|
|
12464
|
+
}
|
|
12465
|
+
function emit3(value2, json, out, human) {
|
|
12466
|
+
if (json) out.log(JSON.stringify(value2, null, 2));
|
|
12467
|
+
else human();
|
|
12468
|
+
}
|
|
12469
|
+
function record10(value2) {
|
|
12470
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
12471
|
+
}
|
|
12472
|
+
function string(value2) {
|
|
12473
|
+
return typeof value2 === "string" ? value2 : null;
|
|
12474
|
+
}
|
|
12475
|
+
function percent(value2) {
|
|
12476
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? `${(value2 * 100).toFixed(2)}%` : "unknown";
|
|
12477
|
+
}
|
|
12478
|
+
|
|
11791
12479
|
// src/provision.ts
|
|
11792
|
-
var
|
|
12480
|
+
var import_apps13 = require("@odla-ai/apps");
|
|
11793
12481
|
var import_ai5 = require("@odla-ai/ai");
|
|
11794
12482
|
var import_node_process12 = __toESM(require("process"), 1);
|
|
11795
12483
|
|
|
@@ -11846,9 +12534,9 @@ async function responseText(res) {
|
|
|
11846
12534
|
}
|
|
11847
12535
|
|
|
11848
12536
|
// src/provision-credentials.ts
|
|
11849
|
-
var
|
|
12537
|
+
var import_apps12 = require("@odla-ai/apps");
|
|
11850
12538
|
async function provisionEnvCredentials(opts) {
|
|
11851
|
-
const tenantId = (0,
|
|
12539
|
+
const tenantId = (0, import_apps12.tenantIdFor)(opts.cfg.app.id, opts.env);
|
|
11852
12540
|
const prior = opts.credentials?.envs[opts.env];
|
|
11853
12541
|
let credentials = opts.credentials;
|
|
11854
12542
|
let dbKey = opts.cfg.services.includes("db") && !opts.rotateDb ? prior?.dbKey : void 0;
|
|
@@ -11941,13 +12629,13 @@ async function safeText7(res) {
|
|
|
11941
12629
|
}
|
|
11942
12630
|
|
|
11943
12631
|
// src/runtime-credentials.ts
|
|
11944
|
-
var
|
|
12632
|
+
var import_node_crypto5 = require("crypto");
|
|
11945
12633
|
function runtimeUrl(cfg, suffix = "") {
|
|
11946
12634
|
return `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials${suffix}`;
|
|
11947
12635
|
}
|
|
11948
12636
|
async function safeError(response2) {
|
|
11949
|
-
const
|
|
11950
|
-
return redactSecrets(
|
|
12637
|
+
const text3 = await response2.text();
|
|
12638
|
+
return redactSecrets(text3.slice(0, 1e3));
|
|
11951
12639
|
}
|
|
11952
12640
|
async function finish(doFetch, cfg, token, sessionId, method) {
|
|
11953
12641
|
return doFetch(runtimeUrl(cfg, `/${encodeURIComponent(sessionId)}`), {
|
|
@@ -11971,7 +12659,7 @@ async function deliverRuntimeCredentials(cfg, options) {
|
|
|
11971
12659
|
},
|
|
11972
12660
|
body: JSON.stringify({
|
|
11973
12661
|
env: options.env,
|
|
11974
|
-
idempotencyKey: `wrangler:${(0,
|
|
12662
|
+
idempotencyKey: `wrangler:${(0, import_node_crypto5.randomUUID)()}`,
|
|
11975
12663
|
target
|
|
11976
12664
|
})
|
|
11977
12665
|
});
|
|
@@ -12121,7 +12809,7 @@ async function provision(options) {
|
|
|
12121
12809
|
optionalProjectCapabilities: ["app.manage"],
|
|
12122
12810
|
forceReview: options.requestGrant
|
|
12123
12811
|
});
|
|
12124
|
-
const apps = (0,
|
|
12812
|
+
const apps = (0, import_apps13.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
12125
12813
|
const existing = await apps.resolveApp(cfg.app.id);
|
|
12126
12814
|
if (existing) {
|
|
12127
12815
|
out.log(`app: ${cfg.app.id} already exists`);
|
|
@@ -12133,7 +12821,7 @@ async function provision(options) {
|
|
|
12133
12821
|
try {
|
|
12134
12822
|
await apps.createApp({ name: cfg.app.name, appId: cfg.app.id });
|
|
12135
12823
|
} catch (error) {
|
|
12136
|
-
if (error instanceof
|
|
12824
|
+
if (error instanceof import_apps13.AppsError && error.status === 403) {
|
|
12137
12825
|
throw new Error(
|
|
12138
12826
|
`app "${cfg.app.id}" does not exist, and this authenticated agent credential has no owner-reviewed app.manage bootstrap grant for that exact id. Run "odla-ai provision --request-grant --email <odla-account>" to open the review URL and continue; developer ownership alone is not agent authority`,
|
|
12139
12827
|
{ cause: error }
|
|
@@ -12146,7 +12834,7 @@ async function provision(options) {
|
|
|
12146
12834
|
for (const env of cfg.envs) {
|
|
12147
12835
|
await assertTenantAdminAccess(doFetch, cfg, env, token);
|
|
12148
12836
|
}
|
|
12149
|
-
const serviceOrder = (0,
|
|
12837
|
+
const serviceOrder = (0, import_apps13.orderAppServices)(cfg.services);
|
|
12150
12838
|
for (const env of cfg.envs) {
|
|
12151
12839
|
for (const service of serviceOrder) {
|
|
12152
12840
|
if (service === "ai") {
|
|
@@ -12180,7 +12868,7 @@ async function provision(options) {
|
|
|
12180
12868
|
}
|
|
12181
12869
|
let devVarsCredentials = credentials;
|
|
12182
12870
|
for (const env of cfg.envs) {
|
|
12183
|
-
const tenantId = (0,
|
|
12871
|
+
const tenantId = (0, import_apps13.tenantIdFor)(cfg.app.id, env);
|
|
12184
12872
|
let dbKey;
|
|
12185
12873
|
if (options.pushSecrets) {
|
|
12186
12874
|
const delivered = await deliverRuntimeCredentials(cfg, {
|
|
@@ -12350,6 +13038,7 @@ var COMMAND_SURFACE = {
|
|
|
12350
13038
|
doctor: {},
|
|
12351
13039
|
help: {},
|
|
12352
13040
|
init: {},
|
|
13041
|
+
monitor: { plan: {}, apply: {}, run: {}, status: {}, incidents: {}, report: {} },
|
|
12353
13042
|
o11y: { status: {} },
|
|
12354
13043
|
operations: { get: {}, wait: {} },
|
|
12355
13044
|
platform: {
|
|
@@ -12382,7 +13071,7 @@ var COMMAND_SURFACE = {
|
|
|
12382
13071
|
rm: {},
|
|
12383
13072
|
lint: {}
|
|
12384
13073
|
},
|
|
12385
|
-
secrets: { push: {}, set: {}, "set-clerk-key": {} },
|
|
13074
|
+
secrets: { push: {}, status: {}, set: {}, "set-clerk-key": {} },
|
|
12386
13075
|
security: {
|
|
12387
13076
|
plan: {},
|
|
12388
13077
|
sources: {},
|
|
@@ -12639,12 +13328,12 @@ async function runbookRemove(ctx, slug) {
|
|
|
12639
13328
|
// src/runbook-import.ts
|
|
12640
13329
|
var import_node_fs17 = require("fs");
|
|
12641
13330
|
var import_node_path16 = require("path");
|
|
12642
|
-
function parseRunbook(
|
|
12643
|
-
let rest =
|
|
13331
|
+
function parseRunbook(text3, slug) {
|
|
13332
|
+
let rest = text3;
|
|
12644
13333
|
const meta = {};
|
|
12645
|
-
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(
|
|
13334
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text3);
|
|
12646
13335
|
if (fm) {
|
|
12647
|
-
rest =
|
|
13336
|
+
rest = text3.slice(fm[0].length);
|
|
12648
13337
|
for (const line of fm[1].split(/\r?\n/)) {
|
|
12649
13338
|
const pair = /^(\w+)\s*:\s*(.+)$/.exec(line.trim());
|
|
12650
13339
|
if (!pair) continue;
|
|
@@ -13413,9 +14102,9 @@ function printHostedSecurityIntent(out, intent) {
|
|
|
13413
14102
|
}
|
|
13414
14103
|
function assertHostedSecurityPlanReady(plan) {
|
|
13415
14104
|
const reasons = [];
|
|
13416
|
-
for (const [label,
|
|
13417
|
-
if (!
|
|
13418
|
-
if (!
|
|
14105
|
+
for (const [label, route3] of Object.entries(plan.routes)) {
|
|
14106
|
+
if (!route3.enabled) reasons.push(`${label} is disabled`);
|
|
14107
|
+
if (!route3.credentialReady) reasons.push(`${label} provider credential is unavailable`);
|
|
13419
14108
|
}
|
|
13420
14109
|
if (!plan.independent) reasons.push("discovery and validation are not independently routed");
|
|
13421
14110
|
if (plan.ready && reasons.length === 0) return;
|
|
@@ -13469,20 +14158,20 @@ function enforceHostedReportGate(report4, parsed, out, emitSuccess) {
|
|
|
13469
14158
|
out.log(`security gate passed: 0 confirmed >= ${failOn}; 0 leads >= ${failOnCandidates ?? "disabled"}; coverage ${report4.coverageStatus}. This is not proof that the application is secure.`);
|
|
13470
14159
|
}
|
|
13471
14160
|
}
|
|
13472
|
-
function printHostedSecurityPlanRoute(out, label,
|
|
13473
|
-
const readiness =
|
|
13474
|
-
|
|
13475
|
-
|
|
14161
|
+
function printHostedSecurityPlanRoute(out, label, route3) {
|
|
14162
|
+
const readiness = route3.enabled && route3.credentialReady ? "ready" : [
|
|
14163
|
+
route3.enabled ? void 0 : "disabled",
|
|
14164
|
+
route3.credentialReady ? void 0 : "credential unavailable"
|
|
13476
14165
|
].filter(Boolean).join(", ");
|
|
13477
|
-
out.log(` ${label}: ${
|
|
13478
|
-
out.log(` bounds: ${
|
|
14166
|
+
out.log(` ${label}: ${route3.provider}/${route3.model} \xB7 policy v${route3.policyVersion} \xB7 ${readiness}`);
|
|
14167
|
+
out.log(` bounds: ${route3.maxCallsPerRun} calls/run \xB7 ${route3.maxInputBytes} input bytes/call \xB7 ${route3.maxOutputTokens} output tokens/call`);
|
|
13479
14168
|
}
|
|
13480
14169
|
function printHostedCoverage(out, job) {
|
|
13481
14170
|
const coverage = job.coverage;
|
|
13482
14171
|
out.log(` coverage: ${job.coverageStatus ?? "pending"}${coverage?.completeCells !== void 0 ? ` ${coverage.completeCells}/${coverage.totalCells ?? "?"}` : ""}${coverage?.shallowCells ? ` shallow=${coverage.shallowCells}` : ""}${coverage?.blockedCells ? ` blocked=${coverage.blockedCells}` : ""}${coverage?.unscheduledCells ? ` unscheduled=${coverage.unscheduledCells}` : ""}${coverage?.budgetExhaustedCells ? ` budget_exhausted=${coverage.budgetExhaustedCells}` : ""}`);
|
|
13483
14172
|
}
|
|
13484
|
-
function routeLabel(
|
|
13485
|
-
return `${
|
|
14173
|
+
function routeLabel(route3) {
|
|
14174
|
+
return `${route3.provider}/${route3.model}${route3.policyVersion ? ` policy v${route3.policyVersion}` : ""}`;
|
|
13486
14175
|
}
|
|
13487
14176
|
var HOSTED_SEVERITIES = ["informational", "low", "medium", "high", "critical"];
|
|
13488
14177
|
function hostedSeverity(value2, flag) {
|
|
@@ -13569,11 +14258,11 @@ function selectEnv(requested, declared, configPath, rootDir) {
|
|
|
13569
14258
|
}
|
|
13570
14259
|
return env;
|
|
13571
14260
|
}
|
|
13572
|
-
async function injectedToken(options,
|
|
13573
|
-
const value2 = options.token ?? await options.getToken?.(Object.freeze({ ...
|
|
14261
|
+
async function injectedToken(options, request3) {
|
|
14262
|
+
const value2 = options.token ?? await options.getToken?.(Object.freeze({ ...request3 }));
|
|
13574
14263
|
if (typeof value2 !== "string" || value2.length < 8 || value2.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value2)) {
|
|
13575
14264
|
throw new Error(
|
|
13576
|
-
|
|
14265
|
+
request3.selfAudit ? "Self-audit requires an injected, scoped platform security token" : "Hosted security requires an injected app developer token or getToken callback"
|
|
13577
14266
|
);
|
|
13578
14267
|
}
|
|
13579
14268
|
return value2;
|
|
@@ -13886,11 +14575,11 @@ async function runLocalSecurityCommand(parsed, dependencies) {
|
|
|
13886
14575
|
sourceDisclosureAck: parsed.options["ack-redacted-source"] === true ? "redacted" : void 0,
|
|
13887
14576
|
fetch: doFetch,
|
|
13888
14577
|
stdout: out,
|
|
13889
|
-
getToken: async (
|
|
13890
|
-
if (
|
|
14578
|
+
getToken: async (request3) => {
|
|
14579
|
+
if (request3.scope === "platform:security:self") {
|
|
13891
14580
|
return getScopedPlatformToken({
|
|
13892
|
-
platform:
|
|
13893
|
-
scope:
|
|
14581
|
+
platform: request3.platform,
|
|
14582
|
+
scope: request3.scope,
|
|
13894
14583
|
email: stringOpt(parsed.options.email),
|
|
13895
14584
|
open,
|
|
13896
14585
|
fetch: doFetch,
|
|
@@ -13899,7 +14588,7 @@ async function runLocalSecurityCommand(parsed, dependencies) {
|
|
|
13899
14588
|
});
|
|
13900
14589
|
}
|
|
13901
14590
|
const cfg = await loadProjectConfig(configPath);
|
|
13902
|
-
if (platformAudience(cfg.platformUrl) !== platformAudience(
|
|
14591
|
+
if (platformAudience(cfg.platformUrl) !== platformAudience(request3.platform)) {
|
|
13903
14592
|
throw new Error("--platform cannot reuse a project developer token from another platform; update odla.config.mjs and authenticate there");
|
|
13904
14593
|
}
|
|
13905
14594
|
return getDeveloperToken(
|
|
@@ -14115,10 +14804,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
14115
14804
|
}
|
|
14116
14805
|
if (command === "bug") {
|
|
14117
14806
|
const action2 = parsed.positionals[1] ?? "list";
|
|
14118
|
-
const
|
|
14807
|
+
const canonical2 = action2 === "report" || action2 === "create" ? "add" : action2;
|
|
14119
14808
|
await pmCommand({
|
|
14120
14809
|
...parsed,
|
|
14121
|
-
positionals: ["pm", "bug",
|
|
14810
|
+
positionals: ["pm", "bug", canonical2, ...parsed.positionals.slice(2)]
|
|
14122
14811
|
}, runtime);
|
|
14123
14812
|
return;
|
|
14124
14813
|
}
|
|
@@ -14130,6 +14819,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
14130
14819
|
await o11yCommand(parsed, runtime);
|
|
14131
14820
|
return;
|
|
14132
14821
|
}
|
|
14822
|
+
if (command === "monitor") {
|
|
14823
|
+
await monitorCommand(parsed, runtime);
|
|
14824
|
+
return;
|
|
14825
|
+
}
|
|
14133
14826
|
if (command === "platform") {
|
|
14134
14827
|
await platformCommand(parsed, runtime);
|
|
14135
14828
|
return;
|
|
@@ -14248,6 +14941,8 @@ async function calendarCommand(parsed, dependencies) {
|
|
|
14248
14941
|
isTerminalHostedSecurityStatus,
|
|
14249
14942
|
listGitHubSecuritySources,
|
|
14250
14943
|
listHostedSecurityJobs,
|
|
14944
|
+
monitorCommand,
|
|
14945
|
+
monitoringWireConfig,
|
|
14251
14946
|
printCapabilities,
|
|
14252
14947
|
provision,
|
|
14253
14948
|
reconcileConfig,
|