@pleri/olam-cli 0.1.120 → 0.1.126

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 (67) 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/create.d.ts.map +1 -1
  6. package/dist/commands/create.js +43 -0
  7. package/dist/commands/create.js.map +1 -1
  8. package/dist/commands/mcp/install-shared.d.ts +2 -0
  9. package/dist/commands/mcp/install-shared.d.ts.map +1 -1
  10. package/dist/commands/mcp/install-shared.js +13 -0
  11. package/dist/commands/mcp/install-shared.js.map +1 -1
  12. package/dist/commands/mcp/uninstall.d.ts.map +1 -1
  13. package/dist/commands/mcp/uninstall.js +1 -10
  14. package/dist/commands/mcp/uninstall.js.map +1 -1
  15. package/dist/commands/memory/_paths.d.ts +15 -0
  16. package/dist/commands/memory/_paths.d.ts.map +1 -0
  17. package/dist/commands/memory/_paths.js +34 -0
  18. package/dist/commands/memory/_paths.js.map +1 -0
  19. package/dist/commands/memory/index.d.ts +17 -0
  20. package/dist/commands/memory/index.d.ts.map +1 -0
  21. package/dist/commands/memory/index.js +34 -0
  22. package/dist/commands/memory/index.js.map +1 -0
  23. package/dist/commands/memory/install.d.ts +57 -0
  24. package/dist/commands/memory/install.d.ts.map +1 -0
  25. package/dist/commands/memory/install.js +114 -0
  26. package/dist/commands/memory/install.js.map +1 -0
  27. package/dist/commands/memory/logs.d.ts +15 -0
  28. package/dist/commands/memory/logs.d.ts.map +1 -0
  29. package/dist/commands/memory/logs.js +45 -0
  30. package/dist/commands/memory/logs.js.map +1 -0
  31. package/dist/commands/memory/secret.d.ts +16 -0
  32. package/dist/commands/memory/secret.d.ts.map +1 -0
  33. package/dist/commands/memory/secret.js +79 -0
  34. package/dist/commands/memory/secret.js.map +1 -0
  35. package/dist/commands/memory/start.d.ts +23 -0
  36. package/dist/commands/memory/start.d.ts.map +1 -0
  37. package/dist/commands/memory/start.js +166 -0
  38. package/dist/commands/memory/start.js.map +1 -0
  39. package/dist/commands/memory/status.d.ts +25 -0
  40. package/dist/commands/memory/status.d.ts.map +1 -0
  41. package/dist/commands/memory/status.js +101 -0
  42. package/dist/commands/memory/status.js.map +1 -0
  43. package/dist/commands/memory/stop.d.ts +12 -0
  44. package/dist/commands/memory/stop.d.ts.map +1 -0
  45. package/dist/commands/memory/stop.js +81 -0
  46. package/dist/commands/memory/stop.js.map +1 -0
  47. package/dist/commands/memory/uninstall.d.ts +19 -0
  48. package/dist/commands/memory/uninstall.d.ts.map +1 -0
  49. package/dist/commands/memory/uninstall.js +60 -0
  50. package/dist/commands/memory/uninstall.js.map +1 -0
  51. package/dist/commands/seed.d.ts +27 -0
  52. package/dist/commands/seed.d.ts.map +1 -0
  53. package/dist/commands/seed.js +303 -0
  54. package/dist/commands/seed.js.map +1 -0
  55. package/dist/image-digests.json +3 -3
  56. package/dist/index.js +1835 -259
  57. package/dist/index.js.map +1 -1
  58. package/dist/lib/memory-secret.d.ts +48 -0
  59. package/dist/lib/memory-secret.d.ts.map +1 -0
  60. package/dist/lib/memory-secret.js +92 -0
  61. package/dist/lib/memory-secret.js.map +1 -0
  62. package/dist/lib/world-mcp-register.d.ts +98 -0
  63. package/dist/lib/world-mcp-register.d.ts.map +1 -0
  64. package/dist/lib/world-mcp-register.js +117 -0
  65. package/dist/lib/world-mcp-register.js.map +1 -0
  66. package/dist/mcp-server.js +502 -65
  67. 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
  }
@@ -23107,6 +23218,18 @@ function readHostCpToken() {
23107
23218
  return "";
23108
23219
  }
23109
23220
  }
23221
+ function readMemorySecret() {
23222
+ const fromEnv = process.env["OLAM_MEMORY_SECRET"];
23223
+ if (fromEnv && fromEnv.length > 0)
23224
+ return fromEnv;
23225
+ const file = path7.join(os5.homedir(), ".olam", "memory-secret");
23226
+ try {
23227
+ return fs5.readFileSync(file, "utf-8").trim();
23228
+ } catch {
23229
+ return "";
23230
+ }
23231
+ }
23232
+ var AGENTMEMORY_HOST_INTERNAL_URL = "http://host.docker.internal:3111";
23110
23233
  function sanitizeContainerName(name) {
23111
23234
  return name.replace(/[^a-zA-Z0-9_.-]/g, "-");
23112
23235
  }
@@ -23236,6 +23359,7 @@ var createWorldContainer = async (docker, worldId, worldName, image, env, resour
23236
23359
  const labels = olamLabels(worldId, worldName);
23237
23360
  const authSecret = readAuthSecret();
23238
23361
  const hostCpToken = readHostCpToken();
23362
+ const memorySecret = readMemorySecret();
23239
23363
  const worldEnv = {
23240
23364
  OLAM_WORLD_ID: worldId,
23241
23365
  OLAM_WORLD_NAME: worldName,
@@ -23252,6 +23376,14 @@ var createWorldContainer = async (docker, worldId, worldName, image, env, resour
23252
23376
  OLAM_HOST_CP_URL: "http://host.docker.internal:19000",
23253
23377
  ...authSecret ? { OLAM_AUTH_SECRET: authSecret } : {},
23254
23378
  ...hostCpToken ? { OLAM_HOST_CP_TOKEN: hostCpToken } : {},
23379
+ // Phase B1 — agent-memory wiring. Both vars are injected together
23380
+ // (or neither): without a secret the URL is useless. The in-world
23381
+ // @agentmemory/mcp shim (Phase B2/B3) reads these env vars to find
23382
+ // the host's REST endpoint.
23383
+ ...memorySecret ? {
23384
+ AGENTMEMORY_URL: AGENTMEMORY_HOST_INTERNAL_URL,
23385
+ AGENTMEMORY_SECRET: memorySecret
23386
+ } : {},
23255
23387
  ...env
23256
23388
  };
23257
23389
  const envList = Object.entries(worldEnv).map(([k, v]) => `${k}=${v}`);
@@ -23362,7 +23494,7 @@ var stopAndRemove = async (container) => {
23362
23494
 
23363
23495
  // ../adapters/dist/docker/exec.js
23364
23496
  import { PassThrough } from "node:stream";
23365
- var demuxStream = (stream) => new Promise((resolve9, reject2) => {
23497
+ var demuxStream = (stream) => new Promise((resolve10, reject2) => {
23366
23498
  const stdoutChunks = [];
23367
23499
  const stderrChunks = [];
23368
23500
  const stdout = new PassThrough();
@@ -23376,7 +23508,7 @@ var demuxStream = (stream) => new Promise((resolve9, reject2) => {
23376
23508
  stream.pipe(stdout);
23377
23509
  }
23378
23510
  stream.on("end", () => {
23379
- resolve9({
23511
+ resolve10({
23380
23512
  stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
23381
23513
  stderr: Buffer.concat(stderrChunks).toString("utf-8")
23382
23514
  });
@@ -23554,6 +23686,19 @@ var DockerProvider = class extends ComputeProvider {
23554
23686
  }
23555
23687
  const mergedEnv = { ...serviceEnv, ...config2.env };
23556
23688
  const container = await createWorldContainer(this.docker, id, name, config2.image, mergedEnv, config2.resources, config2.workspacePath, config2.portOffset, config2.appPorts, config2.cacheArch);
23689
+ for (const networkName3 of config2.extraNetworks ?? []) {
23690
+ try {
23691
+ const network = this.docker.getNetwork(networkName3);
23692
+ await network.connect({ Container: container.id });
23693
+ } catch (err) {
23694
+ const statusCode = err.statusCode;
23695
+ if (statusCode === 404) {
23696
+ throw new Error(`extra network "${networkName3}" not found \u2014 run \`olam bootstrap\` to recreate it`);
23697
+ }
23698
+ if (statusCode !== 403)
23699
+ throw err;
23700
+ }
23701
+ }
23557
23702
  return new DockerWorld(id, name, container, "running");
23558
23703
  }
23559
23704
  // -----------------------------------------------------------------------
@@ -23721,7 +23866,7 @@ var SSHConnectionPool = class {
23721
23866
  // -----------------------------------------------------------------------
23722
23867
  async exec(host, command) {
23723
23868
  const client = await this.getConnection(host);
23724
- return new Promise((resolve9, reject2) => {
23869
+ return new Promise((resolve10, reject2) => {
23725
23870
  client.exec(command, (err, stream) => {
23726
23871
  if (err) {
23727
23872
  reject2(new Error(`SSH exec failed on ${host}: ${err.message}`));
@@ -23736,7 +23881,7 @@ var SSHConnectionPool = class {
23736
23881
  stderr += data.toString();
23737
23882
  });
23738
23883
  stream.on("close", (code) => {
23739
- resolve9({
23884
+ resolve10({
23740
23885
  exitCode: code ?? 0,
23741
23886
  stdout: stdout.trimEnd(),
23742
23887
  stderr: stderr.trimEnd()
@@ -23767,10 +23912,10 @@ var SSHConnectionPool = class {
23767
23912
  throw new Error(`No SSH configuration found for host: ${host}`);
23768
23913
  }
23769
23914
  const client = new SSHClient();
23770
- return new Promise((resolve9, reject2) => {
23915
+ return new Promise((resolve10, reject2) => {
23771
23916
  client.on("ready", () => {
23772
23917
  this.connections.set(host, client);
23773
- resolve9(client);
23918
+ resolve10(client);
23774
23919
  }).on("error", (err) => {
23775
23920
  this.connections.delete(host);
23776
23921
  reject2(new Error(`SSH connection to ${host} failed: ${err.message}`));
@@ -24750,7 +24895,7 @@ function register6(server, ctx, initError) {
24750
24895
  }
24751
24896
  } catch {
24752
24897
  }
24753
- await new Promise((resolve9) => setTimeout(resolve9, POLL_INTERVAL_MS));
24898
+ await new Promise((resolve10) => setTimeout(resolve10, POLL_INTERVAL_MS));
24754
24899
  }
24755
24900
  }
24756
24901
  if (authenticated) {
@@ -26990,15 +27135,15 @@ ${JSON.stringify({ error: reason })}`
26990
27135
  unlinkSync2(udsPath);
26991
27136
  } catch {
26992
27137
  }
26993
- await new Promise((resolve9, reject2) => {
26994
- server.listen(udsPath, () => resolve9());
27138
+ await new Promise((resolve10, reject2) => {
27139
+ server.listen(udsPath, () => resolve10());
26995
27140
  server.once("error", reject2);
26996
27141
  });
26997
27142
  chmodSync3(udsPath, 384);
26998
27143
  port = 0;
26999
27144
  } else {
27000
- await new Promise((resolve9, reject2) => {
27001
- server.listen(opts.port ?? 0, "127.0.0.1", () => resolve9());
27145
+ await new Promise((resolve10, reject2) => {
27146
+ server.listen(opts.port ?? 0, "127.0.0.1", () => resolve10());
27002
27147
  server.once("error", reject2);
27003
27148
  });
27004
27149
  const addr = server.address();
@@ -27014,10 +27159,10 @@ ${JSON.stringify({ error: reason })}`
27014
27159
  } catch {
27015
27160
  }
27016
27161
  await Promise.race([
27017
- new Promise((resolve9, reject2) => {
27018
- server.close((err) => err ? reject2(err) : resolve9());
27162
+ new Promise((resolve10, reject2) => {
27163
+ server.close((err) => err ? reject2(err) : resolve10());
27019
27164
  }),
27020
- new Promise((resolve9) => setTimeout(resolve9, 5e3))
27165
+ new Promise((resolve10) => setTimeout(resolve10, 5e3))
27021
27166
  ]);
27022
27167
  if (udsPath) {
27023
27168
  try {
@@ -27349,10 +27494,10 @@ async function acquireLaunchSlot() {
27349
27494
  _inFlightLaunches++;
27350
27495
  return releaseLaunchSlot;
27351
27496
  }
27352
- return new Promise((resolve9) => {
27497
+ return new Promise((resolve10) => {
27353
27498
  _launchQueue.push(() => {
27354
27499
  _inFlightLaunches++;
27355
- resolve9(releaseLaunchSlot);
27500
+ resolve10(releaseLaunchSlot);
27356
27501
  });
27357
27502
  });
27358
27503
  }
@@ -27368,8 +27513,8 @@ function resolveShootsRoot() {
27368
27513
  }
27369
27514
  function isUnderRoot(absPath, root) {
27370
27515
  if (absPath === root) return true;
27371
- const sep = root.endsWith("/") ? "" : "/";
27372
- return absPath.startsWith(root + sep);
27516
+ const sep2 = root.endsWith("/") ? "" : "/";
27517
+ return absPath.startsWith(root + sep2);
27373
27518
  }
27374
27519
  function deriveAllowedPaths(shots) {
27375
27520
  const paths = /* @__PURE__ */ new Set();
@@ -29637,7 +29782,7 @@ function loadConfig(startDir) {
29637
29782
 
29638
29783
  // ../core/dist/world/manager.js
29639
29784
  import * as crypto5 from "node:crypto";
29640
- import { execSync as execSync5 } from "node:child_process";
29785
+ import { execSync as execSync5, spawnSync as spawnSync4 } from "node:child_process";
29641
29786
  import * as fs22 from "node:fs";
29642
29787
  import * as os13 from "node:os";
29643
29788
  import * as path25 from "node:path";
@@ -29864,7 +30009,7 @@ import * as path17 from "node:path";
29864
30009
 
29865
30010
  // ../core/dist/kg/storage-paths.js
29866
30011
  import { homedir as homedir10 } from "node:os";
29867
- import { join as join18, resolve as resolve4 } from "node:path";
30012
+ import { join as join18, resolve as resolve5 } from "node:path";
29868
30013
 
29869
30014
  // ../core/dist/world/workspace-name.js
29870
30015
  var InvalidWorkspaceNameError = class extends Error {
@@ -29901,7 +30046,7 @@ function assertWithinPrefix(path28, prefix, label) {
29901
30046
  function kgPristinePath(workspace) {
29902
30047
  validateWorkspaceName(workspace);
29903
30048
  const root = kgRoot();
29904
- const path28 = resolve4(join18(root, workspace));
30049
+ const path28 = resolve5(join18(root, workspace));
29905
30050
  assertWithinPrefix(path28, root, "kgPristinePath");
29906
30051
  return path28;
29907
30052
  }
@@ -32470,6 +32615,8 @@ ${detail}`);
32470
32615
  const hostPort = override !== void 0 ? override : ap.port + 1e4 + portOffset;
32471
32616
  return { repoName: ap.name, internalPort: ap.port, hostPort, url: `http://localhost:${hostPort}` };
32472
32617
  });
32618
+ let worldDbNames = void 0;
32619
+ let worldRoleName = void 0;
32473
32620
  try {
32474
32621
  const worldEnv = {};
32475
32622
  if (opts.task)
@@ -32570,7 +32717,27 @@ ${detail}`);
32570
32717
  }
32571
32718
  }
32572
32719
  }
32573
- applyPostgresNetworkOverrides(worldEnv, enrichedRepos);
32720
+ applyPostgresNetworkOverrides(worldEnv, enrichedRepos, worldId);
32721
+ const cloneResult = applyPostgresTemplateClone(worldId, enrichedRepos);
32722
+ worldDbNames = cloneResult.worldDbNames.length > 0 ? cloneResult.worldDbNames : void 0;
32723
+ if (worldDbNames !== void 0) {
32724
+ const roleResult = applyPostgresWorldRole(worldId, worldDbNames);
32725
+ worldRoleName = roleResult.worldRoleName;
32726
+ worldEnv["POSTGRESQL_USERNAME"] = roleResult.worldRoleName;
32727
+ worldEnv["POSTGRESQL_PASSWORD"] = roleResult.password;
32728
+ worldEnv["POSTGRESQL_COMMON_USERNAME"] = roleResult.worldRoleName;
32729
+ worldEnv["POSTGRESQL_COMMON_PASSWORD"] = roleResult.password;
32730
+ worldEnv["POSTGRESQL_MERCHANT_USERNAME"] = roleResult.worldRoleName;
32731
+ worldEnv["POSTGRESQL_MERCHANT_PASSWORD"] = roleResult.password;
32732
+ }
32733
+ if (worldDbNames !== void 0 || worldRoleName !== void 0) {
32734
+ this.registry.update(worldId, {
32735
+ ...worldDbNames !== void 0 ? { worldDbNames: [...worldDbNames] } : {},
32736
+ ...worldRoleName !== void 0 ? { worldRoleName } : {}
32737
+ });
32738
+ }
32739
+ const hybridActive = worldDbNames !== void 0;
32740
+ const extraNetworks = hybridActive ? ["olam-shared"] : void 0;
32574
32741
  await this.provider.createWorld({
32575
32742
  id: worldId,
32576
32743
  name: opts.name,
@@ -32580,6 +32747,7 @@ ${detail}`);
32580
32747
  portOffset,
32581
32748
  workspacePath,
32582
32749
  appPorts: appPorts.length > 0 ? appPorts : void 0,
32750
+ ...extraNetworks ? { extraNetworks } : {},
32583
32751
  // Phase 7 A1: per-world cost ceiling sourced from config.cost.
32584
32752
  // Cloudflare provider forwards this to /session/start so the DO
32585
32753
  // can enforce kill-switch on `cost_update` events. Other providers
@@ -32603,7 +32771,16 @@ ${detail}`);
32603
32771
  sm.transition("running");
32604
32772
  this.registry.update(worldId, {
32605
32773
  status: "running",
32606
- ...appPortUrls.length > 0 ? { appPortUrls } : {}
32774
+ ...appPortUrls.length > 0 ? { appPortUrls } : {},
32775
+ // olam-hybrid-shared-postgres A3: persist the cloned per-world DB
32776
+ // names so olam destroy + olam gc can find them later (A7's
32777
+ // world_db_names TEXT JSON column).
32778
+ ...worldDbNames !== void 0 ? { worldDbNames: [...worldDbNames] } : {},
32779
+ // olam-hybrid-shared-postgres A4: persist the role name so olam destroy
32780
+ // can DROP it after the world's DBs are gone (A7's world_role_name
32781
+ // column). Password is NOT persisted — it lives only in the world
32782
+ // container's env and the in-memory return value.
32783
+ ...worldRoleName !== void 0 ? { worldRoleName } : {}
32607
32784
  });
32608
32785
  const containerName = `olam-${worldId}-devbox`;
32609
32786
  try {
@@ -32903,6 +33080,14 @@ ${opts.task}`;
32903
33080
  cleanupWorldTailscale(worldId, this.registry);
32904
33081
  } catch {
32905
33082
  }
33083
+ const worldDbNames = world.worldDbNames;
33084
+ if (worldDbNames !== void 0 && worldDbNames.length > 0) {
33085
+ dropPostgresWorldDbs(worldId, worldDbNames);
33086
+ }
33087
+ const worldRoleName = world.worldRoleName;
33088
+ if (typeof worldRoleName === "string" && worldRoleName.length > 0) {
33089
+ dropPostgresWorldRole(worldId, worldRoleName);
33090
+ }
32906
33091
  try {
32907
33092
  await this.provider.destroyWorld(worldId);
32908
33093
  } catch {
@@ -33050,16 +33235,268 @@ ${opts.task}`;
33050
33235
  return services;
33051
33236
  }
33052
33237
  };
33053
- function applyPostgresNetworkOverrides(worldEnv, enrichedRepos) {
33238
+ function applyPostgresNetworkOverrides(worldEnv, enrichedRepos, worldId) {
33054
33239
  const hasPostgres = enrichedRepos.some((r) => r.manifest?.services?.["postgres"] !== void 0);
33055
33240
  if (!hasPostgres)
33056
33241
  return;
33057
- worldEnv["POSTGRESQL_HOST"] = "postgres";
33242
+ const hasSeedTemplate = enrichedRepos.some((r) => {
33243
+ const pg = r.manifest?.services?.["postgres"];
33244
+ return pg?.seed_template !== void 0;
33245
+ });
33246
+ const host = hasSeedTemplate ? "olam-postgres" : "postgres";
33247
+ worldEnv["POSTGRESQL_HOST"] = host;
33058
33248
  worldEnv["POSTGRESQL_PORT"] = "5432";
33059
- if ("POSTGRESQL_COMMON_HOST" in worldEnv)
33060
- worldEnv["POSTGRESQL_COMMON_HOST"] = "postgres";
33061
- if ("POSTGRESQL_COMMON_PORT" in worldEnv)
33249
+ if (hasSeedTemplate) {
33250
+ worldEnv["POSTGRESQL_COMMON_HOST"] = host;
33062
33251
  worldEnv["POSTGRESQL_COMMON_PORT"] = "5432";
33252
+ if (worldId !== void 0) {
33253
+ assertSafeWorldId(worldId);
33254
+ const seedTemplates = /* @__PURE__ */ new Set();
33255
+ for (const repo of enrichedRepos) {
33256
+ const pg = repo.manifest?.services?.["postgres"];
33257
+ const raw = pg?.seed_template;
33258
+ if (typeof raw === "string")
33259
+ seedTemplates.add(raw);
33260
+ else if (Array.isArray(raw)) {
33261
+ for (const s of raw)
33262
+ if (typeof s === "string")
33263
+ seedTemplates.add(s);
33264
+ }
33265
+ }
33266
+ for (const seed of seedTemplates) {
33267
+ const match = seed.match(/^atlas_([a-z][a-z0-9_]*?)_seed$/i);
33268
+ if (match && match[1] !== void 0) {
33269
+ const purpose = match[1].toUpperCase();
33270
+ const envKey = `POSTGRESQL_${purpose}_DATABASE`;
33271
+ worldEnv[envKey] = deriveWorldDbName(seed, worldId);
33272
+ }
33273
+ }
33274
+ }
33275
+ } else {
33276
+ if ("POSTGRESQL_COMMON_HOST" in worldEnv)
33277
+ worldEnv["POSTGRESQL_COMMON_HOST"] = "postgres";
33278
+ if ("POSTGRESQL_COMMON_PORT" in worldEnv)
33279
+ worldEnv["POSTGRESQL_COMMON_PORT"] = "5432";
33280
+ }
33281
+ }
33282
+ function applyPostgresTemplateClone(worldId, enrichedRepos, options = {}) {
33283
+ assertSafeWorldId(worldId);
33284
+ const container = options.singletonContainer ?? "olam-postgres";
33285
+ const user = options.postgresUser ?? "development";
33286
+ const seedTemplates = [];
33287
+ const postCloneSqlBySeed = /* @__PURE__ */ new Map();
33288
+ for (const repo of enrichedRepos) {
33289
+ const pg = repo.manifest?.services?.["postgres"];
33290
+ if (!pg?.seed_template)
33291
+ continue;
33292
+ const list = Array.isArray(pg.seed_template) ? pg.seed_template : [pg.seed_template];
33293
+ for (const t of list) {
33294
+ if (typeof t === "string" && t.length > 0 && !seedTemplates.includes(t)) {
33295
+ seedTemplates.push(t);
33296
+ }
33297
+ }
33298
+ if (pg.post_clone_sql && typeof pg.post_clone_sql === "object") {
33299
+ for (const [seedKey, sqlList] of Object.entries(pg.post_clone_sql)) {
33300
+ if (!Array.isArray(sqlList))
33301
+ continue;
33302
+ const existing = postCloneSqlBySeed.get(seedKey) ?? [];
33303
+ for (const stmt of sqlList) {
33304
+ if (typeof stmt === "string" && stmt.length > 0)
33305
+ existing.push(stmt);
33306
+ }
33307
+ postCloneSqlBySeed.set(seedKey, existing);
33308
+ }
33309
+ }
33310
+ }
33311
+ if (seedTemplates.length === 0) {
33312
+ return { worldDbNames: [] };
33313
+ }
33314
+ const spawn4 = options.spawn ?? spawnSync4;
33315
+ const seedToWorldDb = /* @__PURE__ */ new Map();
33316
+ for (const seed of seedTemplates) {
33317
+ seedToWorldDb.set(seed, deriveWorldDbName(seed, worldId));
33318
+ }
33319
+ const created = [];
33320
+ for (const seed of seedTemplates) {
33321
+ const worldDb = seedToWorldDb.get(seed);
33322
+ const exists = spawn4("docker", [
33323
+ "exec",
33324
+ container,
33325
+ "psql",
33326
+ "-U",
33327
+ user,
33328
+ "-tAc",
33329
+ `SELECT 1 FROM pg_database WHERE datname='${worldDb}'`
33330
+ ], { encoding: "utf-8" });
33331
+ const dbAlreadyExists = exists.status === 0 && (exists.stdout || "").trim() === "1";
33332
+ if (!dbAlreadyExists) {
33333
+ const sql = `CREATE DATABASE "${worldDb}" TEMPLATE "${seed}"`;
33334
+ const create = spawn4("docker", ["exec", container, "psql", "-U", user, "-d", "postgres", "-v", "ON_ERROR_STOP=1", "-c", sql], { encoding: "utf-8" });
33335
+ if (create.status !== 0) {
33336
+ throw new Error(`failed to CREATE DATABASE "${worldDb}" TEMPLATE "${seed}": ${(create.stderr || "").trim()}`);
33337
+ }
33338
+ }
33339
+ created.push(worldDb);
33340
+ const stmts = postCloneSqlBySeed.get(seed);
33341
+ if (stmts !== void 0 && stmts.length > 0) {
33342
+ for (const rawStmt of stmts) {
33343
+ const stmt = interpolatePostCloneSql(rawStmt, worldId, seedToWorldDb);
33344
+ const fixup = spawn4("docker", ["exec", container, "psql", "-U", user, "-d", worldDb, "-v", "ON_ERROR_STOP=1", "-c", stmt], { encoding: "utf-8" });
33345
+ if (fixup.status !== 0) {
33346
+ throw new Error(`post_clone_sql against "${worldDb}" failed (statement: ${truncate(stmt, 120)}): ${(fixup.stderr || "").trim()}`);
33347
+ }
33348
+ }
33349
+ }
33350
+ }
33351
+ return { worldDbNames: created };
33352
+ }
33353
+ function interpolatePostCloneSql(stmt, worldId, seedToWorldDb) {
33354
+ return stmt.replace(/\$\{([^}]+)\}/g, (_, expr) => {
33355
+ if (expr === "WORLD_ID")
33356
+ return worldId;
33357
+ const dbMatch = expr.match(/^WORLD_DB:(.+)$/);
33358
+ if (dbMatch && dbMatch[1] !== void 0) {
33359
+ const target = seedToWorldDb.get(dbMatch[1]);
33360
+ if (target === void 0) {
33361
+ throw new Error(`post_clone_sql references seed "${dbMatch[1]}" not declared in seed_template`);
33362
+ }
33363
+ return target;
33364
+ }
33365
+ throw new Error(`post_clone_sql contains unknown placeholder "\${${expr}}" \u2014 only \${WORLD_ID} and \${WORLD_DB:<seed>} are supported`);
33366
+ });
33367
+ }
33368
+ function truncate(s, max) {
33369
+ return s.length <= max ? s : s.slice(0, max - 1) + "\u2026";
33370
+ }
33371
+ function deriveWorldRoleName(worldId) {
33372
+ return `olam_world_${worldId}`;
33373
+ }
33374
+ function applyPostgresWorldRole(worldId, worldDbNames, options = {}) {
33375
+ assertSafeWorldId(worldId);
33376
+ if (worldDbNames.length === 0) {
33377
+ throw new Error(`applyPostgresWorldRole called with empty worldDbNames for "${worldId}" \u2014 should only run when applyPostgresTemplateClone produced clones`);
33378
+ }
33379
+ const spawn4 = options.spawn ?? spawnSync4;
33380
+ const container = options.singletonContainer ?? "olam-postgres";
33381
+ const user = options.postgresUser ?? "development";
33382
+ const genPassword = options.generatePassword ?? defaultPasswordGenerator;
33383
+ const worldRoleName = deriveWorldRoleName(worldId);
33384
+ const password = genPassword();
33385
+ const exists = spawn4("docker", [
33386
+ "exec",
33387
+ container,
33388
+ "psql",
33389
+ "-U",
33390
+ user,
33391
+ "-tAc",
33392
+ `SELECT 1 FROM pg_roles WHERE rolname='${escapeSqlLiteral(worldRoleName)}'`
33393
+ ], { encoding: "utf-8" });
33394
+ const roleExists = exists.status === 0 && (exists.stdout || "").trim() === "1";
33395
+ const escapedPassword = escapeSqlLiteral(password);
33396
+ 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`;
33397
+ const dml = spawn4("docker", ["exec", container, "psql", "-U", user, "-d", "postgres", "-v", "ON_ERROR_STOP=1", "-c", roleDdl], { encoding: "utf-8" });
33398
+ if (dml.status !== 0) {
33399
+ throw new Error(`failed to ${roleExists ? "ALTER" : "CREATE"} ROLE "${worldRoleName}": ` + redactCreateRolePassword((dml.stderr || "").trim()));
33400
+ }
33401
+ for (const worldDb of worldDbNames) {
33402
+ const grantConnect = spawn4("docker", [
33403
+ "exec",
33404
+ container,
33405
+ "psql",
33406
+ "-U",
33407
+ user,
33408
+ "-d",
33409
+ "postgres",
33410
+ "-v",
33411
+ "ON_ERROR_STOP=1",
33412
+ "-c",
33413
+ `GRANT CONNECT ON DATABASE "${worldDb}" TO "${worldRoleName}"`
33414
+ ], { encoding: "utf-8" });
33415
+ if (grantConnect.status !== 0) {
33416
+ throw new Error(`failed to GRANT CONNECT on "${worldDb}" to "${worldRoleName}": ${(grantConnect.stderr || "").trim()}`);
33417
+ }
33418
+ const perDbGrants = [
33419
+ `GRANT USAGE ON SCHEMA public TO "${worldRoleName}"`,
33420
+ `GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "${worldRoleName}"`,
33421
+ `GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO "${worldRoleName}"`,
33422
+ // DEFAULT PRIVILEGES for future objects — Rails migrations create new
33423
+ // tables/sequences post-spawn and they need to be grantable.
33424
+ `ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "${worldRoleName}"`,
33425
+ `ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO "${worldRoleName}"`
33426
+ ].join("; ");
33427
+ const grantResult = spawn4("docker", ["exec", container, "psql", "-U", user, "-d", worldDb, "-v", "ON_ERROR_STOP=1", "-c", perDbGrants], { encoding: "utf-8" });
33428
+ if (grantResult.status !== 0) {
33429
+ throw new Error(`failed to GRANT privileges on "${worldDb}" to "${worldRoleName}": ${(grantResult.stderr || "").trim()}`);
33430
+ }
33431
+ }
33432
+ return { worldRoleName, password };
33433
+ }
33434
+ function defaultPasswordGenerator() {
33435
+ return crypto5.randomBytes(24).toString("base64url");
33436
+ }
33437
+ function escapeSqlLiteral(s) {
33438
+ return s.replace(/'/g, "''");
33439
+ }
33440
+ function redactCreateRolePassword(s) {
33441
+ return s.replace(/PASSWORD\s+'[^']*'/g, "PASSWORD '<scrubbed>'");
33442
+ }
33443
+ function dropPostgresWorldDbs(worldId, worldDbNames, options = {}) {
33444
+ if (worldDbNames.length === 0)
33445
+ return;
33446
+ assertSafeWorldId(worldId);
33447
+ const spawn4 = options.spawn ?? spawnSync4;
33448
+ const container = options.container ?? "olam-postgres";
33449
+ const user = options.user ?? "development";
33450
+ for (const db of worldDbNames) {
33451
+ const drop = spawn4("docker", [
33452
+ "exec",
33453
+ container,
33454
+ "psql",
33455
+ "-U",
33456
+ user,
33457
+ "-d",
33458
+ "postgres",
33459
+ "-c",
33460
+ `DROP DATABASE IF EXISTS "${db}" WITH (FORCE)`
33461
+ ], { encoding: "utf-8" });
33462
+ if (drop.status !== 0) {
33463
+ console.warn(`[manager] destroyWorld(${worldId}): failed to DROP "${db}" from singleton: ${(drop.stderr || "").trim()}`);
33464
+ }
33465
+ }
33466
+ }
33467
+ function dropPostgresWorldRole(worldId, worldRoleName, options = {}) {
33468
+ if (!worldRoleName)
33469
+ return;
33470
+ assertSafeWorldId(worldId);
33471
+ const spawn4 = options.spawn ?? spawnSync4;
33472
+ const container = options.container ?? "olam-postgres";
33473
+ const user = options.user ?? "development";
33474
+ const cleanup = spawn4("docker", [
33475
+ "exec",
33476
+ container,
33477
+ "psql",
33478
+ "-U",
33479
+ user,
33480
+ "-d",
33481
+ "postgres",
33482
+ "-c",
33483
+ `DROP OWNED BY "${worldRoleName}" CASCADE; DROP ROLE IF EXISTS "${worldRoleName}"`
33484
+ ], { encoding: "utf-8" });
33485
+ if (cleanup.status !== 0) {
33486
+ console.warn(`[manager] destroyWorld(${worldId}): failed to DROP role "${worldRoleName}" from singleton: ${(cleanup.stderr || "").trim()}`);
33487
+ }
33488
+ }
33489
+ function assertSafeWorldId(worldId) {
33490
+ if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(worldId)) {
33491
+ 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`);
33492
+ }
33493
+ }
33494
+ function deriveWorldDbName(seed, worldId) {
33495
+ if (seed.endsWith("_seed")) {
33496
+ const prefix = seed.slice(0, -"_seed".length);
33497
+ return `${prefix}_world_${worldId}`;
33498
+ }
33499
+ return `${seed}_world_${worldId}`;
33063
33500
  }
33064
33501
 
33065
33502
  // ../core/dist/cost/tracker.js
@@ -33985,7 +34422,7 @@ function isCloudflaredAvailable() {
33985
34422
  }
33986
34423
  }
33987
34424
  function startTunnel(port) {
33988
- return new Promise((resolve9, reject2) => {
34425
+ return new Promise((resolve10, reject2) => {
33989
34426
  const child = spawn3("cloudflared", ["tunnel", "--url", `http://localhost:${port}`], {
33990
34427
  stdio: ["ignore", "pipe", "pipe"],
33991
34428
  detached: false
@@ -34007,7 +34444,7 @@ function startTunnel(port) {
34007
34444
  if (match) {
34008
34445
  resolved = true;
34009
34446
  clearTimeout(timeout);
34010
- resolve9(match[0]);
34447
+ resolve10(match[0]);
34011
34448
  }
34012
34449
  }
34013
34450
  child.stdout?.on("data", scan);
@@ -34075,8 +34512,8 @@ var DashboardManager = class {
34075
34512
  }
34076
34513
  throw err;
34077
34514
  }
34078
- await new Promise((resolve9, reject2) => {
34079
- this.server.on("listening", resolve9);
34515
+ await new Promise((resolve10, reject2) => {
34516
+ this.server.on("listening", resolve10);
34080
34517
  this.server.on("error", reject2);
34081
34518
  });
34082
34519
  this.info = { localUrl: `http://localhost:${port}` };
@@ -34122,8 +34559,8 @@ var DashboardManager = class {
34122
34559
  async stop() {
34123
34560
  stopTunnel();
34124
34561
  if (this.server) {
34125
- await new Promise((resolve9) => {
34126
- this.server.close(() => resolve9());
34562
+ await new Promise((resolve10) => {
34563
+ this.server.close(() => resolve10());
34127
34564
  });
34128
34565
  this.server = null;
34129
34566
  }
@@ -34237,7 +34674,7 @@ var PleriClient = class {
34237
34674
 
34238
34675
  // ../mcp-server/src/env-loader.ts
34239
34676
  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";
34677
+ import { join as join29, dirname as dirname15, resolve as resolve9 } from "node:path";
34241
34678
  var PROJECT_MARKERS = [
34242
34679
  ".olam/config.yaml",
34243
34680
  ".olam/config.yml",
@@ -34245,8 +34682,8 @@ var PROJECT_MARKERS = [
34245
34682
  "olam.yml"
34246
34683
  ];
34247
34684
  function findProjectRoot2(startDir) {
34248
- let dir = resolve8(startDir);
34249
- const root = resolve8("/");
34685
+ let dir = resolve9(startDir);
34686
+ const root = resolve9("/");
34250
34687
  while (true) {
34251
34688
  for (const marker of PROJECT_MARKERS) {
34252
34689
  if (existsSync23(join29(dir, marker))) return dir;