@odla-ai/cli 0.32.1 → 0.33.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/dist/bin.cjs +396 -137
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-L6YTOTWU.js → chunk-YSQORU5J.js} +364 -133
- package/dist/chunk-YSQORU5J.js.map +1 -0
- package/dist/{cli-LYFPBGNH.js → cli-U436OLYW.js} +2 -2
- package/dist/index.cjs +365 -134
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -2
- package/dist/index.d.ts +45 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-L6YTOTWU.js.map +0 -1
- /package/dist/{cli-LYFPBGNH.js.map → cli-U436OLYW.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -1075,6 +1075,111 @@ function safeText(value2, max) {
|
|
|
1075
1075
|
return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
|
|
1076
1076
|
}
|
|
1077
1077
|
|
|
1078
|
+
// src/calendar-config.ts
|
|
1079
|
+
function calendarServiceConfig(cfg, env) {
|
|
1080
|
+
if (!cfg.services.includes("calendar")) throw new Error("calendar service is not enabled in config services");
|
|
1081
|
+
if (!cfg.envs.includes(env) && env !== "prod") throw new Error(`calendar env "${env}" is not declared in config envs`);
|
|
1082
|
+
const google = cfg.calendar?.google;
|
|
1083
|
+
if (!google) throw new Error("calendar.google is required when the calendar service is enabled");
|
|
1084
|
+
const configured = google.availabilityCalendars?.[env] ?? google.calendars?.[env];
|
|
1085
|
+
if (!configured?.length) throw new Error(`calendar.google.availabilityCalendars.${env} is required`);
|
|
1086
|
+
const availability = unique(configured.map((id) => id.trim()));
|
|
1087
|
+
return {
|
|
1088
|
+
provider: "google",
|
|
1089
|
+
access: "book",
|
|
1090
|
+
bookingCalendarId: google.bookingCalendar?.[env]?.trim() ?? availability[0],
|
|
1091
|
+
availabilityCalendars: availability
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
function calendarBookingPageUrl(cfg, env) {
|
|
1095
|
+
const value2 = cfg.calendar?.google.bookingPageUrl?.[env];
|
|
1096
|
+
if (value2 === void 0 || value2 === null) return value2;
|
|
1097
|
+
return new URL(value2).toString();
|
|
1098
|
+
}
|
|
1099
|
+
function validateCalendarConfig(cfg, envs, services, path) {
|
|
1100
|
+
const enabled = services.includes("calendar");
|
|
1101
|
+
if (!cfg.calendar) {
|
|
1102
|
+
if (enabled) throw new Error(`${path}: calendar.google is required when services includes "calendar"`);
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
if (!isRecord5(cfg.calendar)) throw new Error(`${path}: calendar must be an object`);
|
|
1106
|
+
assertOnly2(cfg.calendar, ["google"], `${path}: calendar`);
|
|
1107
|
+
if (!isRecord5(cfg.calendar.google)) throw new Error(`${path}: calendar.google must be an object`);
|
|
1108
|
+
const google = cfg.calendar.google;
|
|
1109
|
+
assertOnly2(
|
|
1110
|
+
google,
|
|
1111
|
+
["availabilityCalendars", "calendars", "bookingCalendar", "bookingPageUrl"],
|
|
1112
|
+
`${path}: calendar.google`
|
|
1113
|
+
);
|
|
1114
|
+
const availabilityKey = google.availabilityCalendars !== void 0 ? "availabilityCalendars" : google.calendars !== void 0 ? "calendars" : null;
|
|
1115
|
+
if (!availabilityKey || google.availabilityCalendars !== void 0 && google.calendars !== void 0) {
|
|
1116
|
+
throw new Error(`${path}: calendar.google requires exactly one of availabilityCalendars or calendars (legacy)`);
|
|
1117
|
+
}
|
|
1118
|
+
const availability = google[availabilityKey];
|
|
1119
|
+
if (!isRecord5(availability)) throw new Error(`${path}: calendar.google.${availabilityKey} must map env names to calendar ids`);
|
|
1120
|
+
const unknownEnv = Object.keys(availability).find((env) => !envs.includes(env) && env !== "prod");
|
|
1121
|
+
if (unknownEnv) throw new Error(`${path}: calendar.google.${availabilityKey}.${unknownEnv} is not in config envs`);
|
|
1122
|
+
for (const env of envs) {
|
|
1123
|
+
const ids = availability[env];
|
|
1124
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
1125
|
+
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
for (const [env, ids] of Object.entries(availability)) {
|
|
1129
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
1130
|
+
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
|
|
1131
|
+
}
|
|
1132
|
+
if (ids.length > 10) {
|
|
1133
|
+
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must contain at most 10 calendar ids`);
|
|
1134
|
+
}
|
|
1135
|
+
if (ids.some((id) => !safeText2(id, 1024))) {
|
|
1136
|
+
throw new Error(`${path}: calendar.google.${availabilityKey}.${env} contains an invalid calendar id`);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
if (google.bookingCalendar !== void 0) {
|
|
1140
|
+
if (!isRecord5(google.bookingCalendar)) throw new Error(`${path}: calendar.google.bookingCalendar must map env names to one calendar id`);
|
|
1141
|
+
const unknownBookingEnv = Object.keys(google.bookingCalendar).find((env) => !envs.includes(env) && env !== "prod");
|
|
1142
|
+
if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingCalendar.${unknownBookingEnv} is not in config envs`);
|
|
1143
|
+
for (const [env, value2] of Object.entries(google.bookingCalendar)) {
|
|
1144
|
+
if (!safeText2(value2, 1024)) {
|
|
1145
|
+
throw new Error(`${path}: calendar.google.bookingCalendar.${env} must be a calendar id`);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
if (google.bookingPageUrl !== void 0) {
|
|
1150
|
+
if (!isRecord5(google.bookingPageUrl)) throw new Error(`${path}: calendar.google.bookingPageUrl must map env names to HTTPS URLs or null`);
|
|
1151
|
+
const unknownBookingEnv = Object.keys(google.bookingPageUrl).find((env) => !envs.includes(env) && env !== "prod");
|
|
1152
|
+
if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingPageUrl.${unknownBookingEnv} is not in config envs`);
|
|
1153
|
+
for (const [env, value2] of Object.entries(google.bookingPageUrl)) {
|
|
1154
|
+
if (value2 !== null && !safeHttpsUrl(value2)) {
|
|
1155
|
+
throw new Error(`${path}: calendar.google.bookingPageUrl.${env} must be an HTTPS URL without credentials or fragment`);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
function assertOnly2(value2, allowed, label) {
|
|
1161
|
+
const extra = Object.keys(value2).find((key) => !allowed.includes(key));
|
|
1162
|
+
if (extra) throw new Error(`${label}.${extra} is not supported`);
|
|
1163
|
+
}
|
|
1164
|
+
function isRecord5(value2) {
|
|
1165
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
1166
|
+
}
|
|
1167
|
+
function safeText2(value2, max) {
|
|
1168
|
+
return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
|
|
1169
|
+
}
|
|
1170
|
+
function safeHttpsUrl(value2) {
|
|
1171
|
+
if (typeof value2 !== "string" || value2.length > 2048) return false;
|
|
1172
|
+
try {
|
|
1173
|
+
const url = new URL(value2);
|
|
1174
|
+
return url.protocol === "https:" && !url.username && !url.password && !url.hash;
|
|
1175
|
+
} catch {
|
|
1176
|
+
return false;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
function unique(values) {
|
|
1180
|
+
return [...new Set(values.filter(Boolean))];
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1078
1183
|
// src/integration-validation.ts
|
|
1079
1184
|
function validateIntegrations(cfg, path, defaultServices) {
|
|
1080
1185
|
if (cfg.integrations === void 0) return;
|
|
@@ -1082,36 +1187,61 @@ function validateIntegrations(cfg, path, defaultServices) {
|
|
|
1082
1187
|
const ids = /* @__PURE__ */ new Set();
|
|
1083
1188
|
for (const [index, integration] of cfg.integrations.entries()) {
|
|
1084
1189
|
const at = `${path}: integrations[${index}]`;
|
|
1085
|
-
if (!
|
|
1190
|
+
if (!isRecord6(integration)) throw new Error(`${at} must be an object`);
|
|
1086
1191
|
if (!validId(integration.id)) throw new Error(`${at}.id must be lowercase letters, numbers, and hyphens`);
|
|
1087
1192
|
if (ids.has(integration.id)) throw new Error(`${path}: duplicate integration id "${integration.id}"`);
|
|
1088
1193
|
ids.add(integration.id);
|
|
1089
|
-
if (!
|
|
1090
|
-
if (!
|
|
1091
|
-
if (integration.schema !== void 0 && (!
|
|
1194
|
+
if (!safeText3(integration.title, 200)) throw new Error(`${at}.title is required`);
|
|
1195
|
+
if (!safeText3(integration.npm, 200)) throw new Error(`${at}.npm is required`);
|
|
1196
|
+
if (integration.schema !== void 0 && (!isRecord6(integration.schema) || !isRecord6(integration.schema.entities))) {
|
|
1092
1197
|
throw new Error(`${at}.schema must contain an entities object`);
|
|
1093
1198
|
}
|
|
1094
|
-
if (integration.rules !== void 0 && !
|
|
1199
|
+
if (integration.rules !== void 0 && !isRecord6(integration.rules)) throw new Error(`${at}.rules must be an object`);
|
|
1095
1200
|
validateSeeds(integration, at);
|
|
1096
1201
|
validateProbes(integration, at);
|
|
1202
|
+
validateSecrets(integration.secrets, at);
|
|
1097
1203
|
}
|
|
1098
1204
|
const needsDb = cfg.integrations.some((integration) => integration.schema || integration.rules || integration.seeds?.length);
|
|
1099
|
-
const services =
|
|
1205
|
+
const services = unique2(cfg.services?.length ? cfg.services : defaultServices);
|
|
1100
1206
|
if (needsDb && !services.includes("db")) throw new Error(`${path}: schema/rules/seed integrations require the db service`);
|
|
1101
1207
|
}
|
|
1208
|
+
var SECRET_NAME = /^\$?[a-z][a-z0-9_]*$/;
|
|
1209
|
+
function validateSecrets(value2, at) {
|
|
1210
|
+
if (value2 === void 0) return;
|
|
1211
|
+
if (!Array.isArray(value2)) throw new Error(`${at}.secrets must be an array`);
|
|
1212
|
+
const names = /* @__PURE__ */ new Set();
|
|
1213
|
+
for (const [index, secret] of value2.entries()) {
|
|
1214
|
+
const sat = `${at}.secrets[${index}]`;
|
|
1215
|
+
if (!isRecord6(secret)) throw new Error(`${sat} must be an object`);
|
|
1216
|
+
if (typeof secret.name !== "string" || !SECRET_NAME.test(secret.name) || secret.name.length > 64) {
|
|
1217
|
+
throw new Error(`${sat}.name must be lowercase snake_case (optionally "$"-prefixed when reserved), e.g. "clerk_webhook_secret"`);
|
|
1218
|
+
}
|
|
1219
|
+
const dollar = secret.name.startsWith("$");
|
|
1220
|
+
if (dollar !== (secret.reserved === true)) {
|
|
1221
|
+
throw new Error(
|
|
1222
|
+
dollar ? `${sat}.name is "$"-prefixed, so it must also set reserved: true` : `${sat} sets reserved: true, so its name must be "$"-prefixed`
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
if (!safeText3(secret.description, 500)) throw new Error(`${sat}.description is required \u2014 it is what doctor and the docs show`);
|
|
1226
|
+
if (secret.pattern !== void 0 && !safeText3(secret.pattern, 64)) throw new Error(`${sat}.pattern must be a non-empty prefix string`);
|
|
1227
|
+
if (secret.required !== void 0 && typeof secret.required !== "boolean") throw new Error(`${sat}.required must be a boolean`);
|
|
1228
|
+
if (names.has(secret.name)) throw new Error(`${at} declares secret "${secret.name}" twice`);
|
|
1229
|
+
names.add(secret.name);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1102
1232
|
function validateSeeds(integration, at) {
|
|
1103
1233
|
if (integration.seeds === void 0) return;
|
|
1104
1234
|
if (!Array.isArray(integration.seeds)) throw new Error(`${at}.seeds must be an array`);
|
|
1105
1235
|
const ids = /* @__PURE__ */ new Set();
|
|
1106
1236
|
for (const [index, seed] of integration.seeds.entries()) {
|
|
1107
1237
|
const sat = `${at}.seeds[${index}]`;
|
|
1108
|
-
if (!
|
|
1238
|
+
if (!isRecord6(seed) || !safeText3(seed.id, 200) || !safeText3(seed.ns, 200)) throw new Error(`${sat} requires id and ns`);
|
|
1109
1239
|
if (ids.has(seed.id)) throw new Error(`${at} has duplicate seed id "${seed.id}"`);
|
|
1110
1240
|
ids.add(seed.id);
|
|
1111
|
-
if (!
|
|
1241
|
+
if (!isRecord6(seed.key) || !safeText3(seed.key.attr, 200) || !safeText3(seed.key.value, 2048)) {
|
|
1112
1242
|
throw new Error(`${sat}.key requires string attr and value`);
|
|
1113
1243
|
}
|
|
1114
|
-
if (!
|
|
1244
|
+
if (!isRecord6(seed.attrs)) throw new Error(`${sat}.attrs must be an object`);
|
|
1115
1245
|
if (Object.hasOwn(seed.attrs, seed.key.attr) && seed.attrs[seed.key.attr] !== seed.key.value) {
|
|
1116
1246
|
throw new Error(`${sat}.attrs.${seed.key.attr} conflicts with its natural key`);
|
|
1117
1247
|
}
|
|
@@ -1122,16 +1252,16 @@ function validateProbes(integration, at) {
|
|
|
1122
1252
|
if (!Array.isArray(integration.probes)) throw new Error(`${at}.probes must be an array`);
|
|
1123
1253
|
for (const [index, probe] of integration.probes.entries()) {
|
|
1124
1254
|
const pat = `${at}.probes[${index}]`;
|
|
1125
|
-
if (!
|
|
1255
|
+
if (!isRecord6(probe) || !safeProbePath(probe.path)) throw new Error(`${pat}.path must be an absolute path without query or fragment`);
|
|
1126
1256
|
if (!Number.isInteger(probe.expectedStatus) || probe.expectedStatus < 100 || probe.expectedStatus > 599) {
|
|
1127
1257
|
throw new Error(`${pat}.expectedStatus must be an HTTP status`);
|
|
1128
1258
|
}
|
|
1129
1259
|
}
|
|
1130
1260
|
}
|
|
1131
|
-
function
|
|
1261
|
+
function isRecord6(value2) {
|
|
1132
1262
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
1133
1263
|
}
|
|
1134
|
-
function
|
|
1264
|
+
function safeText3(value2, max) {
|
|
1135
1265
|
return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
|
|
1136
1266
|
}
|
|
1137
1267
|
function safeProbePath(value2) {
|
|
@@ -1140,7 +1270,7 @@ function safeProbePath(value2) {
|
|
|
1140
1270
|
function validId(value2) {
|
|
1141
1271
|
return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
|
|
1142
1272
|
}
|
|
1143
|
-
function
|
|
1273
|
+
function unique2(values) {
|
|
1144
1274
|
return [...new Set(values.filter(Boolean))];
|
|
1145
1275
|
}
|
|
1146
1276
|
|
|
@@ -1160,10 +1290,10 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
|
|
|
1160
1290
|
validateRawConfig(raw, resolved);
|
|
1161
1291
|
const platformUrl = trimSlash(process.env.ODLA_PLATFORM_URL || raw.platformUrl || DEFAULT_PLATFORM);
|
|
1162
1292
|
const dbEndpoint = trimSlash(process.env.ODLA_DB_ENDPOINT || raw.dbEndpoint || platformUrl);
|
|
1163
|
-
const envs =
|
|
1164
|
-
const services =
|
|
1293
|
+
const envs = unique3(raw.envs?.length ? raw.envs : DEFAULT_ENVS);
|
|
1294
|
+
const services = unique3(raw.services?.length ? raw.services : DEFAULT_SERVICES);
|
|
1165
1295
|
validateServices(services, resolved);
|
|
1166
|
-
validateCalendarConfig(raw,
|
|
1296
|
+
validateCalendarConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
|
|
1167
1297
|
const local = {
|
|
1168
1298
|
tokenFile: (0, import_node_path4.resolve)(rootDir, raw.local?.tokenFile ?? ".odla/dev-token.json"),
|
|
1169
1299
|
credentialsFile: (0, import_node_path4.resolve)(rootDir, raw.local?.credentialsFile ?? ".odla/credentials.local.json"),
|
|
@@ -1211,26 +1341,6 @@ function buildPlan(cfg) {
|
|
|
1211
1341
|
aiProvider: cfg.ai?.provider
|
|
1212
1342
|
};
|
|
1213
1343
|
}
|
|
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
1344
|
function rulesFromSchema(schema) {
|
|
1235
1345
|
const entities = serializedEntities(schema);
|
|
1236
1346
|
return Object.fromEntries(
|
|
@@ -1262,69 +1372,9 @@ function validateRawConfig(raw, path) {
|
|
|
1262
1372
|
throw new Error(`${path}: services must be an array of non-empty names`);
|
|
1263
1373
|
}
|
|
1264
1374
|
validateAiConfig(cfg, path);
|
|
1375
|
+
validateSecrets(cfg.secrets, `${path}: config`);
|
|
1265
1376
|
validateIntegrations(cfg, path, DEFAULT_SERVICES);
|
|
1266
1377
|
}
|
|
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
1378
|
function validateServices(services, path) {
|
|
1329
1379
|
for (const service of services) {
|
|
1330
1380
|
const definition = (0, import_apps.appServiceDefinition)(service);
|
|
@@ -1338,25 +1388,6 @@ function validateServices(services, path) {
|
|
|
1338
1388
|
}
|
|
1339
1389
|
}
|
|
1340
1390
|
}
|
|
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
1391
|
function validId2(value2) {
|
|
1361
1392
|
return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
|
|
1362
1393
|
}
|
|
@@ -1371,7 +1402,7 @@ async function loadConfigModule(path) {
|
|
|
1371
1402
|
function trimSlash(value2) {
|
|
1372
1403
|
return value2.replace(/\/+$/, "");
|
|
1373
1404
|
}
|
|
1374
|
-
function
|
|
1405
|
+
function unique3(values) {
|
|
1375
1406
|
return [...new Set(values.filter(Boolean))];
|
|
1376
1407
|
}
|
|
1377
1408
|
|
|
@@ -4196,6 +4227,67 @@ function isRecord7(value2) {
|
|
|
4196
4227
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
4197
4228
|
}
|
|
4198
4229
|
|
|
4230
|
+
// src/secret-contract.ts
|
|
4231
|
+
var APP_SOURCE = "app";
|
|
4232
|
+
function resolveSecretContract(cfg) {
|
|
4233
|
+
const byName = /* @__PURE__ */ new Map();
|
|
4234
|
+
const declarations = [
|
|
4235
|
+
...(cfg.secrets ?? []).map((secret) => ({ source: APP_SOURCE, secret })),
|
|
4236
|
+
...(cfg.integrations ?? []).flatMap(
|
|
4237
|
+
(integration) => (integration.secrets ?? []).map((secret) => ({ source: integration.id, secret }))
|
|
4238
|
+
)
|
|
4239
|
+
];
|
|
4240
|
+
for (const { source, secret } of declarations) {
|
|
4241
|
+
const existing = byName.get(secret.name);
|
|
4242
|
+
if (!existing) {
|
|
4243
|
+
byName.set(secret.name, { ...secret, required: secret.required !== false, sources: [source] });
|
|
4244
|
+
continue;
|
|
4245
|
+
}
|
|
4246
|
+
existing.sources.push(source);
|
|
4247
|
+
existing.required = existing.required || secret.required !== false;
|
|
4248
|
+
existing.pattern ??= secret.pattern;
|
|
4249
|
+
}
|
|
4250
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
4251
|
+
}
|
|
4252
|
+
function secretContractWarnings(contract, cfg) {
|
|
4253
|
+
const warnings = [];
|
|
4254
|
+
const declaredPatterns = /* @__PURE__ */ new Map();
|
|
4255
|
+
for (const integration of cfg.integrations ?? []) {
|
|
4256
|
+
for (const secret of integration.secrets ?? []) {
|
|
4257
|
+
if (!secret.pattern) continue;
|
|
4258
|
+
const seen = declaredPatterns.get(secret.name) ?? /* @__PURE__ */ new Map();
|
|
4259
|
+
seen.set(integration.id, secret.pattern);
|
|
4260
|
+
declaredPatterns.set(secret.name, seen);
|
|
4261
|
+
}
|
|
4262
|
+
}
|
|
4263
|
+
for (const secret of cfg.secrets ?? []) {
|
|
4264
|
+
if (!secret.pattern) continue;
|
|
4265
|
+
const seen = declaredPatterns.get(secret.name) ?? /* @__PURE__ */ new Map();
|
|
4266
|
+
seen.set(APP_SOURCE, secret.pattern);
|
|
4267
|
+
declaredPatterns.set(secret.name, seen);
|
|
4268
|
+
}
|
|
4269
|
+
for (const [name, seen] of declaredPatterns) {
|
|
4270
|
+
const distinct = [...new Set(seen.values())];
|
|
4271
|
+
if (distinct.length > 1) {
|
|
4272
|
+
const detail = [...seen].map(([source, pattern]) => `${source} expects "${pattern}"`).join(", ");
|
|
4273
|
+
warnings.push(`secret "${name}" has conflicting patterns \u2014 ${detail}; one of them will reject a valid value`);
|
|
4274
|
+
}
|
|
4275
|
+
}
|
|
4276
|
+
if (contract.length > 0 && !cfg.services.includes("db")) {
|
|
4277
|
+
const names = contract.map((secret) => secret.name).join(", ");
|
|
4278
|
+
warnings.push(`secrets are declared (${names}) but the db service is off \u2014 nothing can read the tenant vault`);
|
|
4279
|
+
}
|
|
4280
|
+
return warnings;
|
|
4281
|
+
}
|
|
4282
|
+
function formatSecretContract(contract) {
|
|
4283
|
+
return contract.map((secret) => {
|
|
4284
|
+
const flags = [secret.required ? "required" : "optional"];
|
|
4285
|
+
if (secret.reserved) flags.push("reserved");
|
|
4286
|
+
if (secret.pattern) flags.push(`${secret.pattern}\u2026`);
|
|
4287
|
+
return ` ${secret.name} (${flags.join(", ")}) \u2014 ${secret.sources.join(", ")}`;
|
|
4288
|
+
});
|
|
4289
|
+
}
|
|
4290
|
+
|
|
4199
4291
|
// src/doctor.ts
|
|
4200
4292
|
async function doctor(options) {
|
|
4201
4293
|
const out = options.stdout ?? console;
|
|
@@ -4212,6 +4304,9 @@ async function doctor(options) {
|
|
|
4212
4304
|
out.log(`schema: ${schema ? `${entities.length} entities` : "none"}`);
|
|
4213
4305
|
out.log(`rules: ${rules ? `${Object.keys(rules).length} namespaces` : "none"}`);
|
|
4214
4306
|
out.log(`ai: ${cfg.services.includes("ai") ? cfg.ai?.provider ? `byok/${cfg.ai.provider}` : "hosted" : "not enabled"}`);
|
|
4307
|
+
const contract = resolveSecretContract(cfg);
|
|
4308
|
+
out.log(`secrets: ${contract.length ? `${contract.length} declared` : "none declared"}`);
|
|
4309
|
+
for (const line of formatSecretContract(contract)) out.log(line);
|
|
4215
4310
|
if (cfg.services.includes("calendar")) {
|
|
4216
4311
|
const calendar = cfg.envs.map((env) => {
|
|
4217
4312
|
const resolved = calendarServiceConfig(cfg, env);
|
|
@@ -4232,6 +4327,7 @@ async function doctor(options) {
|
|
|
4232
4327
|
}
|
|
4233
4328
|
}
|
|
4234
4329
|
warnings.push(...integrationWarnings(database.integrations, schema, rules));
|
|
4330
|
+
warnings.push(...secretContractWarnings(contract, cfg));
|
|
4235
4331
|
if (cfg.services.includes("ai") && cfg.ai?.mode === "byok" && !cfg.ai.provider) {
|
|
4236
4332
|
warnings.push("ai.mode is byok but ai.provider is not set");
|
|
4237
4333
|
}
|
|
@@ -4558,6 +4654,71 @@ async function resolveVaultWrite(options) {
|
|
|
4558
4654
|
return { cfg, tenantId: (0, import_apps10.tenantIdFor)(cfg.app.id, env), value: value2, doFetch, out };
|
|
4559
4655
|
}
|
|
4560
4656
|
|
|
4657
|
+
// src/secrets-status.ts
|
|
4658
|
+
var import_apps11 = require("@odla-ai/apps");
|
|
4659
|
+
async function secretsStatus(options) {
|
|
4660
|
+
const out = options.stdout ?? console;
|
|
4661
|
+
const doFetch = options.fetch ?? fetch;
|
|
4662
|
+
const cfg = await loadProjectConfig(options.configPath);
|
|
4663
|
+
if (!cfg.envs.includes(options.env)) {
|
|
4664
|
+
throw new Error(`env "${options.env}" is not in config envs (${cfg.envs.join(", ")})`);
|
|
4665
|
+
}
|
|
4666
|
+
const tenantId = (0, import_apps11.tenantIdFor)(cfg.app.id, options.env);
|
|
4667
|
+
const contract = resolveSecretContract(cfg);
|
|
4668
|
+
const token = await getDeveloperToken(cfg, options, doFetch, out);
|
|
4669
|
+
const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/secrets`, {
|
|
4670
|
+
headers: { authorization: `Bearer ${token}` }
|
|
4671
|
+
});
|
|
4672
|
+
if (!res.ok) {
|
|
4673
|
+
const detail = (await res.text().catch(() => "")).slice(0, 300);
|
|
4674
|
+
throw new Error(`list secrets for ${tenantId} failed (${res.status}): ${detail || "request failed"}`);
|
|
4675
|
+
}
|
|
4676
|
+
const body = await res.json();
|
|
4677
|
+
const stored = new Set((body.secrets ?? []).map((entry) => String(entry.name)));
|
|
4678
|
+
const report4 = buildReport(cfg.app.id, options.env, tenantId, contract, stored);
|
|
4679
|
+
if (options.json) out.log(JSON.stringify(report4, null, 2));
|
|
4680
|
+
else printReport(report4, out);
|
|
4681
|
+
return report4;
|
|
4682
|
+
}
|
|
4683
|
+
function buildReport(appId, env, tenant, contract, stored) {
|
|
4684
|
+
const declared = new Set(contract.map((secret) => secret.name));
|
|
4685
|
+
const rows = contract.map((secret) => ({
|
|
4686
|
+
name: secret.name,
|
|
4687
|
+
state: secret.reserved ? "reserved" : stored.has(secret.name) ? "set" : "missing",
|
|
4688
|
+
required: secret.required,
|
|
4689
|
+
sources: secret.sources,
|
|
4690
|
+
description: secret.description
|
|
4691
|
+
}));
|
|
4692
|
+
for (const name of [...stored].sort()) {
|
|
4693
|
+
if (!declared.has(name)) rows.push({ name, state: "undeclared", required: false, sources: [] });
|
|
4694
|
+
}
|
|
4695
|
+
const ok = rows.every((row) => row.state !== "missing" || !row.required);
|
|
4696
|
+
return { app: appId, env, tenant, secrets: rows, ok };
|
|
4697
|
+
}
|
|
4698
|
+
function printReport(report4, out) {
|
|
4699
|
+
out.log(`${report4.app} (${report4.tenant})`);
|
|
4700
|
+
if (report4.secrets.length === 0) {
|
|
4701
|
+
out.log(" no secrets declared and none stored");
|
|
4702
|
+
return;
|
|
4703
|
+
}
|
|
4704
|
+
for (const row of report4.secrets) {
|
|
4705
|
+
const label = row.state === "missing" && !row.required ? "missing (optional)" : row.state;
|
|
4706
|
+
const suffix = row.sources.length ? ` \u2014 ${row.sources.join(", ")}` : "";
|
|
4707
|
+
out.log(` ${label.padEnd(18)} ${row.name}${suffix}`);
|
|
4708
|
+
}
|
|
4709
|
+
const missing = report4.secrets.filter((row) => row.state === "missing" && row.required);
|
|
4710
|
+
if (missing.length) {
|
|
4711
|
+
out.log("");
|
|
4712
|
+
for (const row of missing) {
|
|
4713
|
+
out.log(`${row.name} is required but not set \u2014 "odla-ai secrets set ${row.name} --env ${report4.env} --stdin"`);
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
if (report4.secrets.some((row) => row.state === "reserved")) {
|
|
4717
|
+
out.log("");
|
|
4718
|
+
out.log('"reserved" slots are never enumerated by the vault; presence cannot be confirmed here.');
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
|
|
4561
4722
|
// src/skill.ts
|
|
4562
4723
|
var import_node_fs13 = require("fs");
|
|
4563
4724
|
var import_node_os2 = require("os");
|
|
@@ -5027,9 +5188,22 @@ async function secretsCommand(parsed, deps) {
|
|
|
5027
5188
|
await (sub === "set" ? secretsSet(options) : secretsSetClerkKey(options));
|
|
5028
5189
|
return;
|
|
5029
5190
|
}
|
|
5191
|
+
if (sub === "status") {
|
|
5192
|
+
assertArgs(parsed, ["config", "env", "token", "email", "json"], 2);
|
|
5193
|
+
await secretsStatus({
|
|
5194
|
+
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
5195
|
+
env: requiredString(parsed.options.env, "--env"),
|
|
5196
|
+
json: parsed.options.json === true,
|
|
5197
|
+
token: stringOpt(parsed.options.token),
|
|
5198
|
+
email: stringOpt(parsed.options.email),
|
|
5199
|
+
fetch: deps.fetch,
|
|
5200
|
+
stdout: deps.stdout
|
|
5201
|
+
});
|
|
5202
|
+
return;
|
|
5203
|
+
}
|
|
5030
5204
|
if (sub !== "push") {
|
|
5031
5205
|
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".`
|
|
5206
|
+
`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
5207
|
);
|
|
5034
5208
|
}
|
|
5035
5209
|
assertArgs(parsed, ["config", "env", "dry-run", "yes"], 2);
|
|
@@ -5553,7 +5727,7 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
|
|
|
5553
5727
|
}
|
|
5554
5728
|
}
|
|
5555
5729
|
|
|
5556
|
-
// ../harness/dist/chunk-
|
|
5730
|
+
// ../harness/dist/chunk-ANNX7VGK.js
|
|
5557
5731
|
var import_crypto = require("crypto");
|
|
5558
5732
|
var import_promises5 = require("fs/promises");
|
|
5559
5733
|
var import_path5 = require("path");
|
|
@@ -5613,9 +5787,9 @@ function dependenciesOf(values, influence = "data") {
|
|
|
5613
5787
|
result.push({ ref, influence, promptSafetyAtUse: value2.label.promptSafety });
|
|
5614
5788
|
}
|
|
5615
5789
|
}
|
|
5616
|
-
const
|
|
5617
|
-
for (const dep of result)
|
|
5618
|
-
return [...
|
|
5790
|
+
const unique4 = /* @__PURE__ */ new Map();
|
|
5791
|
+
for (const dep of result) unique4.set(`${dep.ref.kind}\0${dep.ref.id}\0${dep.influence}\0${dep.promptSafetyAtUse}`, dep);
|
|
5792
|
+
return [...unique4.values()];
|
|
5619
5793
|
}
|
|
5620
5794
|
|
|
5621
5795
|
// ../camel/dist/chunk-4DQ6BIHP.js
|
|
@@ -5890,7 +6064,7 @@ function validateSnapshot(snapshot, limits) {
|
|
|
5890
6064
|
}
|
|
5891
6065
|
}
|
|
5892
6066
|
|
|
5893
|
-
// ../harness/dist/chunk-
|
|
6067
|
+
// ../harness/dist/chunk-ANNX7VGK.js
|
|
5894
6068
|
var import_child_process4 = require("child_process");
|
|
5895
6069
|
var import_promises6 = require("fs/promises");
|
|
5896
6070
|
var import_path6 = require("path");
|
|
@@ -6192,7 +6366,7 @@ function looksLikeDestination(value2) {
|
|
|
6192
6366
|
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text2) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text2);
|
|
6193
6367
|
}
|
|
6194
6368
|
|
|
6195
|
-
// ../harness/dist/chunk-
|
|
6369
|
+
// ../harness/dist/chunk-ANNX7VGK.js
|
|
6196
6370
|
var import_promises10 = require("fs/promises");
|
|
6197
6371
|
var import_promises11 = require("fs/promises");
|
|
6198
6372
|
var import_path10 = require("path");
|
|
@@ -6460,7 +6634,7 @@ async function buildCodeGraph(input) {
|
|
|
6460
6634
|
return builder.build();
|
|
6461
6635
|
}
|
|
6462
6636
|
|
|
6463
|
-
// ../harness/dist/chunk-
|
|
6637
|
+
// ../harness/dist/chunk-ANNX7VGK.js
|
|
6464
6638
|
var import_crypto4 = require("crypto");
|
|
6465
6639
|
async function digestStagedWorkspace(root, limits) {
|
|
6466
6640
|
const files = [];
|
|
@@ -6647,6 +6821,16 @@ function createCodeRuntimeControlClient(options) {
|
|
|
6647
6821
|
}
|
|
6648
6822
|
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
|
|
6649
6823
|
},
|
|
6824
|
+
recallMemories: async (sessionId, subjects, limit) => {
|
|
6825
|
+
const response2 = await call2(
|
|
6826
|
+
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/recall`,
|
|
6827
|
+
{ subjects: [...subjects], limit }
|
|
6828
|
+
);
|
|
6829
|
+
return Array.isArray(response2.memories) ? response2.memories : [];
|
|
6830
|
+
},
|
|
6831
|
+
rememberMemory: async (sessionId, memory) => {
|
|
6832
|
+
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/remember`, memory);
|
|
6833
|
+
},
|
|
6650
6834
|
reportSessionFailure: async (sessionId, message2) => {
|
|
6651
6835
|
if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
|
|
6652
6836
|
await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
|
|
@@ -8341,6 +8525,29 @@ function validateOptions(options) {
|
|
|
8341
8525
|
throw new TypeError("Code tool broker read-only prefix is invalid");
|
|
8342
8526
|
}
|
|
8343
8527
|
}
|
|
8528
|
+
var MAX_MEMORY_BODY = 4e3;
|
|
8529
|
+
function validateMemory(memory) {
|
|
8530
|
+
if (!memory.subject.includes(":")) {
|
|
8531
|
+
throw new TypeError(`memory subject must be a graph node id, got "${memory.subject}"`);
|
|
8532
|
+
}
|
|
8533
|
+
const body = memory.body.trim();
|
|
8534
|
+
if (!body) throw new TypeError("a memory needs a body");
|
|
8535
|
+
if (body.length > MAX_MEMORY_BODY) throw new TypeError("memory body exceeds its bound");
|
|
8536
|
+
if (!memory.authorId.trim()) throw new TypeError("a memory needs an author");
|
|
8537
|
+
}
|
|
8538
|
+
function hazardFromAttempt(input) {
|
|
8539
|
+
const body = [
|
|
8540
|
+
`Attempt ${input.attempt} at "${input.goal.slice(0, 200)}" failed its proof.`,
|
|
8541
|
+
input.feedback.replace(/\s+/g, " ").slice(0, MAX_MEMORY_BODY - 300)
|
|
8542
|
+
].join(" ");
|
|
8543
|
+
return input.touched.slice(0, 10).map((path) => ({
|
|
8544
|
+
subject: path.includes(":") ? path : `file:${path}`,
|
|
8545
|
+
kind: "hazard",
|
|
8546
|
+
body,
|
|
8547
|
+
evidence: { kind: "gate", ref: input.verificationId },
|
|
8548
|
+
authorId: input.authorId
|
|
8549
|
+
}));
|
|
8550
|
+
}
|
|
8344
8551
|
async function runGoal(spec, attempt) {
|
|
8345
8552
|
assertBudget(spec.budget);
|
|
8346
8553
|
const now = spec.now ?? Date.now;
|
|
@@ -8532,6 +8739,9 @@ function pursueRuntimeGoal(input) {
|
|
|
8532
8739
|
return { gatePassed: false, feedback: "", tokens: outcome.tokens, error: outcome.error };
|
|
8533
8740
|
}
|
|
8534
8741
|
const verdict = await input.gate(attempt);
|
|
8742
|
+
if (!verdict.passed && input.memory) {
|
|
8743
|
+
await rememberFailure(input, attempt, verdict.feedback);
|
|
8744
|
+
}
|
|
8535
8745
|
return {
|
|
8536
8746
|
gatePassed: verdict.passed,
|
|
8537
8747
|
feedback: verdict.feedback,
|
|
@@ -8542,6 +8752,25 @@ function pursueRuntimeGoal(input) {
|
|
|
8542
8752
|
}
|
|
8543
8753
|
);
|
|
8544
8754
|
}
|
|
8755
|
+
async function rememberFailure(input, attempt, feedback) {
|
|
8756
|
+
if (!input.memory || !feedback.trim()) return;
|
|
8757
|
+
try {
|
|
8758
|
+
const touched = await input.touched?.(attempt) ?? [];
|
|
8759
|
+
if (touched.length === 0) return;
|
|
8760
|
+
for (const memory of hazardFromAttempt({
|
|
8761
|
+
goal: input.spec.goal,
|
|
8762
|
+
attempt,
|
|
8763
|
+
feedback,
|
|
8764
|
+
touched,
|
|
8765
|
+
verificationId: `goal-${attempt}`,
|
|
8766
|
+
authorId: input.memory.authorId
|
|
8767
|
+
})) {
|
|
8768
|
+
validateMemory(memory);
|
|
8769
|
+
await input.memory.store.remember(memory);
|
|
8770
|
+
}
|
|
8771
|
+
} catch {
|
|
8772
|
+
}
|
|
8773
|
+
}
|
|
8545
8774
|
function goalEventLine(event) {
|
|
8546
8775
|
if (event.type === "attempt_started") return `Goal attempt ${event.attempt} starting.`;
|
|
8547
8776
|
if (event.type === "attempt_failed") return `Attempt ${event.attempt} did not satisfy the proof.`;
|
|
@@ -9806,7 +10035,9 @@ Commands:
|
|
|
9806
10035
|
copilot, gemini, or agents (repeatable or comma-separated).
|
|
9807
10036
|
secrets Push configured db/o11y secrets into the Worker via wrangler
|
|
9808
10037
|
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
|
|
10038
|
+
reserved Clerk secret key, write-only from stdin or an env var;
|
|
10039
|
+
status compares the secrets the config declares against the
|
|
10040
|
+
names the environment's vault holds (--json for a report).
|
|
9810
10041
|
version Print the CLI version.
|
|
9811
10042
|
|
|
9812
10043
|
Safety:
|
|
@@ -11789,7 +12020,7 @@ async function read2(url, headers, doFetch) {
|
|
|
11789
12020
|
}
|
|
11790
12021
|
|
|
11791
12022
|
// src/provision.ts
|
|
11792
|
-
var
|
|
12023
|
+
var import_apps13 = require("@odla-ai/apps");
|
|
11793
12024
|
var import_ai5 = require("@odla-ai/ai");
|
|
11794
12025
|
var import_node_process12 = __toESM(require("process"), 1);
|
|
11795
12026
|
|
|
@@ -11846,9 +12077,9 @@ async function responseText(res) {
|
|
|
11846
12077
|
}
|
|
11847
12078
|
|
|
11848
12079
|
// src/provision-credentials.ts
|
|
11849
|
-
var
|
|
12080
|
+
var import_apps12 = require("@odla-ai/apps");
|
|
11850
12081
|
async function provisionEnvCredentials(opts) {
|
|
11851
|
-
const tenantId = (0,
|
|
12082
|
+
const tenantId = (0, import_apps12.tenantIdFor)(opts.cfg.app.id, opts.env);
|
|
11852
12083
|
const prior = opts.credentials?.envs[opts.env];
|
|
11853
12084
|
let credentials = opts.credentials;
|
|
11854
12085
|
let dbKey = opts.cfg.services.includes("db") && !opts.rotateDb ? prior?.dbKey : void 0;
|
|
@@ -12121,7 +12352,7 @@ async function provision(options) {
|
|
|
12121
12352
|
optionalProjectCapabilities: ["app.manage"],
|
|
12122
12353
|
forceReview: options.requestGrant
|
|
12123
12354
|
});
|
|
12124
|
-
const apps = (0,
|
|
12355
|
+
const apps = (0, import_apps13.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
12125
12356
|
const existing = await apps.resolveApp(cfg.app.id);
|
|
12126
12357
|
if (existing) {
|
|
12127
12358
|
out.log(`app: ${cfg.app.id} already exists`);
|
|
@@ -12133,7 +12364,7 @@ async function provision(options) {
|
|
|
12133
12364
|
try {
|
|
12134
12365
|
await apps.createApp({ name: cfg.app.name, appId: cfg.app.id });
|
|
12135
12366
|
} catch (error) {
|
|
12136
|
-
if (error instanceof
|
|
12367
|
+
if (error instanceof import_apps13.AppsError && error.status === 403) {
|
|
12137
12368
|
throw new Error(
|
|
12138
12369
|
`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
12370
|
{ cause: error }
|
|
@@ -12146,7 +12377,7 @@ async function provision(options) {
|
|
|
12146
12377
|
for (const env of cfg.envs) {
|
|
12147
12378
|
await assertTenantAdminAccess(doFetch, cfg, env, token);
|
|
12148
12379
|
}
|
|
12149
|
-
const serviceOrder = (0,
|
|
12380
|
+
const serviceOrder = (0, import_apps13.orderAppServices)(cfg.services);
|
|
12150
12381
|
for (const env of cfg.envs) {
|
|
12151
12382
|
for (const service of serviceOrder) {
|
|
12152
12383
|
if (service === "ai") {
|
|
@@ -12180,7 +12411,7 @@ async function provision(options) {
|
|
|
12180
12411
|
}
|
|
12181
12412
|
let devVarsCredentials = credentials;
|
|
12182
12413
|
for (const env of cfg.envs) {
|
|
12183
|
-
const tenantId = (0,
|
|
12414
|
+
const tenantId = (0, import_apps13.tenantIdFor)(cfg.app.id, env);
|
|
12184
12415
|
let dbKey;
|
|
12185
12416
|
if (options.pushSecrets) {
|
|
12186
12417
|
const delivered = await deliverRuntimeCredentials(cfg, {
|
|
@@ -12382,7 +12613,7 @@ var COMMAND_SURFACE = {
|
|
|
12382
12613
|
rm: {},
|
|
12383
12614
|
lint: {}
|
|
12384
12615
|
},
|
|
12385
|
-
secrets: { push: {}, set: {}, "set-clerk-key": {} },
|
|
12616
|
+
secrets: { push: {}, status: {}, set: {}, "set-clerk-key": {} },
|
|
12386
12617
|
security: {
|
|
12387
12618
|
plan: {},
|
|
12388
12619
|
sources: {},
|