@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 CHANGED
@@ -1268,6 +1268,117 @@ var init_ai_config_validation = __esm({
1268
1268
  }
1269
1269
  });
1270
1270
 
1271
+ // src/calendar-config.ts
1272
+ function calendarServiceConfig(cfg, env) {
1273
+ if (!cfg.services.includes("calendar")) throw new Error("calendar service is not enabled in config services");
1274
+ if (!cfg.envs.includes(env) && env !== "prod") throw new Error(`calendar env "${env}" is not declared in config envs`);
1275
+ const google = cfg.calendar?.google;
1276
+ if (!google) throw new Error("calendar.google is required when the calendar service is enabled");
1277
+ const configured = google.availabilityCalendars?.[env] ?? google.calendars?.[env];
1278
+ if (!configured?.length) throw new Error(`calendar.google.availabilityCalendars.${env} is required`);
1279
+ const availability = unique(configured.map((id) => id.trim()));
1280
+ return {
1281
+ provider: "google",
1282
+ access: "book",
1283
+ bookingCalendarId: google.bookingCalendar?.[env]?.trim() ?? availability[0],
1284
+ availabilityCalendars: availability
1285
+ };
1286
+ }
1287
+ function calendarBookingPageUrl(cfg, env) {
1288
+ const value2 = cfg.calendar?.google.bookingPageUrl?.[env];
1289
+ if (value2 === void 0 || value2 === null) return value2;
1290
+ return new URL(value2).toString();
1291
+ }
1292
+ function validateCalendarConfig(cfg, envs, services, path) {
1293
+ const enabled = services.includes("calendar");
1294
+ if (!cfg.calendar) {
1295
+ if (enabled) throw new Error(`${path}: calendar.google is required when services includes "calendar"`);
1296
+ return;
1297
+ }
1298
+ if (!isRecord5(cfg.calendar)) throw new Error(`${path}: calendar must be an object`);
1299
+ assertOnly2(cfg.calendar, ["google"], `${path}: calendar`);
1300
+ if (!isRecord5(cfg.calendar.google)) throw new Error(`${path}: calendar.google must be an object`);
1301
+ const google = cfg.calendar.google;
1302
+ assertOnly2(
1303
+ google,
1304
+ ["availabilityCalendars", "calendars", "bookingCalendar", "bookingPageUrl"],
1305
+ `${path}: calendar.google`
1306
+ );
1307
+ const availabilityKey = google.availabilityCalendars !== void 0 ? "availabilityCalendars" : google.calendars !== void 0 ? "calendars" : null;
1308
+ if (!availabilityKey || google.availabilityCalendars !== void 0 && google.calendars !== void 0) {
1309
+ throw new Error(`${path}: calendar.google requires exactly one of availabilityCalendars or calendars (legacy)`);
1310
+ }
1311
+ const availability = google[availabilityKey];
1312
+ if (!isRecord5(availability)) throw new Error(`${path}: calendar.google.${availabilityKey} must map env names to calendar ids`);
1313
+ const unknownEnv = Object.keys(availability).find((env) => !envs.includes(env) && env !== "prod");
1314
+ if (unknownEnv) throw new Error(`${path}: calendar.google.${availabilityKey}.${unknownEnv} is not in config envs`);
1315
+ for (const env of envs) {
1316
+ const ids = availability[env];
1317
+ if (!Array.isArray(ids) || ids.length === 0) {
1318
+ throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
1319
+ }
1320
+ }
1321
+ for (const [env, ids] of Object.entries(availability)) {
1322
+ if (!Array.isArray(ids) || ids.length === 0) {
1323
+ throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
1324
+ }
1325
+ if (ids.length > 10) {
1326
+ throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must contain at most 10 calendar ids`);
1327
+ }
1328
+ if (ids.some((id) => !safeText2(id, 1024))) {
1329
+ throw new Error(`${path}: calendar.google.${availabilityKey}.${env} contains an invalid calendar id`);
1330
+ }
1331
+ }
1332
+ if (google.bookingCalendar !== void 0) {
1333
+ if (!isRecord5(google.bookingCalendar)) throw new Error(`${path}: calendar.google.bookingCalendar must map env names to one calendar id`);
1334
+ const unknownBookingEnv = Object.keys(google.bookingCalendar).find((env) => !envs.includes(env) && env !== "prod");
1335
+ if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingCalendar.${unknownBookingEnv} is not in config envs`);
1336
+ for (const [env, value2] of Object.entries(google.bookingCalendar)) {
1337
+ if (!safeText2(value2, 1024)) {
1338
+ throw new Error(`${path}: calendar.google.bookingCalendar.${env} must be a calendar id`);
1339
+ }
1340
+ }
1341
+ }
1342
+ if (google.bookingPageUrl !== void 0) {
1343
+ if (!isRecord5(google.bookingPageUrl)) throw new Error(`${path}: calendar.google.bookingPageUrl must map env names to HTTPS URLs or null`);
1344
+ const unknownBookingEnv = Object.keys(google.bookingPageUrl).find((env) => !envs.includes(env) && env !== "prod");
1345
+ if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingPageUrl.${unknownBookingEnv} is not in config envs`);
1346
+ for (const [env, value2] of Object.entries(google.bookingPageUrl)) {
1347
+ if (value2 !== null && !safeHttpsUrl(value2)) {
1348
+ throw new Error(`${path}: calendar.google.bookingPageUrl.${env} must be an HTTPS URL without credentials or fragment`);
1349
+ }
1350
+ }
1351
+ }
1352
+ }
1353
+ function assertOnly2(value2, allowed, label) {
1354
+ const extra = Object.keys(value2).find((key) => !allowed.includes(key));
1355
+ if (extra) throw new Error(`${label}.${extra} is not supported`);
1356
+ }
1357
+ function isRecord5(value2) {
1358
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
1359
+ }
1360
+ function safeText2(value2, max) {
1361
+ return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
1362
+ }
1363
+ function safeHttpsUrl(value2) {
1364
+ if (typeof value2 !== "string" || value2.length > 2048) return false;
1365
+ try {
1366
+ const url = new URL(value2);
1367
+ return url.protocol === "https:" && !url.username && !url.password && !url.hash;
1368
+ } catch {
1369
+ return false;
1370
+ }
1371
+ }
1372
+ function unique(values) {
1373
+ return [...new Set(values.filter(Boolean))];
1374
+ }
1375
+ var init_calendar_config = __esm({
1376
+ "src/calendar-config.ts"() {
1377
+ "use strict";
1378
+ init_cjs_shims();
1379
+ }
1380
+ });
1381
+
1271
1382
  // src/integration-validation.ts
1272
1383
  function validateIntegrations(cfg, path, defaultServices) {
1273
1384
  if (cfg.integrations === void 0) return;
@@ -1275,36 +1386,60 @@ function validateIntegrations(cfg, path, defaultServices) {
1275
1386
  const ids = /* @__PURE__ */ new Set();
1276
1387
  for (const [index, integration] of cfg.integrations.entries()) {
1277
1388
  const at = `${path}: integrations[${index}]`;
1278
- if (!isRecord5(integration)) throw new Error(`${at} must be an object`);
1389
+ if (!isRecord6(integration)) throw new Error(`${at} must be an object`);
1279
1390
  if (!validId(integration.id)) throw new Error(`${at}.id must be lowercase letters, numbers, and hyphens`);
1280
1391
  if (ids.has(integration.id)) throw new Error(`${path}: duplicate integration id "${integration.id}"`);
1281
1392
  ids.add(integration.id);
1282
- if (!safeText2(integration.title, 200)) throw new Error(`${at}.title is required`);
1283
- if (!safeText2(integration.npm, 200)) throw new Error(`${at}.npm is required`);
1284
- if (integration.schema !== void 0 && (!isRecord5(integration.schema) || !isRecord5(integration.schema.entities))) {
1393
+ if (!safeText3(integration.title, 200)) throw new Error(`${at}.title is required`);
1394
+ if (!safeText3(integration.npm, 200)) throw new Error(`${at}.npm is required`);
1395
+ if (integration.schema !== void 0 && (!isRecord6(integration.schema) || !isRecord6(integration.schema.entities))) {
1285
1396
  throw new Error(`${at}.schema must contain an entities object`);
1286
1397
  }
1287
- if (integration.rules !== void 0 && !isRecord5(integration.rules)) throw new Error(`${at}.rules must be an object`);
1398
+ if (integration.rules !== void 0 && !isRecord6(integration.rules)) throw new Error(`${at}.rules must be an object`);
1288
1399
  validateSeeds(integration, at);
1289
1400
  validateProbes(integration, at);
1401
+ validateSecrets(integration.secrets, at);
1290
1402
  }
1291
1403
  const needsDb = cfg.integrations.some((integration) => integration.schema || integration.rules || integration.seeds?.length);
1292
- const services = unique(cfg.services?.length ? cfg.services : defaultServices);
1404
+ const services = unique2(cfg.services?.length ? cfg.services : defaultServices);
1293
1405
  if (needsDb && !services.includes("db")) throw new Error(`${path}: schema/rules/seed integrations require the db service`);
1294
1406
  }
1407
+ function validateSecrets(value2, at) {
1408
+ if (value2 === void 0) return;
1409
+ if (!Array.isArray(value2)) throw new Error(`${at}.secrets must be an array`);
1410
+ const names = /* @__PURE__ */ new Set();
1411
+ for (const [index, secret] of value2.entries()) {
1412
+ const sat = `${at}.secrets[${index}]`;
1413
+ if (!isRecord6(secret)) throw new Error(`${sat} must be an object`);
1414
+ if (typeof secret.name !== "string" || !SECRET_NAME.test(secret.name) || secret.name.length > 64) {
1415
+ throw new Error(`${sat}.name must be lowercase snake_case (optionally "$"-prefixed when reserved), e.g. "clerk_webhook_secret"`);
1416
+ }
1417
+ const dollar = secret.name.startsWith("$");
1418
+ if (dollar !== (secret.reserved === true)) {
1419
+ throw new Error(
1420
+ dollar ? `${sat}.name is "$"-prefixed, so it must also set reserved: true` : `${sat} sets reserved: true, so its name must be "$"-prefixed`
1421
+ );
1422
+ }
1423
+ if (!safeText3(secret.description, 500)) throw new Error(`${sat}.description is required \u2014 it is what doctor and the docs show`);
1424
+ if (secret.pattern !== void 0 && !safeText3(secret.pattern, 64)) throw new Error(`${sat}.pattern must be a non-empty prefix string`);
1425
+ if (secret.required !== void 0 && typeof secret.required !== "boolean") throw new Error(`${sat}.required must be a boolean`);
1426
+ if (names.has(secret.name)) throw new Error(`${at} declares secret "${secret.name}" twice`);
1427
+ names.add(secret.name);
1428
+ }
1429
+ }
1295
1430
  function validateSeeds(integration, at) {
1296
1431
  if (integration.seeds === void 0) return;
1297
1432
  if (!Array.isArray(integration.seeds)) throw new Error(`${at}.seeds must be an array`);
1298
1433
  const ids = /* @__PURE__ */ new Set();
1299
1434
  for (const [index, seed] of integration.seeds.entries()) {
1300
1435
  const sat = `${at}.seeds[${index}]`;
1301
- if (!isRecord5(seed) || !safeText2(seed.id, 200) || !safeText2(seed.ns, 200)) throw new Error(`${sat} requires id and ns`);
1436
+ if (!isRecord6(seed) || !safeText3(seed.id, 200) || !safeText3(seed.ns, 200)) throw new Error(`${sat} requires id and ns`);
1302
1437
  if (ids.has(seed.id)) throw new Error(`${at} has duplicate seed id "${seed.id}"`);
1303
1438
  ids.add(seed.id);
1304
- if (!isRecord5(seed.key) || !safeText2(seed.key.attr, 200) || !safeText2(seed.key.value, 2048)) {
1439
+ if (!isRecord6(seed.key) || !safeText3(seed.key.attr, 200) || !safeText3(seed.key.value, 2048)) {
1305
1440
  throw new Error(`${sat}.key requires string attr and value`);
1306
1441
  }
1307
- if (!isRecord5(seed.attrs)) throw new Error(`${sat}.attrs must be an object`);
1442
+ if (!isRecord6(seed.attrs)) throw new Error(`${sat}.attrs must be an object`);
1308
1443
  if (Object.hasOwn(seed.attrs, seed.key.attr) && seed.attrs[seed.key.attr] !== seed.key.value) {
1309
1444
  throw new Error(`${sat}.attrs.${seed.key.attr} conflicts with its natural key`);
1310
1445
  }
@@ -1315,16 +1450,16 @@ function validateProbes(integration, at) {
1315
1450
  if (!Array.isArray(integration.probes)) throw new Error(`${at}.probes must be an array`);
1316
1451
  for (const [index, probe] of integration.probes.entries()) {
1317
1452
  const pat = `${at}.probes[${index}]`;
1318
- if (!isRecord5(probe) || !safeProbePath(probe.path)) throw new Error(`${pat}.path must be an absolute path without query or fragment`);
1453
+ if (!isRecord6(probe) || !safeProbePath(probe.path)) throw new Error(`${pat}.path must be an absolute path without query or fragment`);
1319
1454
  if (!Number.isInteger(probe.expectedStatus) || probe.expectedStatus < 100 || probe.expectedStatus > 599) {
1320
1455
  throw new Error(`${pat}.expectedStatus must be an HTTP status`);
1321
1456
  }
1322
1457
  }
1323
1458
  }
1324
- function isRecord5(value2) {
1459
+ function isRecord6(value2) {
1325
1460
  return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
1326
1461
  }
1327
- function safeText2(value2, max) {
1462
+ function safeText3(value2, max) {
1328
1463
  return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
1329
1464
  }
1330
1465
  function safeProbePath(value2) {
@@ -1333,13 +1468,15 @@ function safeProbePath(value2) {
1333
1468
  function validId(value2) {
1334
1469
  return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
1335
1470
  }
1336
- function unique(values) {
1471
+ function unique2(values) {
1337
1472
  return [...new Set(values.filter(Boolean))];
1338
1473
  }
1474
+ var SECRET_NAME;
1339
1475
  var init_integration_validation = __esm({
1340
1476
  "src/integration-validation.ts"() {
1341
1477
  "use strict";
1342
1478
  init_cjs_shims();
1479
+ SECRET_NAME = /^\$?[a-z][a-z0-9_]*$/;
1343
1480
  }
1344
1481
  });
1345
1482
 
@@ -1354,10 +1491,10 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
1354
1491
  validateRawConfig(raw, resolved);
1355
1492
  const platformUrl = trimSlash(process.env.ODLA_PLATFORM_URL || raw.platformUrl || DEFAULT_PLATFORM);
1356
1493
  const dbEndpoint = trimSlash(process.env.ODLA_DB_ENDPOINT || raw.dbEndpoint || platformUrl);
1357
- const envs = unique2(raw.envs?.length ? raw.envs : DEFAULT_ENVS);
1358
- const services = unique2(raw.services?.length ? raw.services : DEFAULT_SERVICES);
1494
+ const envs = unique3(raw.envs?.length ? raw.envs : DEFAULT_ENVS);
1495
+ const services = unique3(raw.services?.length ? raw.services : DEFAULT_SERVICES);
1359
1496
  validateServices(services, resolved);
1360
- validateCalendarConfig(raw, unique2([...envs, ...options.additionalEnvs ?? []]), services, resolved);
1497
+ validateCalendarConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
1361
1498
  const local = {
1362
1499
  tokenFile: (0, import_node_path5.resolve)(rootDir, raw.local?.tokenFile ?? ".odla/dev-token.json"),
1363
1500
  credentialsFile: (0, import_node_path5.resolve)(rootDir, raw.local?.credentialsFile ?? ".odla/credentials.local.json"),
@@ -1405,26 +1542,6 @@ function buildPlan(cfg) {
1405
1542
  aiProvider: cfg.ai?.provider
1406
1543
  };
1407
1544
  }
1408
- function calendarServiceConfig(cfg, env) {
1409
- if (!cfg.services.includes("calendar")) throw new Error("calendar service is not enabled in config services");
1410
- if (!cfg.envs.includes(env) && env !== "prod") throw new Error(`calendar env "${env}" is not declared in config envs`);
1411
- const google = cfg.calendar?.google;
1412
- if (!google) throw new Error("calendar.google is required when the calendar service is enabled");
1413
- const configured = google.availabilityCalendars?.[env] ?? google.calendars?.[env];
1414
- if (!configured?.length) throw new Error(`calendar.google.availabilityCalendars.${env} is required`);
1415
- const availability = unique2(configured.map((id) => id.trim()));
1416
- return {
1417
- provider: "google",
1418
- access: "book",
1419
- bookingCalendarId: google.bookingCalendar?.[env]?.trim() ?? availability[0],
1420
- availabilityCalendars: availability
1421
- };
1422
- }
1423
- function calendarBookingPageUrl(cfg, env) {
1424
- const value2 = cfg.calendar?.google.bookingPageUrl?.[env];
1425
- if (value2 === void 0 || value2 === null) return value2;
1426
- return new URL(value2).toString();
1427
- }
1428
1545
  function rulesFromSchema(schema) {
1429
1546
  const entities = serializedEntities(schema);
1430
1547
  return Object.fromEntries(
@@ -1456,69 +1573,9 @@ function validateRawConfig(raw, path) {
1456
1573
  throw new Error(`${path}: services must be an array of non-empty names`);
1457
1574
  }
1458
1575
  validateAiConfig(cfg, path);
1576
+ validateSecrets(cfg.secrets, `${path}: config`);
1459
1577
  validateIntegrations(cfg, path, DEFAULT_SERVICES);
1460
1578
  }
1461
- function validateCalendarConfig(cfg, envs, services, path) {
1462
- const enabled = services.includes("calendar");
1463
- if (!cfg.calendar) {
1464
- if (enabled) throw new Error(`${path}: calendar.google is required when services includes "calendar"`);
1465
- return;
1466
- }
1467
- if (!isRecord6(cfg.calendar)) throw new Error(`${path}: calendar must be an object`);
1468
- assertOnly2(cfg.calendar, ["google"], `${path}: calendar`);
1469
- if (!isRecord6(cfg.calendar.google)) throw new Error(`${path}: calendar.google must be an object`);
1470
- const google = cfg.calendar.google;
1471
- assertOnly2(
1472
- google,
1473
- ["availabilityCalendars", "calendars", "bookingCalendar", "bookingPageUrl"],
1474
- `${path}: calendar.google`
1475
- );
1476
- const availabilityKey = google.availabilityCalendars !== void 0 ? "availabilityCalendars" : google.calendars !== void 0 ? "calendars" : null;
1477
- if (!availabilityKey || google.availabilityCalendars !== void 0 && google.calendars !== void 0) {
1478
- throw new Error(`${path}: calendar.google requires exactly one of availabilityCalendars or calendars (legacy)`);
1479
- }
1480
- const availability = google[availabilityKey];
1481
- if (!isRecord6(availability)) throw new Error(`${path}: calendar.google.${availabilityKey} must map env names to calendar ids`);
1482
- const unknownEnv = Object.keys(availability).find((env) => !envs.includes(env) && env !== "prod");
1483
- if (unknownEnv) throw new Error(`${path}: calendar.google.${availabilityKey}.${unknownEnv} is not in config envs`);
1484
- for (const env of envs) {
1485
- const ids = availability[env];
1486
- if (!Array.isArray(ids) || ids.length === 0) {
1487
- throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
1488
- }
1489
- }
1490
- for (const [env, ids] of Object.entries(availability)) {
1491
- if (!Array.isArray(ids) || ids.length === 0) {
1492
- throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
1493
- }
1494
- if (ids.length > 10) {
1495
- throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must contain at most 10 calendar ids`);
1496
- }
1497
- if (ids.some((id) => !safeText3(id, 1024))) {
1498
- throw new Error(`${path}: calendar.google.${availabilityKey}.${env} contains an invalid calendar id`);
1499
- }
1500
- }
1501
- if (google.bookingCalendar !== void 0) {
1502
- if (!isRecord6(google.bookingCalendar)) throw new Error(`${path}: calendar.google.bookingCalendar must map env names to one calendar id`);
1503
- const unknownBookingEnv = Object.keys(google.bookingCalendar).find((env) => !envs.includes(env) && env !== "prod");
1504
- if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingCalendar.${unknownBookingEnv} is not in config envs`);
1505
- for (const [env, value2] of Object.entries(google.bookingCalendar)) {
1506
- if (!safeText3(value2, 1024)) {
1507
- throw new Error(`${path}: calendar.google.bookingCalendar.${env} must be a calendar id`);
1508
- }
1509
- }
1510
- }
1511
- if (google.bookingPageUrl !== void 0) {
1512
- if (!isRecord6(google.bookingPageUrl)) throw new Error(`${path}: calendar.google.bookingPageUrl must map env names to HTTPS URLs or null`);
1513
- const unknownBookingEnv = Object.keys(google.bookingPageUrl).find((env) => !envs.includes(env) && env !== "prod");
1514
- if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingPageUrl.${unknownBookingEnv} is not in config envs`);
1515
- for (const [env, value2] of Object.entries(google.bookingPageUrl)) {
1516
- if (value2 !== null && !safeHttpsUrl(value2)) {
1517
- throw new Error(`${path}: calendar.google.bookingPageUrl.${env} must be an HTTPS URL without credentials or fragment`);
1518
- }
1519
- }
1520
- }
1521
- }
1522
1579
  function validateServices(services, path) {
1523
1580
  for (const service of services) {
1524
1581
  const definition = (0, import_apps.appServiceDefinition)(service);
@@ -1532,25 +1589,6 @@ function validateServices(services, path) {
1532
1589
  }
1533
1590
  }
1534
1591
  }
1535
- function assertOnly2(value2, allowed, label) {
1536
- const extra = Object.keys(value2).find((key) => !allowed.includes(key));
1537
- if (extra) throw new Error(`${label}.${extra} is not supported`);
1538
- }
1539
- function isRecord6(value2) {
1540
- return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
1541
- }
1542
- function safeText3(value2, max) {
1543
- return typeof value2 === "string" && value2.trim().length > 0 && value2.length <= max && !/[\u0000-\u001f\u007f]/.test(value2);
1544
- }
1545
- function safeHttpsUrl(value2) {
1546
- if (typeof value2 !== "string" || value2.length > 2048) return false;
1547
- try {
1548
- const url = new URL(value2);
1549
- return url.protocol === "https:" && !url.username && !url.password && !url.hash;
1550
- } catch {
1551
- return false;
1552
- }
1553
- }
1554
1592
  function validId2(value2) {
1555
1593
  return typeof value2 === "string" && /^[a-z0-9][a-z0-9-]*$/.test(value2);
1556
1594
  }
@@ -1565,7 +1603,7 @@ async function loadConfigModule(path) {
1565
1603
  function trimSlash(value2) {
1566
1604
  return value2.replace(/\/+$/, "");
1567
1605
  }
1568
- function unique2(values) {
1606
+ function unique3(values) {
1569
1607
  return [...new Set(values.filter(Boolean))];
1570
1608
  }
1571
1609
  var import_node_fs7, import_node_path5, import_node_url, import_apps, DEFAULT_PLATFORM, DEFAULT_ENVS, DEFAULT_SERVICES, configImportSerial, GOOGLE_CALENDAR_EVENTS_SCOPE;
@@ -1578,7 +1616,9 @@ var init_config = __esm({
1578
1616
  import_node_url = require("url");
1579
1617
  import_apps = require("@odla-ai/apps");
1580
1618
  init_ai_config_validation();
1619
+ init_calendar_config();
1581
1620
  init_integration_validation();
1621
+ init_calendar_config();
1582
1622
  DEFAULT_PLATFORM = "https://odla.ai";
1583
1623
  DEFAULT_ENVS = ["dev"];
1584
1624
  DEFAULT_SERVICES = ["db", "ai"];
@@ -4661,6 +4701,74 @@ var init_integrations = __esm({
4661
4701
  }
4662
4702
  });
4663
4703
 
4704
+ // src/secret-contract.ts
4705
+ function resolveSecretContract(cfg) {
4706
+ const byName = /* @__PURE__ */ new Map();
4707
+ const declarations = [
4708
+ ...(cfg.secrets ?? []).map((secret) => ({ source: APP_SOURCE, secret })),
4709
+ ...(cfg.integrations ?? []).flatMap(
4710
+ (integration) => (integration.secrets ?? []).map((secret) => ({ source: integration.id, secret }))
4711
+ )
4712
+ ];
4713
+ for (const { source, secret } of declarations) {
4714
+ const existing = byName.get(secret.name);
4715
+ if (!existing) {
4716
+ byName.set(secret.name, { ...secret, required: secret.required !== false, sources: [source] });
4717
+ continue;
4718
+ }
4719
+ existing.sources.push(source);
4720
+ existing.required = existing.required || secret.required !== false;
4721
+ existing.pattern ??= secret.pattern;
4722
+ }
4723
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
4724
+ }
4725
+ function secretContractWarnings(contract, cfg) {
4726
+ const warnings = [];
4727
+ const declaredPatterns = /* @__PURE__ */ new Map();
4728
+ for (const integration of cfg.integrations ?? []) {
4729
+ for (const secret of integration.secrets ?? []) {
4730
+ if (!secret.pattern) continue;
4731
+ const seen = declaredPatterns.get(secret.name) ?? /* @__PURE__ */ new Map();
4732
+ seen.set(integration.id, secret.pattern);
4733
+ declaredPatterns.set(secret.name, seen);
4734
+ }
4735
+ }
4736
+ for (const secret of cfg.secrets ?? []) {
4737
+ if (!secret.pattern) continue;
4738
+ const seen = declaredPatterns.get(secret.name) ?? /* @__PURE__ */ new Map();
4739
+ seen.set(APP_SOURCE, secret.pattern);
4740
+ declaredPatterns.set(secret.name, seen);
4741
+ }
4742
+ for (const [name, seen] of declaredPatterns) {
4743
+ const distinct = [...new Set(seen.values())];
4744
+ if (distinct.length > 1) {
4745
+ const detail = [...seen].map(([source, pattern]) => `${source} expects "${pattern}"`).join(", ");
4746
+ warnings.push(`secret "${name}" has conflicting patterns \u2014 ${detail}; one of them will reject a valid value`);
4747
+ }
4748
+ }
4749
+ if (contract.length > 0 && !cfg.services.includes("db")) {
4750
+ const names = contract.map((secret) => secret.name).join(", ");
4751
+ warnings.push(`secrets are declared (${names}) but the db service is off \u2014 nothing can read the tenant vault`);
4752
+ }
4753
+ return warnings;
4754
+ }
4755
+ function formatSecretContract(contract) {
4756
+ return contract.map((secret) => {
4757
+ const flags = [secret.required ? "required" : "optional"];
4758
+ if (secret.reserved) flags.push("reserved");
4759
+ if (secret.pattern) flags.push(`${secret.pattern}\u2026`);
4760
+ return ` ${secret.name} (${flags.join(", ")}) \u2014 ${secret.sources.join(", ")}`;
4761
+ });
4762
+ }
4763
+ var APP_SOURCE;
4764
+ var init_secret_contract = __esm({
4765
+ "src/secret-contract.ts"() {
4766
+ "use strict";
4767
+ init_cjs_shims();
4768
+ APP_SOURCE = "app";
4769
+ }
4770
+ });
4771
+
4664
4772
  // src/doctor.ts
4665
4773
  async function doctor(options) {
4666
4774
  const out = options.stdout ?? console;
@@ -4677,6 +4785,9 @@ async function doctor(options) {
4677
4785
  out.log(`schema: ${schema ? `${entities.length} entities` : "none"}`);
4678
4786
  out.log(`rules: ${rules ? `${Object.keys(rules).length} namespaces` : "none"}`);
4679
4787
  out.log(`ai: ${cfg.services.includes("ai") ? cfg.ai?.provider ? `byok/${cfg.ai.provider}` : "hosted" : "not enabled"}`);
4788
+ const contract = resolveSecretContract(cfg);
4789
+ out.log(`secrets: ${contract.length ? `${contract.length} declared` : "none declared"}`);
4790
+ for (const line of formatSecretContract(contract)) out.log(line);
4680
4791
  if (cfg.services.includes("calendar")) {
4681
4792
  const calendar = cfg.envs.map((env) => {
4682
4793
  const resolved = calendarServiceConfig(cfg, env);
@@ -4697,6 +4808,7 @@ async function doctor(options) {
4697
4808
  }
4698
4809
  }
4699
4810
  warnings.push(...integrationWarnings(database.integrations, schema, rules));
4811
+ warnings.push(...secretContractWarnings(contract, cfg));
4700
4812
  if (cfg.services.includes("ai") && cfg.ai?.mode === "byok" && !cfg.ai.provider) {
4701
4813
  warnings.push("ai.mode is byok but ai.provider is not set");
4702
4814
  }
@@ -4745,6 +4857,7 @@ var init_doctor = __esm({
4745
4857
  init_doctor_runbooks();
4746
4858
  init_integrations();
4747
4859
  init_local();
4860
+ init_secret_contract();
4748
4861
  }
4749
4862
  });
4750
4863
 
@@ -5072,6 +5185,81 @@ var init_secrets_set = __esm({
5072
5185
  }
5073
5186
  });
5074
5187
 
5188
+ // src/secrets-status.ts
5189
+ async function secretsStatus(options) {
5190
+ const out = options.stdout ?? console;
5191
+ const doFetch = options.fetch ?? fetch;
5192
+ const cfg = await loadProjectConfig(options.configPath);
5193
+ if (!cfg.envs.includes(options.env)) {
5194
+ throw new Error(`env "${options.env}" is not in config envs (${cfg.envs.join(", ")})`);
5195
+ }
5196
+ const tenantId = (0, import_apps11.tenantIdFor)(cfg.app.id, options.env);
5197
+ const contract = resolveSecretContract(cfg);
5198
+ const token = await getDeveloperToken(cfg, options, doFetch, out);
5199
+ const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/secrets`, {
5200
+ headers: { authorization: `Bearer ${token}` }
5201
+ });
5202
+ if (!res.ok) {
5203
+ const detail = (await res.text().catch(() => "")).slice(0, 300);
5204
+ throw new Error(`list secrets for ${tenantId} failed (${res.status}): ${detail || "request failed"}`);
5205
+ }
5206
+ const body = await res.json();
5207
+ const stored = new Set((body.secrets ?? []).map((entry) => String(entry.name)));
5208
+ const report4 = buildReport(cfg.app.id, options.env, tenantId, contract, stored);
5209
+ if (options.json) out.log(JSON.stringify(report4, null, 2));
5210
+ else printReport(report4, out);
5211
+ return report4;
5212
+ }
5213
+ function buildReport(appId, env, tenant, contract, stored) {
5214
+ const declared = new Set(contract.map((secret) => secret.name));
5215
+ const rows = contract.map((secret) => ({
5216
+ name: secret.name,
5217
+ state: secret.reserved ? "reserved" : stored.has(secret.name) ? "set" : "missing",
5218
+ required: secret.required,
5219
+ sources: secret.sources,
5220
+ description: secret.description
5221
+ }));
5222
+ for (const name of [...stored].sort()) {
5223
+ if (!declared.has(name)) rows.push({ name, state: "undeclared", required: false, sources: [] });
5224
+ }
5225
+ const ok = rows.every((row) => row.state !== "missing" || !row.required);
5226
+ return { app: appId, env, tenant, secrets: rows, ok };
5227
+ }
5228
+ function printReport(report4, out) {
5229
+ out.log(`${report4.app} (${report4.tenant})`);
5230
+ if (report4.secrets.length === 0) {
5231
+ out.log(" no secrets declared and none stored");
5232
+ return;
5233
+ }
5234
+ for (const row of report4.secrets) {
5235
+ const label = row.state === "missing" && !row.required ? "missing (optional)" : row.state;
5236
+ const suffix = row.sources.length ? ` \u2014 ${row.sources.join(", ")}` : "";
5237
+ out.log(` ${label.padEnd(18)} ${row.name}${suffix}`);
5238
+ }
5239
+ const missing = report4.secrets.filter((row) => row.state === "missing" && row.required);
5240
+ if (missing.length) {
5241
+ out.log("");
5242
+ for (const row of missing) {
5243
+ out.log(`${row.name} is required but not set \u2014 "odla-ai secrets set ${row.name} --env ${report4.env} --stdin"`);
5244
+ }
5245
+ }
5246
+ if (report4.secrets.some((row) => row.state === "reserved")) {
5247
+ out.log("");
5248
+ out.log('"reserved" slots are never enumerated by the vault; presence cannot be confirmed here.');
5249
+ }
5250
+ }
5251
+ var import_apps11;
5252
+ var init_secrets_status = __esm({
5253
+ "src/secrets-status.ts"() {
5254
+ "use strict";
5255
+ init_cjs_shims();
5256
+ import_apps11 = require("@odla-ai/apps");
5257
+ init_config();
5258
+ init_secret_contract();
5259
+ init_token();
5260
+ }
5261
+ });
5262
+
5075
5263
  // src/skill-adapters.ts
5076
5264
  function claudeAdapter(skill, canonical) {
5077
5265
  const match = canonical.match(/^---\r?\n([\s\S]*?)\r?\n---/);
@@ -5566,9 +5754,22 @@ async function secretsCommand(parsed, deps) {
5566
5754
  await (sub === "set" ? secretsSet(options) : secretsSetClerkKey(options));
5567
5755
  return;
5568
5756
  }
5757
+ if (sub === "status") {
5758
+ assertArgs(parsed, ["config", "env", "token", "email", "json"], 2);
5759
+ await secretsStatus({
5760
+ configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
5761
+ env: requiredString(parsed.options.env, "--env"),
5762
+ json: parsed.options.json === true,
5763
+ token: stringOpt(parsed.options.token),
5764
+ email: stringOpt(parsed.options.email),
5765
+ fetch: deps.fetch,
5766
+ stdout: deps.stdout
5767
+ });
5768
+ return;
5769
+ }
5569
5770
  if (sub !== "push") {
5570
5771
  throw new Error(
5571
- `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".`
5772
+ `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".`
5572
5773
  );
5573
5774
  }
5574
5775
  assertArgs(parsed, ["config", "env", "dry-run", "yes"], 2);
@@ -5725,6 +5926,7 @@ var init_cli_project = __esm({
5725
5926
  init_init();
5726
5927
  init_secrets();
5727
5928
  init_secrets_set();
5929
+ init_secrets_status();
5728
5930
  init_skill();
5729
5931
  init_smoke();
5730
5932
  SKILL_OPTS = ["dir", "global", "force", "agent", "harness"];
@@ -6183,9 +6385,9 @@ function dependenciesOf(values, influence = "data") {
6183
6385
  result.push({ ref, influence, promptSafetyAtUse: value2.label.promptSafety });
6184
6386
  }
6185
6387
  }
6186
- const unique3 = /* @__PURE__ */ new Map();
6187
- for (const dep of result) unique3.set(`${dep.ref.kind}\0${dep.ref.id}\0${dep.influence}\0${dep.promptSafetyAtUse}`, dep);
6188
- return [...unique3.values()];
6388
+ const unique4 = /* @__PURE__ */ new Map();
6389
+ for (const dep of result) unique4.set(`${dep.ref.kind}\0${dep.ref.id}\0${dep.influence}\0${dep.promptSafetyAtUse}`, dep);
6390
+ return [...unique4.values()];
6189
6391
  }
6190
6392
  var init_chunk_L5DYU2E2 = __esm({
6191
6393
  "../camel/dist/chunk-L5DYU2E2.js"() {
@@ -7089,7 +7291,7 @@ var init_code2 = __esm({
7089
7291
  }
7090
7292
  });
7091
7293
 
7092
- // ../harness/dist/chunk-5FFR7U4L.js
7294
+ // ../harness/dist/chunk-ANNX7VGK.js
7093
7295
  async function digestStagedWorkspace(root, limits) {
7094
7296
  const files = [];
7095
7297
  const walk = async (directory) => {
@@ -7234,6 +7436,16 @@ function createCodeRuntimeControlClient(options) {
7234
7436
  }
7235
7437
  await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
7236
7438
  },
7439
+ recallMemories: async (sessionId, subjects, limit) => {
7440
+ const response2 = await call2(
7441
+ `/registry/code/runtime/sessions/${validSessionId(sessionId)}/recall`,
7442
+ { subjects: [...subjects], limit }
7443
+ );
7444
+ return Array.isArray(response2.memories) ? response2.memories : [];
7445
+ },
7446
+ rememberMemory: async (sessionId, memory) => {
7447
+ await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/remember`, memory);
7448
+ },
7237
7449
  reportSessionFailure: async (sessionId, message2) => {
7238
7450
  if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
7239
7451
  await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
@@ -8774,6 +8986,28 @@ function validateOptions(options) {
8774
8986
  throw new TypeError("Code tool broker read-only prefix is invalid");
8775
8987
  }
8776
8988
  }
8989
+ function validateMemory(memory) {
8990
+ if (!memory.subject.includes(":")) {
8991
+ throw new TypeError(`memory subject must be a graph node id, got "${memory.subject}"`);
8992
+ }
8993
+ const body = memory.body.trim();
8994
+ if (!body) throw new TypeError("a memory needs a body");
8995
+ if (body.length > MAX_MEMORY_BODY) throw new TypeError("memory body exceeds its bound");
8996
+ if (!memory.authorId.trim()) throw new TypeError("a memory needs an author");
8997
+ }
8998
+ function hazardFromAttempt(input) {
8999
+ const body = [
9000
+ `Attempt ${input.attempt} at "${input.goal.slice(0, 200)}" failed its proof.`,
9001
+ input.feedback.replace(/\s+/g, " ").slice(0, MAX_MEMORY_BODY - 300)
9002
+ ].join(" ");
9003
+ return input.touched.slice(0, 10).map((path) => ({
9004
+ subject: path.includes(":") ? path : `file:${path}`,
9005
+ kind: "hazard",
9006
+ body,
9007
+ evidence: { kind: "gate", ref: input.verificationId },
9008
+ authorId: input.authorId
9009
+ }));
9010
+ }
8777
9011
  async function runGoal(spec, attempt) {
8778
9012
  assertBudget(spec.budget);
8779
9013
  const now = spec.now ?? Date.now;
@@ -8964,6 +9198,9 @@ function pursueRuntimeGoal(input) {
8964
9198
  return { gatePassed: false, feedback: "", tokens: outcome.tokens, error: outcome.error };
8965
9199
  }
8966
9200
  const verdict = await input.gate(attempt);
9201
+ if (!verdict.passed && input.memory) {
9202
+ await rememberFailure(input, attempt, verdict.feedback);
9203
+ }
8967
9204
  return {
8968
9205
  gatePassed: verdict.passed,
8969
9206
  feedback: verdict.feedback,
@@ -8974,6 +9211,25 @@ function pursueRuntimeGoal(input) {
8974
9211
  }
8975
9212
  );
8976
9213
  }
9214
+ async function rememberFailure(input, attempt, feedback) {
9215
+ if (!input.memory || !feedback.trim()) return;
9216
+ try {
9217
+ const touched = await input.touched?.(attempt) ?? [];
9218
+ if (touched.length === 0) return;
9219
+ for (const memory of hazardFromAttempt({
9220
+ goal: input.spec.goal,
9221
+ attempt,
9222
+ feedback,
9223
+ touched,
9224
+ verificationId: `goal-${attempt}`,
9225
+ authorId: input.memory.authorId
9226
+ })) {
9227
+ validateMemory(memory);
9228
+ await input.memory.store.remember(memory);
9229
+ }
9230
+ } catch {
9231
+ }
9232
+ }
8977
9233
  function goalEventLine(event) {
8978
9234
  if (event.type === "attempt_started") return `Goal attempt ${event.attempt} starting.`;
8979
9235
  if (event.type === "attempt_failed") return `Attempt ${event.attempt} did not satisfy the proof.`;
@@ -9019,9 +9275,9 @@ async function appendCodeRuntimeEvent(control, command, event, refs) {
9019
9275
  const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
9020
9276
  await control.appendSessionEvent(command.sessionId, eventId, bounded);
9021
9277
  }
9022
- var import_crypto, import_promises5, import_path5, import_child_process4, import_promises6, import_path6, import_child_process5, import_process2, import_crypto2, import_crypto3, import_fs2, import_promises7, import_path7, import_promises8, import_os3, import_path8, import_ai4, import_promises9, import_path9, import_promises10, import_promises11, import_path10, import_crypto4, CODE_RUNTIME_PROTOCOL_VERSION, CodeRuntimeReconciler, CodeRuntimeControlError, record4, invalid, RESERVED, SECRET, PATH, FORBIDDEN, ARTIFACT_PATH, PRIVATE_ARTIFACT_PART, SHA3, DIGEST3, ID3, RULE, DEFAULT_PREFIXES, DEFAULT_SUFFIXES, message, CodeRuntimeCheckpointManager, record22, SOURCE_LIMITS, RESERVED2, SECRET2, V1_SYSTEM_PROMPT, V2_SYSTEM_PROMPT, V3_SYSTEM_PROMPT, SYSTEM_PROMPT_FOR, DEFAULT_MAX_FILES, DEFAULT_MAX_RESULTS, DEFAULT_MAX_FILE_BYTES, DESTINATIONS, READ, LIST, SEARCH, GRAPH, PATCH, RECIPE, cache, shortId, GRAPH_TOOLS, POSITIVE, digestRuntimeValue, runtimeErrorMessage, CodePiRuntimeEngine;
9023
- var init_chunk_5FFR7U4L = __esm({
9024
- "../harness/dist/chunk-5FFR7U4L.js"() {
9278
+ var import_crypto, import_promises5, import_path5, import_child_process4, import_promises6, import_path6, import_child_process5, import_process2, import_crypto2, import_crypto3, import_fs2, import_promises7, import_path7, import_promises8, import_os3, import_path8, import_ai4, import_promises9, import_path9, import_promises10, import_promises11, import_path10, import_crypto4, CODE_RUNTIME_PROTOCOL_VERSION, CodeRuntimeReconciler, CodeRuntimeControlError, record4, invalid, RESERVED, SECRET, PATH, FORBIDDEN, ARTIFACT_PATH, PRIVATE_ARTIFACT_PART, SHA3, DIGEST3, ID3, RULE, DEFAULT_PREFIXES, DEFAULT_SUFFIXES, message, CodeRuntimeCheckpointManager, record22, SOURCE_LIMITS, RESERVED2, SECRET2, V1_SYSTEM_PROMPT, V2_SYSTEM_PROMPT, V3_SYSTEM_PROMPT, SYSTEM_PROMPT_FOR, DEFAULT_MAX_FILES, DEFAULT_MAX_RESULTS, DEFAULT_MAX_FILE_BYTES, DESTINATIONS, READ, LIST, SEARCH, GRAPH, PATCH, RECIPE, cache, shortId, GRAPH_TOOLS, MAX_MEMORY_BODY, POSITIVE, digestRuntimeValue, runtimeErrorMessage, CodePiRuntimeEngine;
9279
+ var init_chunk_ANNX7VGK = __esm({
9280
+ "../harness/dist/chunk-ANNX7VGK.js"() {
9025
9281
  "use strict";
9026
9282
  init_cjs_shims();
9027
9283
  init_chunk_GKDKIU4P();
@@ -9251,6 +9507,7 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
9251
9507
  "sandbox.who_imports",
9252
9508
  "sandbox.who_touches"
9253
9509
  ]);
9510
+ MAX_MEMORY_BODY = 4e3;
9254
9511
  POSITIVE = (value2) => Number.isFinite(value2) && Number(value2) > 0 ? Number(value2) : void 0;
9255
9512
  digestRuntimeValue = (value2) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value2).digest("hex")}`;
9256
9513
  runtimeErrorMessage = (value2) => value2 instanceof Error ? value2.message : String(value2);
@@ -9535,7 +9792,7 @@ var init_node = __esm({
9535
9792
  "../harness/dist/node.js"() {
9536
9793
  "use strict";
9537
9794
  init_cjs_shims();
9538
- init_chunk_5FFR7U4L();
9795
+ init_chunk_ANNX7VGK();
9539
9796
  init_chunk_GKDKIU4P();
9540
9797
  MEASURED_PREMIUM = Object.freeze({
9541
9798
  /** 3 racers vs pure depth at equal budget: 21,044 / 7,936. */
@@ -10504,7 +10761,9 @@ Commands:
10504
10761
  copilot, gemini, or agents (repeatable or comma-separated).
10505
10762
  secrets Push configured db/o11y secrets into the Worker via wrangler
10506
10763
  stdin; set stores a tenant-vault secret and set-clerk-key the
10507
- reserved Clerk secret key, write-only from stdin or an env var.
10764
+ reserved Clerk secret key, write-only from stdin or an env var;
10765
+ status compares the secrets the config declares against the
10766
+ names the environment's vault holds (--json for a report).
10508
10767
  version Print the CLI version.
10509
10768
 
10510
10769
  Safety:
@@ -12738,7 +12997,7 @@ var init_integration_provision = __esm({
12738
12997
 
12739
12998
  // src/provision-credentials.ts
12740
12999
  async function provisionEnvCredentials(opts) {
12741
- const tenantId = (0, import_apps11.tenantIdFor)(opts.cfg.app.id, opts.env);
13000
+ const tenantId = (0, import_apps12.tenantIdFor)(opts.cfg.app.id, opts.env);
12742
13001
  const prior = opts.credentials?.envs[opts.env];
12743
13002
  let credentials = opts.credentials;
12744
13003
  let dbKey = opts.cfg.services.includes("db") && !opts.rotateDb ? prior?.dbKey : void 0;
@@ -12829,12 +13088,12 @@ async function safeText7(res) {
12829
13088
  return "";
12830
13089
  }
12831
13090
  }
12832
- var import_apps11;
13091
+ var import_apps12;
12833
13092
  var init_provision_credentials = __esm({
12834
13093
  "src/provision-credentials.ts"() {
12835
13094
  "use strict";
12836
13095
  init_cjs_shims();
12837
- import_apps11 = require("@odla-ai/apps");
13096
+ import_apps12 = require("@odla-ai/apps");
12838
13097
  init_local();
12839
13098
  init_redact();
12840
13099
  }
@@ -13036,7 +13295,7 @@ async function provision(options) {
13036
13295
  optionalProjectCapabilities: ["app.manage"],
13037
13296
  forceReview: options.requestGrant
13038
13297
  });
13039
- const apps = (0, import_apps12.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
13298
+ const apps = (0, import_apps13.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
13040
13299
  const existing = await apps.resolveApp(cfg.app.id);
13041
13300
  if (existing) {
13042
13301
  out.log(`app: ${cfg.app.id} already exists`);
@@ -13048,7 +13307,7 @@ async function provision(options) {
13048
13307
  try {
13049
13308
  await apps.createApp({ name: cfg.app.name, appId: cfg.app.id });
13050
13309
  } catch (error) {
13051
- if (error instanceof import_apps12.AppsError && error.status === 403) {
13310
+ if (error instanceof import_apps13.AppsError && error.status === 403) {
13052
13311
  throw new Error(
13053
13312
  `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`,
13054
13313
  { cause: error }
@@ -13061,7 +13320,7 @@ async function provision(options) {
13061
13320
  for (const env of cfg.envs) {
13062
13321
  await assertTenantAdminAccess(doFetch, cfg, env, token);
13063
13322
  }
13064
- const serviceOrder = (0, import_apps12.orderAppServices)(cfg.services);
13323
+ const serviceOrder = (0, import_apps13.orderAppServices)(cfg.services);
13065
13324
  for (const env of cfg.envs) {
13066
13325
  for (const service of serviceOrder) {
13067
13326
  if (service === "ai") {
@@ -13095,7 +13354,7 @@ async function provision(options) {
13095
13354
  }
13096
13355
  let devVarsCredentials = credentials;
13097
13356
  for (const env of cfg.envs) {
13098
- const tenantId = (0, import_apps12.tenantIdFor)(cfg.app.id, env);
13357
+ const tenantId = (0, import_apps13.tenantIdFor)(cfg.app.id, env);
13099
13358
  let dbKey;
13100
13359
  if (options.pushSecrets) {
13101
13360
  const delivered = await deliverRuntimeCredentials(cfg, {
@@ -13181,12 +13440,12 @@ async function provision(options) {
13181
13440
  }
13182
13441
  }
13183
13442
  }
13184
- var import_apps12, import_ai5, import_node_process12;
13443
+ var import_apps13, import_ai5, import_node_process12;
13185
13444
  var init_provision = __esm({
13186
13445
  "src/provision.ts"() {
13187
13446
  "use strict";
13188
13447
  init_cjs_shims();
13189
- import_apps12 = require("@odla-ai/apps");
13448
+ import_apps13 = require("@odla-ai/apps");
13190
13449
  import_ai5 = require("@odla-ai/ai");
13191
13450
  import_node_process12 = __toESM(require("process"), 1);
13192
13451
  init_config();
@@ -13357,7 +13616,7 @@ var init_surface = __esm({
13357
13616
  rm: {},
13358
13617
  lint: {}
13359
13618
  },
13360
- secrets: { push: {}, set: {}, "set-clerk-key": {} },
13619
+ secrets: { push: {}, status: {}, set: {}, "set-clerk-key": {} },
13361
13620
  security: {
13362
13621
  plan: {},
13363
13622
  sources: {},