@pleri/olam-cli 0.1.120 → 0.1.125

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.
Files changed (60) hide show
  1. package/dist/commands/bootstrap.d.ts +4 -0
  2. package/dist/commands/bootstrap.d.ts.map +1 -1
  3. package/dist/commands/bootstrap.js +64 -0
  4. package/dist/commands/bootstrap.js.map +1 -1
  5. package/dist/commands/mcp/install-shared.d.ts +2 -0
  6. package/dist/commands/mcp/install-shared.d.ts.map +1 -1
  7. package/dist/commands/mcp/install-shared.js +13 -0
  8. package/dist/commands/mcp/install-shared.js.map +1 -1
  9. package/dist/commands/mcp/uninstall.d.ts.map +1 -1
  10. package/dist/commands/mcp/uninstall.js +1 -10
  11. package/dist/commands/mcp/uninstall.js.map +1 -1
  12. package/dist/commands/memory/_paths.d.ts +15 -0
  13. package/dist/commands/memory/_paths.d.ts.map +1 -0
  14. package/dist/commands/memory/_paths.js +34 -0
  15. package/dist/commands/memory/_paths.js.map +1 -0
  16. package/dist/commands/memory/index.d.ts +17 -0
  17. package/dist/commands/memory/index.d.ts.map +1 -0
  18. package/dist/commands/memory/index.js +34 -0
  19. package/dist/commands/memory/index.js.map +1 -0
  20. package/dist/commands/memory/install.d.ts +57 -0
  21. package/dist/commands/memory/install.d.ts.map +1 -0
  22. package/dist/commands/memory/install.js +114 -0
  23. package/dist/commands/memory/install.js.map +1 -0
  24. package/dist/commands/memory/logs.d.ts +15 -0
  25. package/dist/commands/memory/logs.d.ts.map +1 -0
  26. package/dist/commands/memory/logs.js +45 -0
  27. package/dist/commands/memory/logs.js.map +1 -0
  28. package/dist/commands/memory/secret.d.ts +16 -0
  29. package/dist/commands/memory/secret.d.ts.map +1 -0
  30. package/dist/commands/memory/secret.js +79 -0
  31. package/dist/commands/memory/secret.js.map +1 -0
  32. package/dist/commands/memory/start.d.ts +23 -0
  33. package/dist/commands/memory/start.d.ts.map +1 -0
  34. package/dist/commands/memory/start.js +166 -0
  35. package/dist/commands/memory/start.js.map +1 -0
  36. package/dist/commands/memory/status.d.ts +25 -0
  37. package/dist/commands/memory/status.d.ts.map +1 -0
  38. package/dist/commands/memory/status.js +101 -0
  39. package/dist/commands/memory/status.js.map +1 -0
  40. package/dist/commands/memory/stop.d.ts +12 -0
  41. package/dist/commands/memory/stop.d.ts.map +1 -0
  42. package/dist/commands/memory/stop.js +81 -0
  43. package/dist/commands/memory/stop.js.map +1 -0
  44. package/dist/commands/memory/uninstall.d.ts +19 -0
  45. package/dist/commands/memory/uninstall.d.ts.map +1 -0
  46. package/dist/commands/memory/uninstall.js +60 -0
  47. package/dist/commands/memory/uninstall.js.map +1 -0
  48. package/dist/commands/seed.d.ts +27 -0
  49. package/dist/commands/seed.d.ts.map +1 -0
  50. package/dist/commands/seed.js +303 -0
  51. package/dist/commands/seed.js.map +1 -0
  52. package/dist/image-digests.json +1 -1
  53. package/dist/index.js +1643 -183
  54. package/dist/index.js.map +1 -1
  55. package/dist/lib/memory-secret.d.ts +48 -0
  56. package/dist/lib/memory-secret.d.ts.map +1 -0
  57. package/dist/lib/memory-secret.js +92 -0
  58. package/dist/lib/memory-secret.js.map +1 -0
  59. package/dist/mcp-server.js +481 -65
  60. package/package.json +1 -1
@@ -2986,7 +2986,7 @@ var require_compile = __commonJS({
2986
2986
  const schOrFunc = root.refs[ref];
2987
2987
  if (schOrFunc)
2988
2988
  return schOrFunc;
2989
- let _sch = resolve9.call(this, root, ref);
2989
+ let _sch = resolve10.call(this, root, ref);
2990
2990
  if (_sch === void 0) {
2991
2991
  const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
2992
2992
  const { schemaId } = this.opts;
@@ -3013,7 +3013,7 @@ var require_compile = __commonJS({
3013
3013
  function sameSchemaEnv(s1, s2) {
3014
3014
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3015
3015
  }
3016
- function resolve9(root, ref) {
3016
+ function resolve10(root, ref) {
3017
3017
  let sch;
3018
3018
  while (typeof (sch = this.refs[ref]) == "string")
3019
3019
  ref = sch;
@@ -3588,7 +3588,7 @@ var require_fast_uri = __commonJS({
3588
3588
  }
3589
3589
  return uri;
3590
3590
  }
3591
- function resolve9(baseURI, relativeURI, options) {
3591
+ function resolve10(baseURI, relativeURI, options) {
3592
3592
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3593
3593
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
3594
3594
  schemelessOptions.skipEscape = true;
@@ -3815,7 +3815,7 @@ var require_fast_uri = __commonJS({
3815
3815
  var fastUri = {
3816
3816
  SCHEMES,
3817
3817
  normalize,
3818
- resolve: resolve9,
3818
+ resolve: resolve10,
3819
3819
  resolveComponent,
3820
3820
  equal,
3821
3821
  serialize,
@@ -12914,12 +12914,12 @@ var StdioServerTransport = class {
12914
12914
  this.onclose?.();
12915
12915
  }
12916
12916
  send(message) {
12917
- return new Promise((resolve9) => {
12917
+ return new Promise((resolve10) => {
12918
12918
  const json = serializeMessage(message);
12919
12919
  if (this._stdout.write(json)) {
12920
- resolve9();
12920
+ resolve10();
12921
12921
  } else {
12922
- this._stdout.once("drain", resolve9);
12922
+ this._stdout.once("drain", resolve10);
12923
12923
  }
12924
12924
  });
12925
12925
  }
@@ -18987,7 +18987,7 @@ var Protocol = class {
18987
18987
  return;
18988
18988
  }
18989
18989
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
18990
- await new Promise((resolve9) => setTimeout(resolve9, pollInterval));
18990
+ await new Promise((resolve10) => setTimeout(resolve10, pollInterval));
18991
18991
  options?.signal?.throwIfAborted();
18992
18992
  }
18993
18993
  } catch (error2) {
@@ -19004,7 +19004,7 @@ var Protocol = class {
19004
19004
  */
19005
19005
  request(request2, resultSchema, options) {
19006
19006
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
19007
- return new Promise((resolve9, reject2) => {
19007
+ return new Promise((resolve10, reject2) => {
19008
19008
  const earlyReject = (error2) => {
19009
19009
  reject2(error2);
19010
19010
  };
@@ -19082,7 +19082,7 @@ var Protocol = class {
19082
19082
  if (!parseResult.success) {
19083
19083
  reject2(parseResult.error);
19084
19084
  } else {
19085
- resolve9(parseResult.data);
19085
+ resolve10(parseResult.data);
19086
19086
  }
19087
19087
  } catch (error2) {
19088
19088
  reject2(error2);
@@ -19343,12 +19343,12 @@ var Protocol = class {
19343
19343
  }
19344
19344
  } catch {
19345
19345
  }
19346
- return new Promise((resolve9, reject2) => {
19346
+ return new Promise((resolve10, reject2) => {
19347
19347
  if (signal.aborted) {
19348
19348
  reject2(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
19349
19349
  return;
19350
19350
  }
19351
- const timeoutId = setTimeout(resolve9, interval);
19351
+ const timeoutId = setTimeout(resolve10, interval);
19352
19352
  signal.addEventListener("abort", () => {
19353
19353
  clearTimeout(timeoutId);
19354
19354
  reject2(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -20448,7 +20448,7 @@ var McpServer = class {
20448
20448
  let task = createTaskResult.task;
20449
20449
  const pollInterval = task.pollInterval ?? 5e3;
20450
20450
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
20451
- await new Promise((resolve9) => setTimeout(resolve9, pollInterval));
20451
+ await new Promise((resolve10) => setTimeout(resolve10, pollInterval));
20452
20452
  const updatedTask = await extra.taskStore.getTask(taskId);
20453
20453
  if (!updatedTask) {
20454
20454
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -21411,7 +21411,7 @@ async function safeText(res) {
21411
21411
  }
21412
21412
  }
21413
21413
  function sleep(ms) {
21414
- return new Promise((resolve9) => setTimeout(resolve9, ms));
21414
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
21415
21415
  }
21416
21416
 
21417
21417
  // ../core/dist/auth/container.js
@@ -21543,7 +21543,7 @@ function resolveAuthServicePath() {
21543
21543
  return path3.join(pkgsDir, "auth-service");
21544
21544
  }
21545
21545
  function sleep2(ms) {
21546
- return new Promise((resolve9) => setTimeout(resolve9, ms));
21546
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
21547
21547
  }
21548
21548
 
21549
21549
  // ../core/dist/auth/preflight.js
@@ -21788,6 +21788,7 @@ function rowToMetadata(row) {
21788
21788
  let readinessChain;
21789
21789
  let expectedServices;
21790
21790
  let appPortUrls;
21791
+ let worldDbNames;
21791
21792
  try {
21792
21793
  if (row.readiness_chain)
21793
21794
  readinessChain = JSON.parse(row.readiness_chain);
@@ -21803,6 +21804,11 @@ function rowToMetadata(row) {
21803
21804
  appPortUrls = JSON.parse(row.app_port_urls);
21804
21805
  } catch {
21805
21806
  }
21807
+ try {
21808
+ if (row.world_db_names)
21809
+ worldDbNames = JSON.parse(row.world_db_names);
21810
+ } catch {
21811
+ }
21806
21812
  return {
21807
21813
  id: row.id,
21808
21814
  name: row.name,
@@ -21825,7 +21831,9 @@ function rowToMetadata(row) {
21825
21831
  autoDestroyOnMerge: (row.auto_destroy_on_merge ?? 1) !== 0,
21826
21832
  ...readinessChain !== void 0 ? { readinessChain } : {},
21827
21833
  ...expectedServices !== void 0 ? { expectedServices } : {},
21828
- ...appPortUrls !== void 0 ? { appPortUrls } : {}
21834
+ ...appPortUrls !== void 0 ? { appPortUrls } : {},
21835
+ ...worldDbNames !== void 0 ? { worldDbNames } : {},
21836
+ ...row.world_role_name ? { worldRoleName: row.world_role_name } : {}
21829
21837
  };
21830
21838
  }
21831
21839
  var SCHEMA_VERSION = "1";
@@ -21871,7 +21879,17 @@ var WorldRegistry = class {
21871
21879
  this.db.pragma("foreign_keys = ON");
21872
21880
  this.db.exec(CREATE_TABLE);
21873
21881
  this.db.exec(CREATE_META);
21874
- for (const col of ["readiness_chain TEXT", "expected_services TEXT", "app_port_urls TEXT", "tailscale_paths TEXT"]) {
21882
+ for (const col of [
21883
+ "readiness_chain TEXT",
21884
+ "expected_services TEXT",
21885
+ "app_port_urls TEXT",
21886
+ "tailscale_paths TEXT",
21887
+ // olam-hybrid-shared-postgres Phase A task A7. JSON-serialised string for
21888
+ // world_db_names (SQLite has no native array; matches repos column shape).
21889
+ // TEXT[] would fail at DDL time (closes OQ13/OQ22 / FS-03).
21890
+ "world_db_names TEXT",
21891
+ "world_role_name TEXT"
21892
+ ]) {
21875
21893
  try {
21876
21894
  this.db.exec(`ALTER TABLE worlds ADD COLUMN ${col}`);
21877
21895
  } catch {
@@ -21897,8 +21915,9 @@ var WorldRegistry = class {
21897
21915
  (id, name, status, repos, branch, port_offset, workspace_path,
21898
21916
  compute_provider, total_cost_usd, thought_count, created_at, updated_at,
21899
21917
  pr_url, pr_number, pr_repo, pr_created_at, pr_state, pr_merged_at, auto_destroy_on_merge,
21900
- readiness_chain, expected_services, app_port_urls)
21901
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(world.id, world.name, world.status, JSON.stringify(world.repos), world.branch, world.portOffset, world.workspacePath, world.computeProvider, world.totalCostUsd, world.thoughtCount, world.createdAt, world.updatedAt, world.prUrl ?? null, world.prNumber ?? null, world.prRepo ?? null, world.prCreatedAt ?? null, world.prState ?? "none", world.prMergedAt ?? null, world.autoDestroyOnMerge === false ? 0 : 1, world.readinessChain !== void 0 ? JSON.stringify(world.readinessChain) : null, world.expectedServices !== void 0 ? JSON.stringify(world.expectedServices) : null, world.appPortUrls !== void 0 ? JSON.stringify(world.appPortUrls) : null);
21918
+ readiness_chain, expected_services, app_port_urls,
21919
+ world_db_names, world_role_name)
21920
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(world.id, world.name, world.status, JSON.stringify(world.repos), world.branch, world.portOffset, world.workspacePath, world.computeProvider, world.totalCostUsd, world.thoughtCount, world.createdAt, world.updatedAt, world.prUrl ?? null, world.prNumber ?? null, world.prRepo ?? null, world.prCreatedAt ?? null, world.prState ?? "none", world.prMergedAt ?? null, world.autoDestroyOnMerge === false ? 0 : 1, world.readinessChain !== void 0 ? JSON.stringify(world.readinessChain) : null, world.expectedServices !== void 0 ? JSON.stringify(world.expectedServices) : null, world.appPortUrls !== void 0 ? JSON.stringify(world.appPortUrls) : null, world.worldDbNames !== void 0 ? JSON.stringify(world.worldDbNames) : null, world.worldRoleName ?? null);
21902
21921
  }
21903
21922
  update(worldId, updates) {
21904
21923
  const setClauses = [];
@@ -21923,9 +21942,12 @@ var WorldRegistry = class {
21923
21942
  autoDestroyOnMerge: "auto_destroy_on_merge",
21924
21943
  readinessChain: "readiness_chain",
21925
21944
  expectedServices: "expected_services",
21926
- appPortUrls: "app_port_urls"
21945
+ appPortUrls: "app_port_urls",
21946
+ // olam-hybrid-shared-postgres Phase A task A7.
21947
+ worldDbNames: "world_db_names",
21948
+ worldRoleName: "world_role_name"
21927
21949
  };
21928
- const jsonColumns = /* @__PURE__ */ new Set(["repos", "readinessChain", "expectedServices", "appPortUrls"]);
21950
+ const jsonColumns = /* @__PURE__ */ new Set(["repos", "readinessChain", "expectedServices", "appPortUrls", "worldDbNames"]);
21929
21951
  for (const [key, col] of Object.entries(columnMap)) {
21930
21952
  const val = updates[key];
21931
21953
  if (val !== void 0) {
@@ -22326,7 +22348,7 @@ var runtimeVersionSchema = external_exports.object({
22326
22348
 
22327
22349
  // ../core/dist/world/repo-manifest.js
22328
22350
  import { existsSync as existsSync4, lstatSync, readFileSync as readFileSync3 } from "node:fs";
22329
- import { join as join5 } from "node:path";
22351
+ import { join as join5, resolve as resolve3, sep } from "node:path";
22330
22352
  import YAML from "yaml";
22331
22353
 
22332
22354
  // ../core/dist/manifest/version.js
@@ -22354,7 +22376,44 @@ var serviceSchema = external_exports.object({
22354
22376
  // - 'world' (default): container is per-world (today's behaviour).
22355
22377
  // - 'shared': container is shared across worlds (consumed by later phases).
22356
22378
  // TODO(phase-D): enforce shared-service deduplication in planManifestPipeline.
22357
- scope: external_exports.enum(["world", "shared"]).optional()
22379
+ scope: external_exports.enum(["world", "shared"]).optional(),
22380
+ // olam-hybrid-shared-postgres Phase A task A8.
22381
+ // Declares which postgres template DB(s) Olam should clone at world
22382
+ // create time. Consumed by `applyPostgresTemplateClone` (task A3 in
22383
+ // manager.ts) which runs `CREATE DATABASE <world-db> TEMPLATE <name>`
22384
+ // per entry. Accepts either a single string (single-DB repo) or an
22385
+ // array (atlas-core → ['atlas_common_seed', 'atlas_merchant_seed'];
22386
+ // atlas-pay → adds 'atlas_pay_seed' per Phase B Decision 17).
22387
+ seed_template: external_exports.union([external_exports.string().min(1), external_exports.array(external_exports.string().min(1)).nonempty()]).optional(),
22388
+ // Optional hash bound to the seed_template content for cross-machine
22389
+ // drift detection. The A1 bake script writes this value via .adb.yaml
22390
+ // post-bake (operator pastes from seed/seed-hash.txt; auto-write CI
22391
+ // tool is a Phase D follow-up per the plan's Out of scope).
22392
+ seed_template_hash: external_exports.string().optional(),
22393
+ // olam-hybrid-shared-postgres Phase A task A5 (closes OQ15 plan-killer).
22394
+ // Per-clone SQL hook: after `CREATE DATABASE <world-db> TEMPLATE <seed>`,
22395
+ // run these statements against the cloned world-DB. Atlas-core uses this
22396
+ // to UPDATE `merchants.identifier` so `Current.merchant` (which resolves
22397
+ // via `Merchant.find_by(identifier: ENV['POSTGRESQL_MERCHANT_DATABASE'])`)
22398
+ // matches the per-world merchant DB name.
22399
+ //
22400
+ // Shape: { <seed_template_name>: [<sql statement>, ...] }
22401
+ // The seed_template_name key selects which cloned DB the statements run
22402
+ // in. Order is preserved within each seed's list.
22403
+ //
22404
+ // Interpolation in statements:
22405
+ // ${WORLD_ID} → world id (e.g. frost-oak-5916)
22406
+ // ${WORLD_DB:<seed>} → corresponding world-DB name for that seed
22407
+ // (e.g. ${WORLD_DB:atlas_merchant_seed} →
22408
+ // atlas_merchant_world_frost-oak-5916)
22409
+ //
22410
+ // SECURITY INVARIANT: statements come from the operator's own checked-out
22411
+ // `.olam.yaml` (same trust class as `BootstrapStep.cmd`). They run as the
22412
+ // singleton's `development` superuser today; A4's per-world role lands
22413
+ // later in the seam. Statements are NOT exposed to operator-controlled
22414
+ // env variables — only the two whitelisted placeholders above. No shell
22415
+ // expansion, no DOLLAR-name expansion.
22416
+ post_clone_sql: external_exports.record(external_exports.string().min(1), external_exports.array(external_exports.string().min(1))).optional()
22358
22417
  }).passthrough();
22359
22418
  var deploySchema = external_exports.object({
22360
22419
  tags: external_exports.array(external_exports.string()).optional()
@@ -22409,7 +22468,12 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
22409
22468
  "secrets",
22410
22469
  "bootstrap",
22411
22470
  "start",
22412
- "deploy"
22471
+ "deploy",
22472
+ // F-7 (olam-hybrid-shared-postgres dogfood finding): native inheritance —
22473
+ // `.olam.yaml` may declare `inherits: .adb.yaml` to deep-merge an adb-shaped
22474
+ // base manifest into itself. Retires the atlas-one `bin/olam-create-wrap.sh`
22475
+ // stopgap. See the inheritance resolver in `loadRepoManifest`.
22476
+ "inherits"
22413
22477
  ]);
22414
22478
  var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
22415
22479
  function refineForbiddenKeys(value, path28, ctx, rejectSource) {
@@ -22453,6 +22517,23 @@ function rejectForbiddenKeys(value, path28, rejectSource) {
22453
22517
  function unknownTopLevelKeys(parsed) {
22454
22518
  return Object.keys(parsed).filter((k) => !KNOWN_TOP_LEVEL_KEYS.has(k));
22455
22519
  }
22520
+ function deepMergeManifest(base, overlay) {
22521
+ const out = { ...base };
22522
+ for (const key of Object.keys(overlay)) {
22523
+ if (FORBIDDEN_KEYS.has(key))
22524
+ continue;
22525
+ const overlayValue = overlay[key];
22526
+ if (overlayValue === void 0)
22527
+ continue;
22528
+ const baseValue = base[key];
22529
+ const bothObjects = isPlainObject3(baseValue) && isPlainObject3(overlayValue);
22530
+ out[key] = bothObjects ? deepMergeManifest(baseValue, overlayValue) : overlayValue;
22531
+ }
22532
+ return out;
22533
+ }
22534
+ function isPlainObject3(value) {
22535
+ return value !== null && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
22536
+ }
22456
22537
  function loadRepoManifest(repoDir) {
22457
22538
  const olamPath = join5(repoDir, ".olam.yaml");
22458
22539
  const adbPath = join5(repoDir, ".adb.yaml");
@@ -22483,7 +22564,37 @@ function loadRepoManifest(repoDir) {
22483
22564
  throw new Error(`[manifest] ${manifestPath2}: expected a YAML mapping at the top level`);
22484
22565
  }
22485
22566
  rejectForbiddenKeys(parsed, manifestPath2, true);
22486
- const body = RepoManifestSchema.parse(parsed);
22567
+ let mergedParsed = parsed;
22568
+ const inheritsRaw = parsed["inherits"];
22569
+ if (typeof inheritsRaw === "string" && inheritsRaw.length > 0) {
22570
+ if (source !== "olam") {
22571
+ throw new Error(`[manifest] ${manifestPath2}: \`inherits\` only valid in .olam.yaml (found in .adb.yaml \u2014 drop the field)`);
22572
+ }
22573
+ const inheritedAbs = resolve3(repoDir, inheritsRaw);
22574
+ const repoDirAbs = resolve3(repoDir);
22575
+ if (!inheritedAbs.startsWith(repoDirAbs + sep) && inheritedAbs !== repoDirAbs) {
22576
+ throw new Error(`[manifest] ${manifestPath2}: inherits target "${inheritsRaw}" escapes repo dir`);
22577
+ }
22578
+ if (!existsSync4(inheritedAbs)) {
22579
+ throw new Error(`[manifest] ${manifestPath2}: inherits target "${inheritsRaw}" not found`);
22580
+ }
22581
+ const inheritedStat = lstatSync(inheritedAbs);
22582
+ if (inheritedStat.isSymbolicLink()) {
22583
+ throw new Error(`[manifest] ${manifestPath2}: inherits target "${inheritsRaw}" is a symlink (not permitted)`);
22584
+ }
22585
+ const inheritedRaw = readFileSync3(inheritedAbs, "utf-8");
22586
+ const inheritedParsed = YAML.parse(inheritedRaw, { maxAliasCount: 100 });
22587
+ if (inheritedParsed !== null && inheritedParsed !== void 0) {
22588
+ if (typeof inheritedParsed !== "object" || Array.isArray(inheritedParsed)) {
22589
+ throw new Error(`[manifest] ${inheritedAbs}: expected a YAML mapping at the top level`);
22590
+ }
22591
+ rejectForbiddenKeys(inheritedParsed, inheritedAbs, true);
22592
+ const overlay = { ...parsed };
22593
+ delete overlay["inherits"];
22594
+ mergedParsed = deepMergeManifest(inheritedParsed, overlay);
22595
+ }
22596
+ }
22597
+ const body = RepoManifestSchema.parse(mergedParsed);
22487
22598
  if (parsed["version"] === void 0) {
22488
22599
  console.warn(`[manifest] ${manifestPath2}: missing "version: ${MANIFEST_VERSION}" field \u2014 add it to suppress this warning (backward-compat: file still parses)`);
22489
22600
  }
@@ -23012,7 +23123,7 @@ var realDocker = {
23012
23123
  }
23013
23124
  };
23014
23125
  function spawnAsync(cmd, args, opts = {}) {
23015
- return new Promise((resolve9) => {
23126
+ return new Promise((resolve10) => {
23016
23127
  const child = spawn(cmd, [...args], {
23017
23128
  stdio: ["ignore", "pipe", "pipe"],
23018
23129
  signal: opts.signal
@@ -23026,10 +23137,10 @@ function spawnAsync(cmd, args, opts = {}) {
23026
23137
  stderr += chunk.toString();
23027
23138
  });
23028
23139
  child.on("error", (err) => {
23029
- resolve9({ exitCode: -1, stdout, stderr: stderr + err.message });
23140
+ resolve10({ exitCode: -1, stdout, stderr: stderr + err.message });
23030
23141
  });
23031
23142
  child.on("close", (code) => {
23032
- resolve9({ exitCode: code ?? -1, stdout, stderr });
23143
+ resolve10({ exitCode: code ?? -1, stdout, stderr });
23033
23144
  });
23034
23145
  });
23035
23146
  }
@@ -23362,7 +23473,7 @@ var stopAndRemove = async (container) => {
23362
23473
 
23363
23474
  // ../adapters/dist/docker/exec.js
23364
23475
  import { PassThrough } from "node:stream";
23365
- var demuxStream = (stream) => new Promise((resolve9, reject2) => {
23476
+ var demuxStream = (stream) => new Promise((resolve10, reject2) => {
23366
23477
  const stdoutChunks = [];
23367
23478
  const stderrChunks = [];
23368
23479
  const stdout = new PassThrough();
@@ -23376,7 +23487,7 @@ var demuxStream = (stream) => new Promise((resolve9, reject2) => {
23376
23487
  stream.pipe(stdout);
23377
23488
  }
23378
23489
  stream.on("end", () => {
23379
- resolve9({
23490
+ resolve10({
23380
23491
  stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
23381
23492
  stderr: Buffer.concat(stderrChunks).toString("utf-8")
23382
23493
  });
@@ -23554,6 +23665,19 @@ var DockerProvider = class extends ComputeProvider {
23554
23665
  }
23555
23666
  const mergedEnv = { ...serviceEnv, ...config2.env };
23556
23667
  const container = await createWorldContainer(this.docker, id, name, config2.image, mergedEnv, config2.resources, config2.workspacePath, config2.portOffset, config2.appPorts, config2.cacheArch);
23668
+ for (const networkName3 of config2.extraNetworks ?? []) {
23669
+ try {
23670
+ const network = this.docker.getNetwork(networkName3);
23671
+ await network.connect({ Container: container.id });
23672
+ } catch (err) {
23673
+ const statusCode = err.statusCode;
23674
+ if (statusCode === 404) {
23675
+ throw new Error(`extra network "${networkName3}" not found \u2014 run \`olam bootstrap\` to recreate it`);
23676
+ }
23677
+ if (statusCode !== 403)
23678
+ throw err;
23679
+ }
23680
+ }
23557
23681
  return new DockerWorld(id, name, container, "running");
23558
23682
  }
23559
23683
  // -----------------------------------------------------------------------
@@ -23721,7 +23845,7 @@ var SSHConnectionPool = class {
23721
23845
  // -----------------------------------------------------------------------
23722
23846
  async exec(host, command) {
23723
23847
  const client = await this.getConnection(host);
23724
- return new Promise((resolve9, reject2) => {
23848
+ return new Promise((resolve10, reject2) => {
23725
23849
  client.exec(command, (err, stream) => {
23726
23850
  if (err) {
23727
23851
  reject2(new Error(`SSH exec failed on ${host}: ${err.message}`));
@@ -23736,7 +23860,7 @@ var SSHConnectionPool = class {
23736
23860
  stderr += data.toString();
23737
23861
  });
23738
23862
  stream.on("close", (code) => {
23739
- resolve9({
23863
+ resolve10({
23740
23864
  exitCode: code ?? 0,
23741
23865
  stdout: stdout.trimEnd(),
23742
23866
  stderr: stderr.trimEnd()
@@ -23767,10 +23891,10 @@ var SSHConnectionPool = class {
23767
23891
  throw new Error(`No SSH configuration found for host: ${host}`);
23768
23892
  }
23769
23893
  const client = new SSHClient();
23770
- return new Promise((resolve9, reject2) => {
23894
+ return new Promise((resolve10, reject2) => {
23771
23895
  client.on("ready", () => {
23772
23896
  this.connections.set(host, client);
23773
- resolve9(client);
23897
+ resolve10(client);
23774
23898
  }).on("error", (err) => {
23775
23899
  this.connections.delete(host);
23776
23900
  reject2(new Error(`SSH connection to ${host} failed: ${err.message}`));
@@ -24750,7 +24874,7 @@ function register6(server, ctx, initError) {
24750
24874
  }
24751
24875
  } catch {
24752
24876
  }
24753
- await new Promise((resolve9) => setTimeout(resolve9, POLL_INTERVAL_MS));
24877
+ await new Promise((resolve10) => setTimeout(resolve10, POLL_INTERVAL_MS));
24754
24878
  }
24755
24879
  }
24756
24880
  if (authenticated) {
@@ -26990,15 +27114,15 @@ ${JSON.stringify({ error: reason })}`
26990
27114
  unlinkSync2(udsPath);
26991
27115
  } catch {
26992
27116
  }
26993
- await new Promise((resolve9, reject2) => {
26994
- server.listen(udsPath, () => resolve9());
27117
+ await new Promise((resolve10, reject2) => {
27118
+ server.listen(udsPath, () => resolve10());
26995
27119
  server.once("error", reject2);
26996
27120
  });
26997
27121
  chmodSync3(udsPath, 384);
26998
27122
  port = 0;
26999
27123
  } else {
27000
- await new Promise((resolve9, reject2) => {
27001
- server.listen(opts.port ?? 0, "127.0.0.1", () => resolve9());
27124
+ await new Promise((resolve10, reject2) => {
27125
+ server.listen(opts.port ?? 0, "127.0.0.1", () => resolve10());
27002
27126
  server.once("error", reject2);
27003
27127
  });
27004
27128
  const addr = server.address();
@@ -27014,10 +27138,10 @@ ${JSON.stringify({ error: reason })}`
27014
27138
  } catch {
27015
27139
  }
27016
27140
  await Promise.race([
27017
- new Promise((resolve9, reject2) => {
27018
- server.close((err) => err ? reject2(err) : resolve9());
27141
+ new Promise((resolve10, reject2) => {
27142
+ server.close((err) => err ? reject2(err) : resolve10());
27019
27143
  }),
27020
- new Promise((resolve9) => setTimeout(resolve9, 5e3))
27144
+ new Promise((resolve10) => setTimeout(resolve10, 5e3))
27021
27145
  ]);
27022
27146
  if (udsPath) {
27023
27147
  try {
@@ -27349,10 +27473,10 @@ async function acquireLaunchSlot() {
27349
27473
  _inFlightLaunches++;
27350
27474
  return releaseLaunchSlot;
27351
27475
  }
27352
- return new Promise((resolve9) => {
27476
+ return new Promise((resolve10) => {
27353
27477
  _launchQueue.push(() => {
27354
27478
  _inFlightLaunches++;
27355
- resolve9(releaseLaunchSlot);
27479
+ resolve10(releaseLaunchSlot);
27356
27480
  });
27357
27481
  });
27358
27482
  }
@@ -27368,8 +27492,8 @@ function resolveShootsRoot() {
27368
27492
  }
27369
27493
  function isUnderRoot(absPath, root) {
27370
27494
  if (absPath === root) return true;
27371
- const sep = root.endsWith("/") ? "" : "/";
27372
- return absPath.startsWith(root + sep);
27495
+ const sep2 = root.endsWith("/") ? "" : "/";
27496
+ return absPath.startsWith(root + sep2);
27373
27497
  }
27374
27498
  function deriveAllowedPaths(shots) {
27375
27499
  const paths = /* @__PURE__ */ new Set();
@@ -29637,7 +29761,7 @@ function loadConfig(startDir) {
29637
29761
 
29638
29762
  // ../core/dist/world/manager.js
29639
29763
  import * as crypto5 from "node:crypto";
29640
- import { execSync as execSync5 } from "node:child_process";
29764
+ import { execSync as execSync5, spawnSync as spawnSync4 } from "node:child_process";
29641
29765
  import * as fs22 from "node:fs";
29642
29766
  import * as os13 from "node:os";
29643
29767
  import * as path25 from "node:path";
@@ -29864,7 +29988,7 @@ import * as path17 from "node:path";
29864
29988
 
29865
29989
  // ../core/dist/kg/storage-paths.js
29866
29990
  import { homedir as homedir10 } from "node:os";
29867
- import { join as join18, resolve as resolve4 } from "node:path";
29991
+ import { join as join18, resolve as resolve5 } from "node:path";
29868
29992
 
29869
29993
  // ../core/dist/world/workspace-name.js
29870
29994
  var InvalidWorkspaceNameError = class extends Error {
@@ -29901,7 +30025,7 @@ function assertWithinPrefix(path28, prefix, label) {
29901
30025
  function kgPristinePath(workspace) {
29902
30026
  validateWorkspaceName(workspace);
29903
30027
  const root = kgRoot();
29904
- const path28 = resolve4(join18(root, workspace));
30028
+ const path28 = resolve5(join18(root, workspace));
29905
30029
  assertWithinPrefix(path28, root, "kgPristinePath");
29906
30030
  return path28;
29907
30031
  }
@@ -32470,6 +32594,8 @@ ${detail}`);
32470
32594
  const hostPort = override !== void 0 ? override : ap.port + 1e4 + portOffset;
32471
32595
  return { repoName: ap.name, internalPort: ap.port, hostPort, url: `http://localhost:${hostPort}` };
32472
32596
  });
32597
+ let worldDbNames = void 0;
32598
+ let worldRoleName = void 0;
32473
32599
  try {
32474
32600
  const worldEnv = {};
32475
32601
  if (opts.task)
@@ -32570,7 +32696,27 @@ ${detail}`);
32570
32696
  }
32571
32697
  }
32572
32698
  }
32573
- applyPostgresNetworkOverrides(worldEnv, enrichedRepos);
32699
+ applyPostgresNetworkOverrides(worldEnv, enrichedRepos, worldId);
32700
+ const cloneResult = applyPostgresTemplateClone(worldId, enrichedRepos);
32701
+ worldDbNames = cloneResult.worldDbNames.length > 0 ? cloneResult.worldDbNames : void 0;
32702
+ if (worldDbNames !== void 0) {
32703
+ const roleResult = applyPostgresWorldRole(worldId, worldDbNames);
32704
+ worldRoleName = roleResult.worldRoleName;
32705
+ worldEnv["POSTGRESQL_USERNAME"] = roleResult.worldRoleName;
32706
+ worldEnv["POSTGRESQL_PASSWORD"] = roleResult.password;
32707
+ worldEnv["POSTGRESQL_COMMON_USERNAME"] = roleResult.worldRoleName;
32708
+ worldEnv["POSTGRESQL_COMMON_PASSWORD"] = roleResult.password;
32709
+ worldEnv["POSTGRESQL_MERCHANT_USERNAME"] = roleResult.worldRoleName;
32710
+ worldEnv["POSTGRESQL_MERCHANT_PASSWORD"] = roleResult.password;
32711
+ }
32712
+ if (worldDbNames !== void 0 || worldRoleName !== void 0) {
32713
+ this.registry.update(worldId, {
32714
+ ...worldDbNames !== void 0 ? { worldDbNames: [...worldDbNames] } : {},
32715
+ ...worldRoleName !== void 0 ? { worldRoleName } : {}
32716
+ });
32717
+ }
32718
+ const hybridActive = worldDbNames !== void 0;
32719
+ const extraNetworks = hybridActive ? ["olam-shared"] : void 0;
32574
32720
  await this.provider.createWorld({
32575
32721
  id: worldId,
32576
32722
  name: opts.name,
@@ -32580,6 +32726,7 @@ ${detail}`);
32580
32726
  portOffset,
32581
32727
  workspacePath,
32582
32728
  appPorts: appPorts.length > 0 ? appPorts : void 0,
32729
+ ...extraNetworks ? { extraNetworks } : {},
32583
32730
  // Phase 7 A1: per-world cost ceiling sourced from config.cost.
32584
32731
  // Cloudflare provider forwards this to /session/start so the DO
32585
32732
  // can enforce kill-switch on `cost_update` events. Other providers
@@ -32603,7 +32750,16 @@ ${detail}`);
32603
32750
  sm.transition("running");
32604
32751
  this.registry.update(worldId, {
32605
32752
  status: "running",
32606
- ...appPortUrls.length > 0 ? { appPortUrls } : {}
32753
+ ...appPortUrls.length > 0 ? { appPortUrls } : {},
32754
+ // olam-hybrid-shared-postgres A3: persist the cloned per-world DB
32755
+ // names so olam destroy + olam gc can find them later (A7's
32756
+ // world_db_names TEXT JSON column).
32757
+ ...worldDbNames !== void 0 ? { worldDbNames: [...worldDbNames] } : {},
32758
+ // olam-hybrid-shared-postgres A4: persist the role name so olam destroy
32759
+ // can DROP it after the world's DBs are gone (A7's world_role_name
32760
+ // column). Password is NOT persisted — it lives only in the world
32761
+ // container's env and the in-memory return value.
32762
+ ...worldRoleName !== void 0 ? { worldRoleName } : {}
32607
32763
  });
32608
32764
  const containerName = `olam-${worldId}-devbox`;
32609
32765
  try {
@@ -32903,6 +33059,14 @@ ${opts.task}`;
32903
33059
  cleanupWorldTailscale(worldId, this.registry);
32904
33060
  } catch {
32905
33061
  }
33062
+ const worldDbNames = world.worldDbNames;
33063
+ if (worldDbNames !== void 0 && worldDbNames.length > 0) {
33064
+ dropPostgresWorldDbs(worldId, worldDbNames);
33065
+ }
33066
+ const worldRoleName = world.worldRoleName;
33067
+ if (typeof worldRoleName === "string" && worldRoleName.length > 0) {
33068
+ dropPostgresWorldRole(worldId, worldRoleName);
33069
+ }
32906
33070
  try {
32907
33071
  await this.provider.destroyWorld(worldId);
32908
33072
  } catch {
@@ -33050,16 +33214,268 @@ ${opts.task}`;
33050
33214
  return services;
33051
33215
  }
33052
33216
  };
33053
- function applyPostgresNetworkOverrides(worldEnv, enrichedRepos) {
33217
+ function applyPostgresNetworkOverrides(worldEnv, enrichedRepos, worldId) {
33054
33218
  const hasPostgres = enrichedRepos.some((r) => r.manifest?.services?.["postgres"] !== void 0);
33055
33219
  if (!hasPostgres)
33056
33220
  return;
33057
- worldEnv["POSTGRESQL_HOST"] = "postgres";
33221
+ const hasSeedTemplate = enrichedRepos.some((r) => {
33222
+ const pg = r.manifest?.services?.["postgres"];
33223
+ return pg?.seed_template !== void 0;
33224
+ });
33225
+ const host = hasSeedTemplate ? "olam-postgres" : "postgres";
33226
+ worldEnv["POSTGRESQL_HOST"] = host;
33058
33227
  worldEnv["POSTGRESQL_PORT"] = "5432";
33059
- if ("POSTGRESQL_COMMON_HOST" in worldEnv)
33060
- worldEnv["POSTGRESQL_COMMON_HOST"] = "postgres";
33061
- if ("POSTGRESQL_COMMON_PORT" in worldEnv)
33228
+ if (hasSeedTemplate) {
33229
+ worldEnv["POSTGRESQL_COMMON_HOST"] = host;
33062
33230
  worldEnv["POSTGRESQL_COMMON_PORT"] = "5432";
33231
+ if (worldId !== void 0) {
33232
+ assertSafeWorldId(worldId);
33233
+ const seedTemplates = /* @__PURE__ */ new Set();
33234
+ for (const repo of enrichedRepos) {
33235
+ const pg = repo.manifest?.services?.["postgres"];
33236
+ const raw = pg?.seed_template;
33237
+ if (typeof raw === "string")
33238
+ seedTemplates.add(raw);
33239
+ else if (Array.isArray(raw)) {
33240
+ for (const s of raw)
33241
+ if (typeof s === "string")
33242
+ seedTemplates.add(s);
33243
+ }
33244
+ }
33245
+ for (const seed of seedTemplates) {
33246
+ const match = seed.match(/^atlas_([a-z][a-z0-9_]*?)_seed$/i);
33247
+ if (match && match[1] !== void 0) {
33248
+ const purpose = match[1].toUpperCase();
33249
+ const envKey = `POSTGRESQL_${purpose}_DATABASE`;
33250
+ worldEnv[envKey] = deriveWorldDbName(seed, worldId);
33251
+ }
33252
+ }
33253
+ }
33254
+ } else {
33255
+ if ("POSTGRESQL_COMMON_HOST" in worldEnv)
33256
+ worldEnv["POSTGRESQL_COMMON_HOST"] = "postgres";
33257
+ if ("POSTGRESQL_COMMON_PORT" in worldEnv)
33258
+ worldEnv["POSTGRESQL_COMMON_PORT"] = "5432";
33259
+ }
33260
+ }
33261
+ function applyPostgresTemplateClone(worldId, enrichedRepos, options = {}) {
33262
+ assertSafeWorldId(worldId);
33263
+ const container = options.singletonContainer ?? "olam-postgres";
33264
+ const user = options.postgresUser ?? "development";
33265
+ const seedTemplates = [];
33266
+ const postCloneSqlBySeed = /* @__PURE__ */ new Map();
33267
+ for (const repo of enrichedRepos) {
33268
+ const pg = repo.manifest?.services?.["postgres"];
33269
+ if (!pg?.seed_template)
33270
+ continue;
33271
+ const list = Array.isArray(pg.seed_template) ? pg.seed_template : [pg.seed_template];
33272
+ for (const t of list) {
33273
+ if (typeof t === "string" && t.length > 0 && !seedTemplates.includes(t)) {
33274
+ seedTemplates.push(t);
33275
+ }
33276
+ }
33277
+ if (pg.post_clone_sql && typeof pg.post_clone_sql === "object") {
33278
+ for (const [seedKey, sqlList] of Object.entries(pg.post_clone_sql)) {
33279
+ if (!Array.isArray(sqlList))
33280
+ continue;
33281
+ const existing = postCloneSqlBySeed.get(seedKey) ?? [];
33282
+ for (const stmt of sqlList) {
33283
+ if (typeof stmt === "string" && stmt.length > 0)
33284
+ existing.push(stmt);
33285
+ }
33286
+ postCloneSqlBySeed.set(seedKey, existing);
33287
+ }
33288
+ }
33289
+ }
33290
+ if (seedTemplates.length === 0) {
33291
+ return { worldDbNames: [] };
33292
+ }
33293
+ const spawn4 = options.spawn ?? spawnSync4;
33294
+ const seedToWorldDb = /* @__PURE__ */ new Map();
33295
+ for (const seed of seedTemplates) {
33296
+ seedToWorldDb.set(seed, deriveWorldDbName(seed, worldId));
33297
+ }
33298
+ const created = [];
33299
+ for (const seed of seedTemplates) {
33300
+ const worldDb = seedToWorldDb.get(seed);
33301
+ const exists = spawn4("docker", [
33302
+ "exec",
33303
+ container,
33304
+ "psql",
33305
+ "-U",
33306
+ user,
33307
+ "-tAc",
33308
+ `SELECT 1 FROM pg_database WHERE datname='${worldDb}'`
33309
+ ], { encoding: "utf-8" });
33310
+ const dbAlreadyExists = exists.status === 0 && (exists.stdout || "").trim() === "1";
33311
+ if (!dbAlreadyExists) {
33312
+ const sql = `CREATE DATABASE "${worldDb}" TEMPLATE "${seed}"`;
33313
+ const create = spawn4("docker", ["exec", container, "psql", "-U", user, "-d", "postgres", "-v", "ON_ERROR_STOP=1", "-c", sql], { encoding: "utf-8" });
33314
+ if (create.status !== 0) {
33315
+ throw new Error(`failed to CREATE DATABASE "${worldDb}" TEMPLATE "${seed}": ${(create.stderr || "").trim()}`);
33316
+ }
33317
+ }
33318
+ created.push(worldDb);
33319
+ const stmts = postCloneSqlBySeed.get(seed);
33320
+ if (stmts !== void 0 && stmts.length > 0) {
33321
+ for (const rawStmt of stmts) {
33322
+ const stmt = interpolatePostCloneSql(rawStmt, worldId, seedToWorldDb);
33323
+ const fixup = spawn4("docker", ["exec", container, "psql", "-U", user, "-d", worldDb, "-v", "ON_ERROR_STOP=1", "-c", stmt], { encoding: "utf-8" });
33324
+ if (fixup.status !== 0) {
33325
+ throw new Error(`post_clone_sql against "${worldDb}" failed (statement: ${truncate(stmt, 120)}): ${(fixup.stderr || "").trim()}`);
33326
+ }
33327
+ }
33328
+ }
33329
+ }
33330
+ return { worldDbNames: created };
33331
+ }
33332
+ function interpolatePostCloneSql(stmt, worldId, seedToWorldDb) {
33333
+ return stmt.replace(/\$\{([^}]+)\}/g, (_, expr) => {
33334
+ if (expr === "WORLD_ID")
33335
+ return worldId;
33336
+ const dbMatch = expr.match(/^WORLD_DB:(.+)$/);
33337
+ if (dbMatch && dbMatch[1] !== void 0) {
33338
+ const target = seedToWorldDb.get(dbMatch[1]);
33339
+ if (target === void 0) {
33340
+ throw new Error(`post_clone_sql references seed "${dbMatch[1]}" not declared in seed_template`);
33341
+ }
33342
+ return target;
33343
+ }
33344
+ throw new Error(`post_clone_sql contains unknown placeholder "\${${expr}}" \u2014 only \${WORLD_ID} and \${WORLD_DB:<seed>} are supported`);
33345
+ });
33346
+ }
33347
+ function truncate(s, max) {
33348
+ return s.length <= max ? s : s.slice(0, max - 1) + "\u2026";
33349
+ }
33350
+ function deriveWorldRoleName(worldId) {
33351
+ return `olam_world_${worldId}`;
33352
+ }
33353
+ function applyPostgresWorldRole(worldId, worldDbNames, options = {}) {
33354
+ assertSafeWorldId(worldId);
33355
+ if (worldDbNames.length === 0) {
33356
+ throw new Error(`applyPostgresWorldRole called with empty worldDbNames for "${worldId}" \u2014 should only run when applyPostgresTemplateClone produced clones`);
33357
+ }
33358
+ const spawn4 = options.spawn ?? spawnSync4;
33359
+ const container = options.singletonContainer ?? "olam-postgres";
33360
+ const user = options.postgresUser ?? "development";
33361
+ const genPassword = options.generatePassword ?? defaultPasswordGenerator;
33362
+ const worldRoleName = deriveWorldRoleName(worldId);
33363
+ const password = genPassword();
33364
+ const exists = spawn4("docker", [
33365
+ "exec",
33366
+ container,
33367
+ "psql",
33368
+ "-U",
33369
+ user,
33370
+ "-tAc",
33371
+ `SELECT 1 FROM pg_roles WHERE rolname='${escapeSqlLiteral(worldRoleName)}'`
33372
+ ], { encoding: "utf-8" });
33373
+ const roleExists = exists.status === 0 && (exists.stdout || "").trim() === "1";
33374
+ const escapedPassword = escapeSqlLiteral(password);
33375
+ const roleDdl = roleExists ? `ALTER ROLE "${worldRoleName}" WITH LOGIN PASSWORD '${escapedPassword}' NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION` : `CREATE ROLE "${worldRoleName}" WITH LOGIN PASSWORD '${escapedPassword}' NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION`;
33376
+ const dml = spawn4("docker", ["exec", container, "psql", "-U", user, "-d", "postgres", "-v", "ON_ERROR_STOP=1", "-c", roleDdl], { encoding: "utf-8" });
33377
+ if (dml.status !== 0) {
33378
+ throw new Error(`failed to ${roleExists ? "ALTER" : "CREATE"} ROLE "${worldRoleName}": ` + redactCreateRolePassword((dml.stderr || "").trim()));
33379
+ }
33380
+ for (const worldDb of worldDbNames) {
33381
+ const grantConnect = spawn4("docker", [
33382
+ "exec",
33383
+ container,
33384
+ "psql",
33385
+ "-U",
33386
+ user,
33387
+ "-d",
33388
+ "postgres",
33389
+ "-v",
33390
+ "ON_ERROR_STOP=1",
33391
+ "-c",
33392
+ `GRANT CONNECT ON DATABASE "${worldDb}" TO "${worldRoleName}"`
33393
+ ], { encoding: "utf-8" });
33394
+ if (grantConnect.status !== 0) {
33395
+ throw new Error(`failed to GRANT CONNECT on "${worldDb}" to "${worldRoleName}": ${(grantConnect.stderr || "").trim()}`);
33396
+ }
33397
+ const perDbGrants = [
33398
+ `GRANT USAGE ON SCHEMA public TO "${worldRoleName}"`,
33399
+ `GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "${worldRoleName}"`,
33400
+ `GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO "${worldRoleName}"`,
33401
+ // DEFAULT PRIVILEGES for future objects — Rails migrations create new
33402
+ // tables/sequences post-spawn and they need to be grantable.
33403
+ `ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "${worldRoleName}"`,
33404
+ `ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO "${worldRoleName}"`
33405
+ ].join("; ");
33406
+ const grantResult = spawn4("docker", ["exec", container, "psql", "-U", user, "-d", worldDb, "-v", "ON_ERROR_STOP=1", "-c", perDbGrants], { encoding: "utf-8" });
33407
+ if (grantResult.status !== 0) {
33408
+ throw new Error(`failed to GRANT privileges on "${worldDb}" to "${worldRoleName}": ${(grantResult.stderr || "").trim()}`);
33409
+ }
33410
+ }
33411
+ return { worldRoleName, password };
33412
+ }
33413
+ function defaultPasswordGenerator() {
33414
+ return crypto5.randomBytes(24).toString("base64url");
33415
+ }
33416
+ function escapeSqlLiteral(s) {
33417
+ return s.replace(/'/g, "''");
33418
+ }
33419
+ function redactCreateRolePassword(s) {
33420
+ return s.replace(/PASSWORD\s+'[^']*'/g, "PASSWORD '<scrubbed>'");
33421
+ }
33422
+ function dropPostgresWorldDbs(worldId, worldDbNames, options = {}) {
33423
+ if (worldDbNames.length === 0)
33424
+ return;
33425
+ assertSafeWorldId(worldId);
33426
+ const spawn4 = options.spawn ?? spawnSync4;
33427
+ const container = options.container ?? "olam-postgres";
33428
+ const user = options.user ?? "development";
33429
+ for (const db of worldDbNames) {
33430
+ const drop = spawn4("docker", [
33431
+ "exec",
33432
+ container,
33433
+ "psql",
33434
+ "-U",
33435
+ user,
33436
+ "-d",
33437
+ "postgres",
33438
+ "-c",
33439
+ `DROP DATABASE IF EXISTS "${db}" WITH (FORCE)`
33440
+ ], { encoding: "utf-8" });
33441
+ if (drop.status !== 0) {
33442
+ console.warn(`[manager] destroyWorld(${worldId}): failed to DROP "${db}" from singleton: ${(drop.stderr || "").trim()}`);
33443
+ }
33444
+ }
33445
+ }
33446
+ function dropPostgresWorldRole(worldId, worldRoleName, options = {}) {
33447
+ if (!worldRoleName)
33448
+ return;
33449
+ assertSafeWorldId(worldId);
33450
+ const spawn4 = options.spawn ?? spawnSync4;
33451
+ const container = options.container ?? "olam-postgres";
33452
+ const user = options.user ?? "development";
33453
+ const cleanup = spawn4("docker", [
33454
+ "exec",
33455
+ container,
33456
+ "psql",
33457
+ "-U",
33458
+ user,
33459
+ "-d",
33460
+ "postgres",
33461
+ "-c",
33462
+ `DROP OWNED BY "${worldRoleName}" CASCADE; DROP ROLE IF EXISTS "${worldRoleName}"`
33463
+ ], { encoding: "utf-8" });
33464
+ if (cleanup.status !== 0) {
33465
+ console.warn(`[manager] destroyWorld(${worldId}): failed to DROP role "${worldRoleName}" from singleton: ${(cleanup.stderr || "").trim()}`);
33466
+ }
33467
+ }
33468
+ function assertSafeWorldId(worldId) {
33469
+ if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(worldId)) {
33470
+ throw new Error(`world id "${worldId}" contains characters outside [a-z0-9-] \u2014 hybrid-postgres + post_clone_sql interpolation require the name-generator's [a-z0-9-]+ alphabet`);
33471
+ }
33472
+ }
33473
+ function deriveWorldDbName(seed, worldId) {
33474
+ if (seed.endsWith("_seed")) {
33475
+ const prefix = seed.slice(0, -"_seed".length);
33476
+ return `${prefix}_world_${worldId}`;
33477
+ }
33478
+ return `${seed}_world_${worldId}`;
33063
33479
  }
33064
33480
 
33065
33481
  // ../core/dist/cost/tracker.js
@@ -33985,7 +34401,7 @@ function isCloudflaredAvailable() {
33985
34401
  }
33986
34402
  }
33987
34403
  function startTunnel(port) {
33988
- return new Promise((resolve9, reject2) => {
34404
+ return new Promise((resolve10, reject2) => {
33989
34405
  const child = spawn3("cloudflared", ["tunnel", "--url", `http://localhost:${port}`], {
33990
34406
  stdio: ["ignore", "pipe", "pipe"],
33991
34407
  detached: false
@@ -34007,7 +34423,7 @@ function startTunnel(port) {
34007
34423
  if (match) {
34008
34424
  resolved = true;
34009
34425
  clearTimeout(timeout);
34010
- resolve9(match[0]);
34426
+ resolve10(match[0]);
34011
34427
  }
34012
34428
  }
34013
34429
  child.stdout?.on("data", scan);
@@ -34075,8 +34491,8 @@ var DashboardManager = class {
34075
34491
  }
34076
34492
  throw err;
34077
34493
  }
34078
- await new Promise((resolve9, reject2) => {
34079
- this.server.on("listening", resolve9);
34494
+ await new Promise((resolve10, reject2) => {
34495
+ this.server.on("listening", resolve10);
34080
34496
  this.server.on("error", reject2);
34081
34497
  });
34082
34498
  this.info = { localUrl: `http://localhost:${port}` };
@@ -34122,8 +34538,8 @@ var DashboardManager = class {
34122
34538
  async stop() {
34123
34539
  stopTunnel();
34124
34540
  if (this.server) {
34125
- await new Promise((resolve9) => {
34126
- this.server.close(() => resolve9());
34541
+ await new Promise((resolve10) => {
34542
+ this.server.close(() => resolve10());
34127
34543
  });
34128
34544
  this.server = null;
34129
34545
  }
@@ -34237,7 +34653,7 @@ var PleriClient = class {
34237
34653
 
34238
34654
  // ../mcp-server/src/env-loader.ts
34239
34655
  import { readFileSync as readFileSync17, existsSync as existsSync23, statSync as statSync7 } from "node:fs";
34240
- import { join as join29, dirname as dirname15, resolve as resolve8 } from "node:path";
34656
+ import { join as join29, dirname as dirname15, resolve as resolve9 } from "node:path";
34241
34657
  var PROJECT_MARKERS = [
34242
34658
  ".olam/config.yaml",
34243
34659
  ".olam/config.yml",
@@ -34245,8 +34661,8 @@ var PROJECT_MARKERS = [
34245
34661
  "olam.yml"
34246
34662
  ];
34247
34663
  function findProjectRoot2(startDir) {
34248
- let dir = resolve8(startDir);
34249
- const root = resolve8("/");
34664
+ let dir = resolve9(startDir);
34665
+ const root = resolve9("/");
34250
34666
  while (true) {
34251
34667
  for (const marker of PROJECT_MARKERS) {
34252
34668
  if (existsSync23(join29(dir, marker))) return dir;