iranti-control-plane 0.5.0 → 0.5.3

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.
@@ -18407,10 +18407,10 @@ var require_view = __commonJS({
18407
18407
  var debug = require_src()("express:view");
18408
18408
  var path3 = require("path");
18409
18409
  var fs2 = require("fs");
18410
- var dirname9 = path3.dirname;
18410
+ var dirname10 = path3.dirname;
18411
18411
  var basename7 = path3.basename;
18412
18412
  var extname3 = path3.extname;
18413
- var join12 = path3.join;
18413
+ var join13 = path3.join;
18414
18414
  var resolve10 = path3.resolve;
18415
18415
  module2.exports = View;
18416
18416
  function View(name, options) {
@@ -18446,7 +18446,7 @@ var require_view = __commonJS({
18446
18446
  for (var i = 0; i < roots.length && !path4; i++) {
18447
18447
  var root = roots[i];
18448
18448
  var loc = resolve10(root, name);
18449
- var dir = dirname9(loc);
18449
+ var dir = dirname10(loc);
18450
18450
  var file2 = basename7(loc);
18451
18451
  path4 = this.resolve(dir, file2);
18452
18452
  }
@@ -18458,12 +18458,12 @@ var require_view = __commonJS({
18458
18458
  };
18459
18459
  View.prototype.resolve = function resolve11(dir, file2) {
18460
18460
  var ext = this.ext;
18461
- var path4 = join12(dir, file2);
18461
+ var path4 = join13(dir, file2);
18462
18462
  var stat2 = tryStat(path4);
18463
18463
  if (stat2 && stat2.isFile()) {
18464
18464
  return path4;
18465
18465
  }
18466
- path4 = join12(dir, basename7(file2, ext), "index" + ext);
18466
+ path4 = join13(dir, basename7(file2, ext), "index" + ext);
18467
18467
  stat2 = tryStat(path4);
18468
18468
  if (stat2 && stat2.isFile()) {
18469
18469
  return path4;
@@ -19096,7 +19096,7 @@ var require_send = __commonJS({
19096
19096
  var Stream = require("stream");
19097
19097
  var util2 = require("util");
19098
19098
  var extname3 = path3.extname;
19099
- var join12 = path3.join;
19099
+ var join13 = path3.join;
19100
19100
  var normalize = path3.normalize;
19101
19101
  var resolve10 = path3.resolve;
19102
19102
  var sep = path3.sep;
@@ -19315,7 +19315,7 @@ var require_send = __commonJS({
19315
19315
  return res;
19316
19316
  }
19317
19317
  parts = path4.split(sep);
19318
- path4 = normalize(join12(root, path4));
19318
+ path4 = normalize(join13(root, path4));
19319
19319
  } else {
19320
19320
  if (UP_PATH_REGEXP.test(path4)) {
19321
19321
  debug('malicious path "%s"', path4);
@@ -19326,12 +19326,12 @@ var require_send = __commonJS({
19326
19326
  path4 = resolve10(path4);
19327
19327
  }
19328
19328
  if (containsDotFile(parts)) {
19329
- var access9 = this._dotfiles;
19330
- if (access9 === void 0) {
19331
- access9 = parts[parts.length - 1][0] === "." ? this._hidden ? "allow" : "ignore" : "allow";
19329
+ var access10 = this._dotfiles;
19330
+ if (access10 === void 0) {
19331
+ access10 = parts[parts.length - 1][0] === "." ? this._hidden ? "allow" : "ignore" : "allow";
19332
19332
  }
19333
- debug('%s dotfile "%s"', access9, path4);
19334
- switch (access9) {
19333
+ debug('%s dotfile "%s"', access10, path4);
19334
+ switch (access10) {
19335
19335
  case "allow":
19336
19336
  break;
19337
19337
  case "deny":
@@ -19450,7 +19450,7 @@ var require_send = __commonJS({
19450
19450
  if (err) return self.onStatError(err);
19451
19451
  return self.error(404);
19452
19452
  }
19453
- var p = join12(path4, self._index[i]);
19453
+ var p = join13(path4, self._index[i]);
19454
19454
  debug('stat "%s"', p);
19455
19455
  fs2.stat(p, function(err2, stat2) {
19456
19456
  if (err2) return next(err2);
@@ -20589,7 +20589,7 @@ var require_application = __commonJS({
20589
20589
  "src/server/node_modules/express/lib/application.js"(exports2, module2) {
20590
20590
  "use strict";
20591
20591
  var finalhandler = require_finalhandler();
20592
- var Router32 = require_router();
20592
+ var Router33 = require_router();
20593
20593
  var methods = require_methods();
20594
20594
  var middleware = require_init();
20595
20595
  var query2 = require_query();
@@ -20654,7 +20654,7 @@ var require_application = __commonJS({
20654
20654
  };
20655
20655
  app2.lazyrouter = function lazyrouter() {
20656
20656
  if (!this._router) {
20657
- this._router = new Router32({
20657
+ this._router = new Router33({
20658
20658
  caseSensitive: this.enabled("case sensitive routing"),
20659
20659
  strict: this.enabled("strict routing")
20660
20660
  });
@@ -22518,7 +22518,7 @@ var require_express = __commonJS({
22518
22518
  var mixin = require_merge_descriptors();
22519
22519
  var proto = require_application();
22520
22520
  var Route = require_route();
22521
- var Router32 = require_router();
22521
+ var Router33 = require_router();
22522
22522
  var req = require_request();
22523
22523
  var res = require_response();
22524
22524
  exports2 = module2.exports = createApplication;
@@ -22541,7 +22541,7 @@ var require_express = __commonJS({
22541
22541
  exports2.request = req;
22542
22542
  exports2.response = res;
22543
22543
  exports2.Route = Route;
22544
- exports2.Router = Router32;
22544
+ exports2.Router = Router33;
22545
22545
  exports2.json = bodyParser.json;
22546
22546
  exports2.query = require_query();
22547
22547
  exports2.raw = bodyParser.raw;
@@ -27129,7 +27129,7 @@ var require_pg_pool = __commonJS({
27129
27129
  pool2.emit("error", err, client);
27130
27130
  };
27131
27131
  }
27132
- var Pool9 = class extends EventEmitter {
27132
+ var Pool12 = class extends EventEmitter {
27133
27133
  constructor(options, Client3) {
27134
27134
  super();
27135
27135
  this.options = Object.assign({}, options);
@@ -27496,7 +27496,7 @@ var require_pg_pool = __commonJS({
27496
27496
  return this._clients.length;
27497
27497
  }
27498
27498
  };
27499
- module2.exports = Pool9;
27499
+ module2.exports = Pool12;
27500
27500
  }
27501
27501
  });
27502
27502
 
@@ -27911,12 +27911,12 @@ var require_lib5 = __commonJS({
27911
27911
  var Connection2 = require_connection();
27912
27912
  var Result2 = require_result();
27913
27913
  var utils = require_utils3();
27914
- var Pool9 = require_pg_pool();
27914
+ var Pool12 = require_pg_pool();
27915
27915
  var TypeOverrides2 = require_type_overrides();
27916
27916
  var { DatabaseError: DatabaseError2 } = require_dist();
27917
27917
  var { escapeIdentifier: escapeIdentifier2, escapeLiteral: escapeLiteral2 } = require_utils3();
27918
27918
  var poolFactory = (Client4) => {
27919
- return class BoundPool extends Pool9 {
27919
+ return class BoundPool extends Pool12 {
27920
27920
  constructor(options) {
27921
27921
  super(options, Client4);
27922
27922
  }
@@ -28474,11 +28474,11 @@ var require_codegen = __commonJS({
28474
28474
  const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
28475
28475
  return `${varKind} ${this.name}${rhs};` + _n;
28476
28476
  }
28477
- optimizeNames(names, constants7) {
28477
+ optimizeNames(names, constants8) {
28478
28478
  if (!names[this.name.str])
28479
28479
  return;
28480
28480
  if (this.rhs)
28481
- this.rhs = optimizeExpr(this.rhs, names, constants7);
28481
+ this.rhs = optimizeExpr(this.rhs, names, constants8);
28482
28482
  return this;
28483
28483
  }
28484
28484
  get names() {
@@ -28495,10 +28495,10 @@ var require_codegen = __commonJS({
28495
28495
  render({ _n }) {
28496
28496
  return `${this.lhs} = ${this.rhs};` + _n;
28497
28497
  }
28498
- optimizeNames(names, constants7) {
28498
+ optimizeNames(names, constants8) {
28499
28499
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
28500
28500
  return;
28501
- this.rhs = optimizeExpr(this.rhs, names, constants7);
28501
+ this.rhs = optimizeExpr(this.rhs, names, constants8);
28502
28502
  return this;
28503
28503
  }
28504
28504
  get names() {
@@ -28559,8 +28559,8 @@ var require_codegen = __commonJS({
28559
28559
  optimizeNodes() {
28560
28560
  return `${this.code}` ? this : void 0;
28561
28561
  }
28562
- optimizeNames(names, constants7) {
28563
- this.code = optimizeExpr(this.code, names, constants7);
28562
+ optimizeNames(names, constants8) {
28563
+ this.code = optimizeExpr(this.code, names, constants8);
28564
28564
  return this;
28565
28565
  }
28566
28566
  get names() {
@@ -28589,12 +28589,12 @@ var require_codegen = __commonJS({
28589
28589
  }
28590
28590
  return nodes.length > 0 ? this : void 0;
28591
28591
  }
28592
- optimizeNames(names, constants7) {
28592
+ optimizeNames(names, constants8) {
28593
28593
  const { nodes } = this;
28594
28594
  let i = nodes.length;
28595
28595
  while (i--) {
28596
28596
  const n = nodes[i];
28597
- if (n.optimizeNames(names, constants7))
28597
+ if (n.optimizeNames(names, constants8))
28598
28598
  continue;
28599
28599
  subtractNames(names, n.names);
28600
28600
  nodes.splice(i, 1);
@@ -28647,12 +28647,12 @@ var require_codegen = __commonJS({
28647
28647
  return void 0;
28648
28648
  return this;
28649
28649
  }
28650
- optimizeNames(names, constants7) {
28650
+ optimizeNames(names, constants8) {
28651
28651
  var _a2;
28652
- this.else = (_a2 = this.else) === null || _a2 === void 0 ? void 0 : _a2.optimizeNames(names, constants7);
28653
- if (!(super.optimizeNames(names, constants7) || this.else))
28652
+ this.else = (_a2 = this.else) === null || _a2 === void 0 ? void 0 : _a2.optimizeNames(names, constants8);
28653
+ if (!(super.optimizeNames(names, constants8) || this.else))
28654
28654
  return;
28655
- this.condition = optimizeExpr(this.condition, names, constants7);
28655
+ this.condition = optimizeExpr(this.condition, names, constants8);
28656
28656
  return this;
28657
28657
  }
28658
28658
  get names() {
@@ -28675,10 +28675,10 @@ var require_codegen = __commonJS({
28675
28675
  render(opts) {
28676
28676
  return `for(${this.iteration})` + super.render(opts);
28677
28677
  }
28678
- optimizeNames(names, constants7) {
28679
- if (!super.optimizeNames(names, constants7))
28678
+ optimizeNames(names, constants8) {
28679
+ if (!super.optimizeNames(names, constants8))
28680
28680
  return;
28681
- this.iteration = optimizeExpr(this.iteration, names, constants7);
28681
+ this.iteration = optimizeExpr(this.iteration, names, constants8);
28682
28682
  return this;
28683
28683
  }
28684
28684
  get names() {
@@ -28714,10 +28714,10 @@ var require_codegen = __commonJS({
28714
28714
  render(opts) {
28715
28715
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
28716
28716
  }
28717
- optimizeNames(names, constants7) {
28718
- if (!super.optimizeNames(names, constants7))
28717
+ optimizeNames(names, constants8) {
28718
+ if (!super.optimizeNames(names, constants8))
28719
28719
  return;
28720
- this.iterable = optimizeExpr(this.iterable, names, constants7);
28720
+ this.iterable = optimizeExpr(this.iterable, names, constants8);
28721
28721
  return this;
28722
28722
  }
28723
28723
  get names() {
@@ -28759,11 +28759,11 @@ var require_codegen = __commonJS({
28759
28759
  (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNodes();
28760
28760
  return this;
28761
28761
  }
28762
- optimizeNames(names, constants7) {
28762
+ optimizeNames(names, constants8) {
28763
28763
  var _a2, _b;
28764
- super.optimizeNames(names, constants7);
28765
- (_a2 = this.catch) === null || _a2 === void 0 ? void 0 : _a2.optimizeNames(names, constants7);
28766
- (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants7);
28764
+ super.optimizeNames(names, constants8);
28765
+ (_a2 = this.catch) === null || _a2 === void 0 ? void 0 : _a2.optimizeNames(names, constants8);
28766
+ (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants8);
28767
28767
  return this;
28768
28768
  }
28769
28769
  get names() {
@@ -29064,7 +29064,7 @@ var require_codegen = __commonJS({
29064
29064
  function addExprNames(names, from) {
29065
29065
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
29066
29066
  }
29067
- function optimizeExpr(expr, names, constants7) {
29067
+ function optimizeExpr(expr, names, constants8) {
29068
29068
  if (expr instanceof code_1.Name)
29069
29069
  return replaceName(expr);
29070
29070
  if (!canOptimize(expr))
@@ -29079,14 +29079,14 @@ var require_codegen = __commonJS({
29079
29079
  return items;
29080
29080
  }, []));
29081
29081
  function replaceName(n) {
29082
- const c = constants7[n.str];
29082
+ const c = constants8[n.str];
29083
29083
  if (c === void 0 || names[n.str] !== 1)
29084
29084
  return n;
29085
29085
  delete names[n.str];
29086
29086
  return c;
29087
29087
  }
29088
29088
  function canOptimize(e) {
29089
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants7[c.str] !== void 0);
29089
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants8[c.str] !== void 0);
29090
29090
  }
29091
29091
  }
29092
29092
  function subtractNames(names, from) {
@@ -35370,11 +35370,11 @@ __export(runner_exports, {
35370
35370
  });
35371
35371
  function resolveMigrationPath(file2) {
35372
35372
  const candidates = [
35373
- (0, import_path18.resolve)(__dirname2, file2),
35374
- (0, import_path18.resolve)(__dirname2, "..", "..", "migrations", file2)
35373
+ (0, import_path19.resolve)(__dirname2, file2),
35374
+ (0, import_path19.resolve)(__dirname2, "..", "..", "migrations", file2)
35375
35375
  ];
35376
35376
  for (const candidate of candidates) {
35377
- if ((0, import_fs12.existsSync)(candidate)) return candidate;
35377
+ if ((0, import_fs13.existsSync)(candidate)) return candidate;
35378
35378
  }
35379
35379
  return candidates[0];
35380
35380
  }
@@ -35383,11 +35383,12 @@ async function run() {
35383
35383
  const migrations = [
35384
35384
  "001_create_staff_events.sql",
35385
35385
  "002_create_archive_flags.sql",
35386
- "003_staff_events_metrics_index.sql"
35386
+ "003_staff_events_metrics_index.sql",
35387
+ "004_create_fleet_ledger.sql"
35387
35388
  ];
35388
35389
  try {
35389
35390
  for (const file2 of migrations) {
35390
- const sql = (0, import_fs12.readFileSync)(resolveMigrationPath(file2), "utf8");
35391
+ const sql = (0, import_fs13.readFileSync)(resolveMigrationPath(file2), "utf8");
35391
35392
  console.log(`[migrate] Running ${file2}`);
35392
35393
  await migrationPool.query(sql);
35393
35394
  console.log(`[migrate] Done: ${file2}`);
@@ -35397,17 +35398,17 @@ async function run() {
35397
35398
  await migrationPool.end();
35398
35399
  }
35399
35400
  }
35400
- var import_fs12, import_path18, import_url2, __dirname2;
35401
+ var import_fs13, import_path19, import_url2, __dirname2;
35401
35402
  var init_runner = __esm({
35402
35403
  "src/server/migrations/runner.ts"() {
35403
35404
  "use strict";
35404
- import_fs12 = require("fs");
35405
- import_path18 = require("path");
35405
+ import_fs13 = require("fs");
35406
+ import_path19 = require("path");
35406
35407
  import_url2 = require("url");
35407
35408
  init_esm();
35408
35409
  init_db();
35409
- __dirname2 = (0, import_path18.dirname)((0, import_url2.fileURLToPath)(__importmeta_url));
35410
- if (process.argv[1] && (0, import_path18.resolve)(process.argv[1]) === (0, import_url2.fileURLToPath)(__importmeta_url)) {
35410
+ __dirname2 = (0, import_path19.dirname)((0, import_url2.fileURLToPath)(__importmeta_url));
35411
+ if (process.argv[1] && (0, import_path19.resolve)(process.argv[1]) === (0, import_url2.fileURLToPath)(__importmeta_url)) {
35411
35412
  run().catch((err) => {
35412
35413
  console.error("[migrate] Failed:", err);
35413
35414
  process.exitCode = 1;
@@ -35761,16 +35762,16 @@ __export(index_exports, {
35761
35762
  VERSION: () => VERSION
35762
35763
  });
35763
35764
  module.exports = __toCommonJS(index_exports);
35764
- var import_express32 = __toESM(require_express2(), 1);
35765
+ var import_express33 = __toESM(require_express2(), 1);
35765
35766
  var import_cors = __toESM(require_lib3(), 1);
35766
35767
  var import_net = __toESM(require("net"), 1);
35767
- var import_path19 = require("path");
35768
- var import_fs13 = require("fs");
35768
+ var import_path20 = require("path");
35769
+ var import_fs14 = require("fs");
35769
35770
  var import_url3 = require("url");
35770
35771
  var import_module = require("module");
35771
35772
 
35772
35773
  // src/server/routes/control-plane/index.ts
35773
- var import_express31 = __toESM(require_express2(), 1);
35774
+ var import_express32 = __toESM(require_express2(), 1);
35774
35775
 
35775
35776
  // src/server/routes/control-plane/kb.ts
35776
35777
  var import_express = __toESM(require_express2(), 1);
@@ -53953,11 +53954,105 @@ setupRouter.use((err, _req, res, _next) => {
53953
53954
 
53954
53955
  // src/server/routes/control-plane/repair.ts
53955
53956
  var import_express7 = __toESM(require_express2(), 1);
53957
+ var import_promises6 = require("fs/promises");
53958
+ var import_path9 = require("path");
53959
+ init_esm();
53960
+
53961
+ // src/server/lib/db-provision.ts
53962
+ var import_child_process2 = require("child_process");
53956
53963
  var import_promises5 = require("fs/promises");
53964
+ var import_fs6 = require("fs");
53957
53965
  var import_path8 = require("path");
53958
53966
  init_esm();
53959
- var repairRouter = (0, import_express7.Router)();
53960
53967
  var { Pool: Pool6 } = esm_default;
53968
+ function escapePgIdentifier(value) {
53969
+ return `"${value.replace(/"/g, '""')}"`;
53970
+ }
53971
+ async function createDatabaseIfMissing(dbUrl) {
53972
+ const parsed = new URL(dbUrl);
53973
+ const databaseName = decodeURIComponent(parsed.pathname.replace(/^\//, "").trim());
53974
+ if (!databaseName) {
53975
+ throw new Error("DATABASE_URL does not include a database name.");
53976
+ }
53977
+ if (databaseName.toLowerCase() === "postgres") {
53978
+ throw new Error('Refusing to operate on the maintenance database "postgres".');
53979
+ }
53980
+ parsed.pathname = "/postgres";
53981
+ const adminUrl = parsed.toString();
53982
+ const pool2 = new Pool6({
53983
+ connectionString: adminUrl,
53984
+ max: 1,
53985
+ idleTimeoutMillis: 1e3,
53986
+ connectionTimeoutMillis: 4e3
53987
+ });
53988
+ try {
53989
+ const existing = await pool2.query("SELECT 1 FROM pg_database WHERE datname = $1", [databaseName]);
53990
+ if (existing.rows.length > 0) {
53991
+ return { databaseName, alreadyExisted: true };
53992
+ }
53993
+ await pool2.query(`CREATE DATABASE ${escapePgIdentifier(databaseName)}`);
53994
+ return { databaseName, alreadyExisted: false };
53995
+ } finally {
53996
+ await pool2.end().catch(() => {
53997
+ });
53998
+ }
53999
+ }
54000
+ async function runInstanceMigrations(dbUrl, instanceDir) {
54001
+ const resolution = await resolveIrantiCli();
54002
+ if (!resolution) {
54003
+ return { ran: false, note: "iranti CLI not found \u2014 run migrations manually." };
54004
+ }
54005
+ const cliEntry = resolution.args[0];
54006
+ if (!cliEntry || !cliEntry.toLowerCase().endsWith(".js")) {
54007
+ return { ran: false, note: "Could not locate bundled setup script \u2014 run migrations manually." };
54008
+ }
54009
+ const packageRoot = (0, import_path8.dirname)((0, import_path8.dirname)(cliEntry));
54010
+ const setupScript = (0, import_path8.join)(packageRoot, "dist", "scripts", "setup.js");
54011
+ try {
54012
+ await (0, import_promises5.access)(setupScript, import_fs6.constants.F_OK);
54013
+ } catch {
54014
+ return { ran: false, note: "Bundled setup script not found \u2014 run migrations manually." };
54015
+ }
54016
+ const escalationDir = (0, import_path8.join)(instanceDir, "escalation");
54017
+ await (0, import_promises5.mkdir)(escalationDir, { recursive: true }).catch(() => {
54018
+ });
54019
+ await new Promise((resolve10, reject) => {
54020
+ (0, import_child_process2.execFile)(
54021
+ process.execPath,
54022
+ [setupScript],
54023
+ {
54024
+ env: {
54025
+ ...process.env,
54026
+ DATABASE_URL: dbUrl,
54027
+ IRANTI_ESCALATION_DIR: escalationDir
54028
+ },
54029
+ timeout: 6e4
54030
+ },
54031
+ (err) => {
54032
+ if (err) reject(err);
54033
+ else resolve10();
54034
+ }
54035
+ );
54036
+ });
54037
+ return { ran: true, note: "Migrations applied." };
54038
+ }
54039
+ async function provisionDatabase(dbUrl, instanceDir) {
54040
+ const { databaseName, alreadyExisted } = await createDatabaseIfMissing(dbUrl);
54041
+ let migrated = false;
54042
+ let migrationNote = "";
54043
+ try {
54044
+ const result = await runInstanceMigrations(dbUrl, instanceDir);
54045
+ migrated = result.ran;
54046
+ migrationNote = result.note;
54047
+ } catch (err) {
54048
+ migrationNote = `Migrations failed: ${err instanceof Error ? err.message : String(err)}`;
54049
+ }
54050
+ return { databaseName, created: !alreadyExisted, migrated, migrationNote };
54051
+ }
54052
+
54053
+ // src/server/routes/control-plane/repair.ts
54054
+ var repairRouter = (0, import_express7.Router)();
54055
+ var { Pool: Pool7 } = esm_default;
53961
54056
  async function doctorCheckRuntimeAvailability(scope) {
53962
54057
  const controller = new AbortController();
53963
54058
  const timeout = setTimeout(() => controller.abort(), 2500);
@@ -54041,7 +54136,7 @@ async function resolveProjectOr422(scope, projectId, res) {
54041
54136
  }
54042
54137
  async function writeAuditLog(scope, action, detail) {
54043
54138
  if (!scope.databaseUrl) return;
54044
- const pool2 = new Pool6({
54139
+ const pool2 = new Pool7({
54045
54140
  connectionString: scope.databaseUrl,
54046
54141
  max: 1,
54047
54142
  idleTimeoutMillis: 1e3,
@@ -54078,8 +54173,8 @@ repairRouter.post(
54078
54173
  if (!scope) return;
54079
54174
  const projectPath = await resolveProjectOr422(scope, req.params["projectId"], res);
54080
54175
  if (!projectPath) return;
54081
- await (0, import_promises5.access)(projectPath, import_promises5.constants.W_OK);
54082
- const filePath = (0, import_path8.join)(projectPath, ".mcp.json");
54176
+ await (0, import_promises6.access)(projectPath, import_promises6.constants.W_OK);
54177
+ const filePath = (0, import_path9.join)(projectPath, ".mcp.json");
54083
54178
  const content = JSON.stringify(
54084
54179
  {
54085
54180
  mcpServers: {
@@ -54094,12 +54189,12 @@ repairRouter.post(
54094
54189
  ) + "\n";
54095
54190
  let action = "created";
54096
54191
  try {
54097
- await (0, import_promises5.access)(filePath, import_promises5.constants.F_OK);
54192
+ await (0, import_promises6.access)(filePath, import_promises6.constants.F_OK);
54098
54193
  action = "replaced";
54099
54194
  } catch {
54100
54195
  action = "created";
54101
54196
  }
54102
- await (0, import_promises5.writeFile)(filePath, content, "utf8");
54197
+ await (0, import_promises6.writeFile)(filePath, content, "utf8");
54103
54198
  await writeAuditLog(scope, "repair_mcp_json", {
54104
54199
  instanceId: scope.instanceId,
54105
54200
  instanceName: scope.instanceName,
@@ -54148,12 +54243,12 @@ repairRouter.post(
54148
54243
  if (!scope) return;
54149
54244
  const projectPath = await resolveProjectOr422(scope, req.params["projectId"], res);
54150
54245
  if (!projectPath) return;
54151
- await (0, import_promises5.access)(projectPath, import_promises5.constants.W_OK);
54152
- const filePath = (0, import_path8.join)(projectPath, "CLAUDE.md");
54246
+ await (0, import_promises6.access)(projectPath, import_promises6.constants.W_OK);
54247
+ const filePath = (0, import_path9.join)(projectPath, "CLAUDE.md");
54153
54248
  const block = buildIrantiBlock(scope);
54154
54249
  let existingContent = null;
54155
54250
  try {
54156
- existingContent = await (0, import_promises5.readFile)(filePath, "utf8");
54251
+ existingContent = await (0, import_promises6.readFile)(filePath, "utf8");
54157
54252
  } catch (err) {
54158
54253
  if (err.code !== "ENOENT") throw err;
54159
54254
  }
@@ -54182,7 +54277,7 @@ repairRouter.post(
54182
54277
  }
54183
54278
  }
54184
54279
  const diff = buildDiff(existingContent, finalContent);
54185
- await (0, import_promises5.writeFile)(filePath, finalContent, "utf8");
54280
+ await (0, import_promises6.writeFile)(filePath, finalContent, "utf8");
54186
54281
  await writeAuditLog(scope, "repair_claude_md", {
54187
54282
  instanceId: scope.instanceId,
54188
54283
  instanceName: scope.instanceName,
@@ -54208,7 +54303,7 @@ async function doctorCheckDatabase(scope) {
54208
54303
  commands: []
54209
54304
  };
54210
54305
  }
54211
- const pool2 = new Pool6({
54306
+ const pool2 = new Pool7({
54212
54307
  connectionString: scope.databaseUrl,
54213
54308
  max: 1,
54214
54309
  idleTimeoutMillis: 1e3,
@@ -54277,7 +54372,7 @@ function projectRepairUrl(scope, projectPath, kind) {
54277
54372
  async function doctorProjectChecks(scope) {
54278
54373
  const checks = await Promise.all(scope.boundProjects.map(async (project) => {
54279
54374
  const integration = await inspectProjectIntegration(project.projectPath);
54280
- const projectName = (0, import_path8.basename)(project.projectPath);
54375
+ const projectName = (0, import_path9.basename)(project.projectPath);
54281
54376
  const mcpCheck = {
54282
54377
  id: `mcp_integration:${project.projectPath}`,
54283
54378
  label: `MCP integration (${projectName})`,
@@ -54377,6 +54472,30 @@ repairRouter.post("/:instanceId/doctor", async (req, res, next) => {
54377
54472
  next(err);
54378
54473
  }
54379
54474
  });
54475
+ repairRouter.post("/:instanceId/repair/provision-database", async (req, res, next) => {
54476
+ try {
54477
+ const scope = await resolveScopeOr404(req.params["instanceId"], res);
54478
+ if (!scope) return;
54479
+ if (!scope.databaseUrl) {
54480
+ res.status(422).json({
54481
+ error: "No DATABASE_URL configured for this instance.",
54482
+ code: "NO_DATABASE_URL"
54483
+ });
54484
+ return;
54485
+ }
54486
+ const result = await provisionDatabase(scope.databaseUrl, scope.instanceDir);
54487
+ await writeAuditLog(scope, "provision_database", {
54488
+ instanceId: scope.instanceId,
54489
+ instanceName: scope.instanceName,
54490
+ databaseName: result.databaseName,
54491
+ created: result.created,
54492
+ migrated: result.migrated
54493
+ });
54494
+ res.json({ ok: true, ...result });
54495
+ } catch (err) {
54496
+ next(err);
54497
+ }
54498
+ });
54380
54499
  repairRouter.use((err, _req, res, _next) => {
54381
54500
  const apiErr = err;
54382
54501
  if (err?.code === "EACCES") {
@@ -54526,7 +54645,7 @@ escalationsRouter.get("/", async (req, res, next) => {
54526
54645
  "supersededBy"::text AS "supersededBy",
54527
54646
  "conflictLog"
54528
54647
  FROM archive
54529
- WHERE "resolutionState" IS NULL AND "supersededBy" IS NOT NULL
54648
+ WHERE "archivedReason" = 'escalated' AND "resolutionState" = 'pending'
54530
54649
  ORDER BY "archivedAt" ASC`
54531
54650
  );
54532
54651
  if (archiveResult.rows.length === 0) {
@@ -54595,7 +54714,7 @@ escalationsRouter.get("/", async (req, res, next) => {
54595
54714
  "archivedAt",
54596
54715
  NULL::text AS "resolutionNote"
54597
54716
  FROM archive
54598
- WHERE "resolutionState" IS NOT NULL
54717
+ WHERE "archivedReason" = 'escalated' AND "resolutionState" = 'resolved'
54599
54718
  ORDER BY "archivedAt" DESC
54600
54719
  LIMIT 500`
54601
54720
  );
@@ -54669,14 +54788,14 @@ escalationsRouter.post(
54669
54788
  "resolutionState",
54670
54789
  "conflictLog"
54671
54790
  FROM archive
54672
- WHERE id = $1::uuid`,
54791
+ WHERE id = $1::int`,
54673
54792
  [id]
54674
54793
  );
54675
54794
  if (archiveResult.rows.length === 0) {
54676
54795
  throw createApiError(`Escalation not found: ${id}`, "NOT_FOUND", 404);
54677
54796
  }
54678
54797
  const archiveRow = archiveResult.rows[0];
54679
- if (archiveRow.resolutionState != null) {
54798
+ if (archiveRow.resolutionState != null && archiveRow.resolutionState !== "pending") {
54680
54799
  throw createApiError(
54681
54800
  `Escalation ${id} is already resolved (resolutionState: ${archiveRow.resolutionState})`,
54682
54801
  "ALREADY_RESOLVED",
@@ -54689,8 +54808,8 @@ escalationsRouter.post(
54689
54808
  const resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
54690
54809
  if (resolution === "keep_existing") {
54691
54810
  await query(
54692
- `UPDATE archive SET "resolutionState" = 'resolved_keep_existing' WHERE id = $1::uuid`,
54693
- [id]
54811
+ `UPDATE archive SET "resolutionState" = 'resolved', "conflictLog" = COALESCE("conflictLog", '{}'::jsonb) || $2::jsonb WHERE id = $1::int`,
54812
+ [id, JSON.stringify({ resolutionDetail: "keep_existing", resolvedAt, resolvedBy: "control_plane_operator" })]
54694
54813
  );
54695
54814
  } else if (resolution === "accept_challenger") {
54696
54815
  const valueRaw = archiveRow.valueRaw;
@@ -54717,8 +54836,8 @@ escalationsRouter.post(
54717
54836
  [entityType, entityId, key, JSON.stringify(valueRaw), valueSummary, confidence, source, createdBy, validFrom, validUntil]
54718
54837
  );
54719
54838
  await query(
54720
- `UPDATE archive SET "resolutionState" = 'resolved_accept_challenger' WHERE id = $1::uuid`,
54721
- [id]
54839
+ `UPDATE archive SET "resolutionState" = 'resolved', "conflictLog" = COALESCE("conflictLog", '{}'::jsonb) || $2::jsonb WHERE id = $1::int`,
54840
+ [id, JSON.stringify({ resolutionDetail: "accept_challenger", resolvedAt, resolvedBy: "control_plane_operator" })]
54722
54841
  );
54723
54842
  } else {
54724
54843
  const parsedCustom = JSON.parse(customValue);
@@ -54740,8 +54859,8 @@ escalationsRouter.post(
54740
54859
  [entityType, entityId, key, JSON.stringify(parsedCustom), createdBy]
54741
54860
  );
54742
54861
  await query(
54743
- `UPDATE archive SET "resolutionState" = 'resolved_custom' WHERE id = $1::uuid`,
54744
- [id]
54862
+ `UPDATE archive SET "resolutionState" = 'resolved', "conflictLog" = COALESCE("conflictLog", '{}'::jsonb) || $2::jsonb WHERE id = $1::int`,
54863
+ [id, JSON.stringify({ resolutionDetail: "custom", resolvedAt, resolvedBy: "control_plane_operator" })]
54745
54864
  );
54746
54865
  }
54747
54866
  await writeAuditLog2("conflict_resolved", {
@@ -54769,8 +54888,8 @@ escalationsRouter.use(errorHandler2);
54769
54888
 
54770
54889
  // src/server/routes/control-plane/providers.ts
54771
54890
  var import_express9 = __toESM(require_express2(), 1);
54772
- var import_fs6 = require("fs");
54773
- var import_path9 = require("path");
54891
+ var import_fs7 = require("fs");
54892
+ var import_path10 = require("path");
54774
54893
  init_db();
54775
54894
  var providersRouter = (0, import_express9.Router)();
54776
54895
  var PROVIDER_KEY_VARS = {
@@ -54807,8 +54926,8 @@ function scopeSummary3(scope) {
54807
54926
  }
54808
54927
  function writeEnvVarAtPath(filePath, key, value, syncLiveEnv) {
54809
54928
  let raw = "";
54810
- if ((0, import_fs6.existsSync)(filePath)) {
54811
- raw = (0, import_fs6.readFileSync)(filePath, "utf8");
54929
+ if ((0, import_fs7.existsSync)(filePath)) {
54930
+ raw = (0, import_fs7.readFileSync)(filePath, "utf8");
54812
54931
  }
54813
54932
  const lineEnding = raw.includes("\r\n") ? "\r\n" : "\n";
54814
54933
  const rawLines = raw.split(/\r?\n/);
@@ -54835,7 +54954,7 @@ function writeEnvVarAtPath(filePath, key, value, syncLiveEnv) {
54835
54954
  }
54836
54955
  updated.push(`${key}=${value}`);
54837
54956
  }
54838
- (0, import_fs6.writeFileSync)(filePath, updated.join(lineEnding), "utf8");
54957
+ (0, import_fs7.writeFileSync)(filePath, updated.join(lineEnding), "utf8");
54839
54958
  if (syncLiveEnv) {
54840
54959
  if (value !== null) {
54841
54960
  env[key] = value;
@@ -54852,7 +54971,7 @@ var reachabilityCache = /* @__PURE__ */ new Map();
54852
54971
  var REACHABILITY_TTL_MS = 60 * 1e3;
54853
54972
  function currentBindingEnvPath() {
54854
54973
  const configured = env["IRANTI_INSTANCE_ENV"] ?? process.env["IRANTI_INSTANCE_ENV"] ?? "";
54855
- return configured.trim() ? (0, import_path9.resolve)(configured) : null;
54974
+ return configured.trim() ? (0, import_path10.resolve)(configured) : null;
54856
54975
  }
54857
54976
  async function resolveScopeFromRequest2(req) {
54858
54977
  const paramInstance = typeof req.params["instanceId"] === "string" ? req.params["instanceId"] : "";
@@ -54868,7 +54987,7 @@ async function resolveScopeFromRequest2(req) {
54868
54987
  }
54869
54988
  function shouldSyncLiveEnv(scope) {
54870
54989
  const bindingEnvPath = currentBindingEnvPath();
54871
- return bindingEnvPath !== null && bindingEnvPath === (0, import_path9.resolve)(scope.instanceEnvPath);
54990
+ return bindingEnvPath !== null && bindingEnvPath === (0, import_path10.resolve)(scope.instanceEnvPath);
54872
54991
  }
54873
54992
  function writeEnvVarForScope(scope, key, value) {
54874
54993
  writeEnvVarAtPath(scope.instanceEnvPath, key, value, shouldSyncLiveEnv(scope));
@@ -56683,7 +56802,7 @@ whoknowsRouter.use((err, _req, res, _next) => {
56683
56802
  var import_express14 = __toESM(require_express2(), 1);
56684
56803
  init_esm();
56685
56804
  var diagnosticsRouter = (0, import_express14.Router)();
56686
- var { Pool: Pool7 } = esm_default;
56805
+ var { Pool: Pool8 } = esm_default;
56687
56806
  var DIAGNOSTIC_ENTITY_TYPE = "__diagnostics__";
56688
56807
  var DIAGNOSTIC_ENTITY_ID = "__probe__";
56689
56808
  var ROUNDTRIP_KEY = "roundtrip_marker";
@@ -56768,7 +56887,7 @@ function buildKbRouteFailure(scope, routeLabel, statusCode, responseBody, authFi
56768
56887
  }
56769
56888
  async function withScopedPool3(databaseUrl2, fn) {
56770
56889
  if (!databaseUrl2) return fn(null);
56771
- const pool2 = new Pool7({
56890
+ const pool2 = new Pool8({
56772
56891
  connectionString: databaseUrl2,
56773
56892
  max: 1,
56774
56893
  idleTimeoutMillis: 1e3,
@@ -57509,8 +57628,28 @@ metricsRouter.use(
57509
57628
 
57510
57629
  // src/server/routes/control-plane/overview.ts
57511
57630
  var import_express16 = __toESM(require_express2(), 1);
57631
+ init_esm();
57512
57632
  init_db();
57633
+ var { Pool: Pool9 } = esm_default;
57513
57634
  var overviewRouter = (0, import_express16.Router)();
57635
+ async function withScopedPool4(databaseUrl2, fn) {
57636
+ if (!databaseUrl2) return fn(null);
57637
+ const pool2 = new Pool9({
57638
+ connectionString: databaseUrl2,
57639
+ max: 2,
57640
+ idleTimeoutMillis: 2e3,
57641
+ connectionTimeoutMillis: 5e3
57642
+ });
57643
+ try {
57644
+ return await fn(pool2);
57645
+ } finally {
57646
+ await pool2.end().catch(() => {
57647
+ });
57648
+ }
57649
+ }
57650
+ function scopedQuery(pool2) {
57651
+ return (text, params) => pool2.query(text, params);
57652
+ }
57514
57653
  var HEALTH_FALLBACK = {
57515
57654
  overall: "error",
57516
57655
  checks: [],
@@ -57518,11 +57657,11 @@ var HEALTH_FALLBACK = {
57518
57657
  };
57519
57658
  var WRITE_ACTION_TYPES2 = ["write_created", "write_replaced"];
57520
57659
  var ARCHIVAL_ACTION_TYPES2 = ["entry_archived", "entry_decayed"];
57521
- async function fetchKBSummary() {
57660
+ async function fetchKBSummary(queryFn = query) {
57522
57661
  const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
57523
57662
  const fallbackFromKnowledgeBase = async (truncated) => {
57524
57663
  try {
57525
- const result = await query(
57664
+ const result = await queryFn(
57526
57665
  `SELECT
57527
57666
  COUNT(*)::text AS total_facts,
57528
57667
  COUNT(*) FILTER (WHERE "createdAt" >= NOW() - INTERVAL '24 hours')::text AS facts_last_24h,
@@ -57544,7 +57683,7 @@ async function fetchKBSummary() {
57544
57683
  }
57545
57684
  };
57546
57685
  try {
57547
- const existsResult = await query(
57686
+ const existsResult = await queryFn(
57548
57687
  `SELECT EXISTS (
57549
57688
  SELECT 1 FROM information_schema.tables
57550
57689
  WHERE table_schema = 'public' AND table_name = 'staff_events'
@@ -57554,7 +57693,7 @@ async function fetchKBSummary() {
57554
57693
  if (!tableExists) {
57555
57694
  return await fallbackFromKnowledgeBase(true);
57556
57695
  }
57557
- const result = await query(
57696
+ const result = await queryFn(
57558
57697
  `SELECT
57559
57698
  COUNT(*) FILTER (WHERE action_type = ANY($1)) AS total_writes_all_time,
57560
57699
  COUNT(*) FILTER (WHERE action_type = ANY($2)) AS total_archived_all_time,
@@ -57582,9 +57721,9 @@ async function fetchKBSummary() {
57582
57721
  }
57583
57722
  }
57584
57723
  var PG_UNDEFINED_TABLE2 = "42P01";
57585
- async function fetchRecentEvents() {
57724
+ async function fetchRecentEvents(queryFn = query) {
57586
57725
  try {
57587
- const result = await query(
57726
+ const result = await queryFn(
57588
57727
  `SELECT id, staff_component, action_type, agent_id, entity_type, entity_id, key, reason, timestamp
57589
57728
  FROM staff_events
57590
57729
  ORDER BY timestamp DESC
@@ -57651,10 +57790,10 @@ function getIrantiUrl4() {
57651
57790
  function getIrantiApiKey5() {
57652
57791
  return env["IRANTI_API_KEY"] ?? process.env["IRANTI_API_KEY"] ?? "";
57653
57792
  }
57654
- async function fetchActiveAgents() {
57793
+ async function fetchActiveAgents(instanceBaseUrl, instanceApiKey) {
57655
57794
  try {
57656
- const baseUrl = getIrantiUrl4();
57657
- const apiKey = getIrantiApiKey5();
57795
+ const baseUrl = instanceBaseUrl ?? getIrantiUrl4();
57796
+ const apiKey = instanceApiKey ?? getIrantiApiKey5();
57658
57797
  const headers = { "Content-Type": "application/json" };
57659
57798
  if (apiKey) headers["X-Iranti-Key"] = apiKey;
57660
57799
  const controller = new AbortController();
@@ -57704,26 +57843,38 @@ async function fetchActiveAgents() {
57704
57843
  return [];
57705
57844
  }
57706
57845
  }
57707
- overviewRouter.get("/", async (_req, res) => {
57846
+ overviewRouter.get("/", async (req, res) => {
57708
57847
  const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
57709
- const [healthResult, kbResult, eventsResult, agentsResult] = await Promise.allSettled([
57710
- (async () => {
57711
- const full = await runAllHealthChecks();
57712
- return {
57713
- overall: full.overall,
57714
- checks: full.checks.map((c) => ({ name: c.name, status: c.status })),
57715
- fetchedAt: full.checkedAt
57716
- };
57717
- })(),
57718
- fetchKBSummary(),
57719
- fetchRecentEvents(),
57720
- fetchActiveAgents()
57721
- ]);
57722
- const health = healthResult.status === "fulfilled" ? healthResult.value : { ...HEALTH_FALLBACK, fetchedAt };
57723
- const kb = kbResult.status === "fulfilled" ? kbResult.value : { totalFacts: 0, factsLast24h: 0, activeAgentsLast7d: 0, truncated: true, fetchedAt };
57724
- const recentEvents = eventsResult.status === "fulfilled" ? eventsResult.value : [];
57725
- const activeAgents = agentsResult.status === "fulfilled" ? agentsResult.value : [];
57726
- const response = { health, kb, recentEvents, activeAgents, fetchedAt };
57848
+ const instanceId = typeof req.query["instanceId"] === "string" ? req.query["instanceId"] : void 0;
57849
+ let scope = null;
57850
+ if (instanceId) {
57851
+ scope = await resolveInstanceAuthority(instanceId).catch(() => null);
57852
+ }
57853
+ const buildResponse = async (pool2) => {
57854
+ const qFn = pool2 ? scopedQuery(pool2) : query;
57855
+ const instanceBaseUrl = scope?.apiBaseUrl;
57856
+ const instanceApiKey = scope?.apiKey ?? void 0;
57857
+ const [healthResult, kbResult, eventsResult, agentsResult] = await Promise.allSettled([
57858
+ (async () => {
57859
+ const full = await runAllHealthChecks(instanceId);
57860
+ return {
57861
+ overall: full.overall,
57862
+ checks: full.checks.map((c) => ({ name: c.name, status: c.status })),
57863
+ fetchedAt: full.checkedAt
57864
+ };
57865
+ })(),
57866
+ fetchKBSummary(qFn),
57867
+ fetchRecentEvents(qFn),
57868
+ fetchActiveAgents(instanceBaseUrl, instanceApiKey)
57869
+ ]);
57870
+ const health = healthResult.status === "fulfilled" ? healthResult.value : { ...HEALTH_FALLBACK, fetchedAt };
57871
+ const kb = kbResult.status === "fulfilled" ? kbResult.value : { totalFacts: 0, factsLast24h: 0, activeAgentsLast7d: 0, truncated: true, fetchedAt };
57872
+ const recentEvents = eventsResult.status === "fulfilled" ? eventsResult.value : [];
57873
+ const activeAgents = agentsResult.status === "fulfilled" ? agentsResult.value : [];
57874
+ return { health, kb, recentEvents, activeAgents, fetchedAt };
57875
+ };
57876
+ const databaseUrl2 = scope?.databaseUrl ?? null;
57877
+ const response = scope ? await withScopedPool4(databaseUrl2, (pool2) => buildResponse(pool2)) : await buildResponse(null);
57727
57878
  res.status(200).json(response);
57728
57879
  });
57729
57880
 
@@ -57964,6 +58115,44 @@ function normalizeSort(value) {
57964
58115
  return void 0;
57965
58116
  }
57966
58117
  }
58118
+ function normalizeLedgerLevel(value) {
58119
+ switch (String(value ?? "").trim().toLowerCase()) {
58120
+ case "audit":
58121
+ return "audit";
58122
+ case "debug":
58123
+ return "debug";
58124
+ default:
58125
+ return void 0;
58126
+ }
58127
+ }
58128
+ function parseLedgerLimit(value) {
58129
+ if (value === void 0 || value === null || String(value).trim() === "") {
58130
+ return 50;
58131
+ }
58132
+ const parsed = Number.parseInt(String(value), 10);
58133
+ if (!Number.isFinite(parsed) || parsed < 1) {
58134
+ throw new Error("limit must be an integer >= 1");
58135
+ }
58136
+ return Math.min(parsed, 200);
58137
+ }
58138
+ function normalizeLedgerEvent(raw) {
58139
+ const level = normalizeLedgerLevel(raw["level"]) ?? "audit";
58140
+ const metadata = raw["metadata"];
58141
+ return {
58142
+ eventId: String(raw["eventId"] ?? raw["event_id"] ?? raw["id"] ?? ""),
58143
+ timestamp: raw["timestamp"] instanceof Date ? raw["timestamp"].toISOString() : String(raw["timestamp"] ?? ""),
58144
+ staffComponent: String(raw["staffComponent"] ?? raw["staff_component"] ?? "Attendant"),
58145
+ actionType: String(raw["actionType"] ?? raw["action_type"] ?? ""),
58146
+ agentId: String(raw["agentId"] ?? raw["agent_id"] ?? ""),
58147
+ source: String(raw["source"] ?? ""),
58148
+ entityType: typeof raw["entityType"] === "string" ? raw["entityType"] : typeof raw["entity_type"] === "string" ? raw["entity_type"] : null,
58149
+ entityId: typeof raw["entityId"] === "string" ? raw["entityId"] : typeof raw["entity_id"] === "string" ? raw["entity_id"] : null,
58150
+ key: typeof raw["key"] === "string" ? raw["key"] : null,
58151
+ reason: typeof raw["reason"] === "string" ? raw["reason"] : null,
58152
+ level,
58153
+ metadata: metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata : null
58154
+ };
58155
+ }
57967
58156
  function applySessionFilters(sessions, options) {
57968
58157
  let filtered = sessions;
57969
58158
  if (options.agentId) filtered = filtered.filter((entry) => entry.agentId === options.agentId);
@@ -58026,6 +58215,98 @@ async function fetchIrantiSessions(req) {
58026
58215
  clearTimeout(timeout);
58027
58216
  }
58028
58217
  }
58218
+ async function fetchLedgerByParams(req, params) {
58219
+ const url2 = new URL(`${getIrantiUrl5()}/memory/ledger`);
58220
+ for (const [k, v] of Object.entries(params)) url2.searchParams.set(k, v);
58221
+ const controller = new AbortController();
58222
+ const timeout = setTimeout(() => controller.abort(), 4e3);
58223
+ try {
58224
+ const response = await fetch(url2.toString(), {
58225
+ method: "GET",
58226
+ headers: buildHeaders5(req),
58227
+ signal: controller.signal
58228
+ });
58229
+ const body = await response.json().catch(() => ({}));
58230
+ if (!response.ok) {
58231
+ const upstreamError = typeof body["error"] === "string" ? body["error"] : null;
58232
+ return {
58233
+ items: [],
58234
+ error: upstreamError ? `Iranti /memory/ledger returned HTTP ${response.status}: ${upstreamError}` : `Iranti /memory/ledger returned HTTP ${response.status}`
58235
+ };
58236
+ }
58237
+ const rawItems = Array.isArray(body["items"]) ? body["items"] : [];
58238
+ return {
58239
+ items: rawItems.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)).map(normalizeLedgerEvent)
58240
+ };
58241
+ } catch (err) {
58242
+ return { items: [], error: err instanceof Error ? err.message : String(err) };
58243
+ } finally {
58244
+ clearTimeout(timeout);
58245
+ }
58246
+ }
58247
+ async function fetchIrantiSessionLedger(req, sessionId) {
58248
+ const limit = parseLedgerLimit(req.query["limit"]);
58249
+ const agentId = typeof req.query["agentId"] === "string" ? req.query["agentId"].trim() : "";
58250
+ const actionType = typeof req.query["actionType"] === "string" ? req.query["actionType"].trim() : "";
58251
+ const host = typeof req.query["host"] === "string" ? req.query["host"].trim() : "";
58252
+ const level = normalizeLedgerLevel(req.query["level"]);
58253
+ const startedAt = typeof req.query["startedAt"] === "string" ? req.query["startedAt"].trim() : "";
58254
+ const lastHeartbeatAt = typeof req.query["lastHeartbeatAt"] === "string" ? req.query["lastHeartbeatAt"].trim() : "";
58255
+ const sharedParams = { limit: String(limit) };
58256
+ if (agentId) sharedParams["agentId"] = agentId;
58257
+ if (actionType) sharedParams["actionType"] = actionType;
58258
+ if (host) sharedParams["host"] = host;
58259
+ if (level) sharedParams["level"] = level;
58260
+ try {
58261
+ const bySessionId = fetchLedgerByParams(req, { ...sharedParams, sessionId });
58262
+ let byTimeRange = Promise.resolve({ items: [] });
58263
+ if (agentId && startedAt) {
58264
+ const after = new Date(new Date(startedAt).getTime() - 10 * 6e4).toISOString();
58265
+ const before = lastHeartbeatAt ? new Date(new Date(lastHeartbeatAt).getTime() + 2 * 6e4).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
58266
+ byTimeRange = fetchLedgerByParams(req, {
58267
+ ...sharedParams,
58268
+ agentId,
58269
+ after,
58270
+ before,
58271
+ limit: String(limit)
58272
+ });
58273
+ }
58274
+ const [sessionResult, rangeResult] = await Promise.all([bySessionId, byTimeRange]);
58275
+ if (sessionResult.error && rangeResult.items.length === 0) {
58276
+ return {
58277
+ items: [],
58278
+ total: 0,
58279
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
58280
+ note: "Source: Iranti /memory/ledger",
58281
+ error: sessionResult.error
58282
+ };
58283
+ }
58284
+ const seen = /* @__PURE__ */ new Set();
58285
+ const merged = [];
58286
+ for (const item of [...sessionResult.items, ...rangeResult.items]) {
58287
+ if (item.eventId && !seen.has(item.eventId)) {
58288
+ seen.add(item.eventId);
58289
+ merged.push(item);
58290
+ }
58291
+ }
58292
+ merged.sort((a, b) => b.timestamp > a.timestamp ? 1 : b.timestamp < a.timestamp ? -1 : 0);
58293
+ const limited = merged.slice(0, limit);
58294
+ return {
58295
+ items: limited,
58296
+ total: merged.length,
58297
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
58298
+ note: "Source: Iranti /memory/ledger"
58299
+ };
58300
+ } catch (error2) {
58301
+ return {
58302
+ items: [],
58303
+ total: 0,
58304
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
58305
+ note: "Source: Iranti /memory/ledger",
58306
+ error: error2 instanceof Error ? error2.message : String(error2)
58307
+ };
58308
+ }
58309
+ }
58029
58310
  async function fetchLocalFallbackSessions() {
58030
58311
  const [legacyResult, attendantResult] = await Promise.all([
58031
58312
  query(`
@@ -58095,6 +58376,29 @@ sessionsRouter.get("/", async (req, res) => {
58095
58376
  });
58096
58377
  }
58097
58378
  });
58379
+ sessionsRouter.get("/:sessionId/ledger", async (req, res) => {
58380
+ try {
58381
+ const sessionId = typeof req.params["sessionId"] === "string" ? req.params["sessionId"].trim() : "";
58382
+ if (!sessionId) {
58383
+ res.status(400).json({
58384
+ items: [],
58385
+ total: 0,
58386
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
58387
+ error: "sessionId path parameter is required."
58388
+ });
58389
+ return;
58390
+ }
58391
+ const ledger = await fetchIrantiSessionLedger(req, sessionId);
58392
+ res.json(ledger);
58393
+ } catch (error2) {
58394
+ res.status(400).json({
58395
+ items: [],
58396
+ total: 0,
58397
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
58398
+ error: error2 instanceof Error ? error2.message : String(error2)
58399
+ });
58400
+ }
58401
+ });
58098
58402
  async function proxySessionAction(req, res, action) {
58099
58403
  const agentId = typeof req.body?.agentId === "string" ? req.body.agentId.trim() : "";
58100
58404
  if (!agentId) {
@@ -58149,7 +58453,7 @@ sessionsRouter.use((err, _req, res, _next) => {
58149
58453
 
58150
58454
  // src/server/routes/control-plane/upgrade.ts
58151
58455
  var import_express18 = __toESM(require_express2(), 1);
58152
- var import_child_process2 = require("child_process");
58456
+ var import_child_process3 = require("child_process");
58153
58457
  var upgradeRouter = (0, import_express18.Router)();
58154
58458
  var JOB_STORE_MAX = 50;
58155
58459
  var jobStore = /* @__PURE__ */ new Map();
@@ -58209,7 +58513,7 @@ upgradeRouter.post("/:name/upgrade", async (req, res) => {
58209
58513
  if (runtimeRoot) cliArgs.push("--root", runtimeRoot);
58210
58514
  let child;
58211
58515
  try {
58212
- child = (0, import_child_process2.spawn)(launch.command, cliArgs, {
58516
+ child = (0, import_child_process3.spawn)(launch.command, cliArgs, {
58213
58517
  stdio: ["ignore", "pipe", "pipe"],
58214
58518
  windowsHide: true
58215
58519
  });
@@ -58417,12 +58721,12 @@ installStateRouter.get("/", async (_req, res) => {
58417
58721
 
58418
58722
  // src/server/routes/control-plane/lifecycle.ts
58419
58723
  var import_express21 = __toESM(require_express2(), 1);
58420
- var import_child_process3 = require("child_process");
58421
- var import_path11 = require("path");
58422
- var import_promises6 = require("fs/promises");
58724
+ var import_child_process4 = require("child_process");
58725
+ var import_path12 = require("path");
58726
+ var import_promises7 = require("fs/promises");
58423
58727
 
58424
58728
  // src/server/routes/control-plane/instance-identifiers.ts
58425
- var import_path10 = require("path");
58729
+ var import_path11 = require("path");
58426
58730
  init_db();
58427
58731
  function configuredInstanceEnvPath() {
58428
58732
  const raw = process.env["IRANTI_INSTANCE_ENV"] ?? env["IRANTI_INSTANCE_ENV"] ?? "";
@@ -58431,19 +58735,19 @@ function configuredInstanceEnvPath() {
58431
58735
  function configuredRuntimeRoot() {
58432
58736
  const instanceEnvPath = configuredInstanceEnvPath();
58433
58737
  if (!instanceEnvPath) return process.cwd();
58434
- return (0, import_path10.resolve)((0, import_path10.dirname)(instanceEnvPath), "..", "..");
58738
+ return (0, import_path11.resolve)((0, import_path11.dirname)(instanceEnvPath), "..", "..");
58435
58739
  }
58436
58740
  function configuredInstanceName() {
58437
58741
  const explicit = process.env["IRANTI_INSTANCE"] ?? env["IRANTI_INSTANCE"] ?? "";
58438
58742
  if (explicit.trim()) return explicit.trim();
58439
58743
  const instanceEnvPath = configuredInstanceEnvPath();
58440
58744
  if (!instanceEnvPath) return null;
58441
- return (0, import_path10.basename)((0, import_path10.dirname)(instanceEnvPath));
58745
+ return (0, import_path11.basename)((0, import_path11.dirname)(instanceEnvPath));
58442
58746
  }
58443
58747
  function getConfiguredInstanceIdentifiers() {
58444
58748
  const runtimeRoot = configuredRuntimeRoot();
58445
58749
  const instanceName = configuredInstanceName();
58446
- const instanceDir = instanceName ? (0, import_path10.join)(runtimeRoot, "instances", instanceName) : runtimeRoot;
58750
+ const instanceDir = instanceName ? (0, import_path11.join)(runtimeRoot, "instances", instanceName) : runtimeRoot;
58447
58751
  const instanceId = deriveInstanceId(instanceDir);
58448
58752
  const valid = /* @__PURE__ */ new Set([instanceId]);
58449
58753
  if (instanceName) valid.add(instanceName);
@@ -58471,8 +58775,8 @@ function parseEnvContent2(content) {
58471
58775
  return parsed;
58472
58776
  }
58473
58777
  async function resolveInstancePort(runtimeRoot, name) {
58474
- const envPath = (0, import_path11.join)(runtimeRoot, "instances", name, ".env");
58475
- const content = await (0, import_promises6.readFile)(envPath, "utf8");
58778
+ const envPath = (0, import_path12.join)(runtimeRoot, "instances", name, ".env");
58779
+ const content = await (0, import_promises7.readFile)(envPath, "utf8");
58476
58780
  const parsed = parseEnvContent2(content);
58477
58781
  const rawPort = parsed["IRANTI_PORT"] ?? parsed["PORT"] ?? "3001";
58478
58782
  const port = Number.parseInt(rawPort, 10);
@@ -58520,7 +58824,7 @@ function sleep(ms) {
58520
58824
  }
58521
58825
  function execFileAsync2(command, args) {
58522
58826
  return new Promise((resolve10, reject) => {
58523
- (0, import_child_process3.execFile)(command, args, (error2) => {
58827
+ (0, import_child_process4.execFile)(command, args, (error2) => {
58524
58828
  if (error2) {
58525
58829
  reject(error2);
58526
58830
  return;
@@ -58665,7 +58969,7 @@ lifecycleRouter.post("/:name/start", async (req, res) => {
58665
58969
  });
58666
58970
  return;
58667
58971
  }
58668
- const child = (0, import_child_process3.spawn)(
58972
+ const child = (0, import_child_process4.spawn)(
58669
58973
  launch.command,
58670
58974
  [...launch.args, "run", "--instance", name, "--root", runtimeRoot],
58671
58975
  {
@@ -58863,11 +59167,11 @@ lifecycleRouter.get("/:name/process-status", (req, res) => {
58863
59167
 
58864
59168
  // src/server/routes/control-plane/open-file.ts
58865
59169
  var import_express22 = __toESM(require_express2(), 1);
58866
- var import_child_process4 = require("child_process");
59170
+ var import_child_process5 = require("child_process");
58867
59171
  var import_util7 = require("util");
58868
59172
  var path2 = __toESM(require("path"), 1);
58869
59173
  var fs = __toESM(require("fs/promises"), 1);
58870
- var execAsync = (0, import_util7.promisify)(import_child_process4.exec);
59174
+ var execAsync = (0, import_util7.promisify)(import_child_process5.exec);
58871
59175
  var openFileRouter = (0, import_express22.Router)();
58872
59176
  var ALLOWED_PATHS = /* @__PURE__ */ new Set([
58873
59177
  ".env.iranti",
@@ -58934,20 +59238,21 @@ openFileRouter.post("/", async (req, res) => {
58934
59238
  // src/server/routes/control-plane/auth-keys.ts
58935
59239
  var import_express23 = __toESM(require_express2(), 1);
58936
59240
  var import_crypto4 = require("crypto");
58937
- var import_fs7 = require("fs");
58938
- var import_path12 = require("path");
58939
- init_db();
59241
+ var import_fs8 = require("fs");
59242
+ var import_path13 = require("path");
59243
+ init_esm();
58940
59244
  var authKeysRouter = (0, import_express23.Router)();
59245
+ var { Pool: Pool10 } = esm_default;
58941
59246
  var REGISTRY_ENTITY_TYPE = "system";
58942
59247
  var REGISTRY_ENTITY_ID = "auth";
58943
59248
  var REGISTRY_KEY = "api_keys";
58944
59249
  var REGISTRY_SOURCE = "system";
58945
59250
  var REGISTRY_CREATED_BY = "system";
58946
- function keyPepper() {
58947
- return process.env.IRANTI_API_KEY_PEPPER ?? "";
59251
+ function keyPepper(scope) {
59252
+ return scope.env["IRANTI_API_KEY_PEPPER"]?.trim() ?? "";
58948
59253
  }
58949
- function hashSecret(secret) {
58950
- return (0, import_crypto4.createHash)("sha256").update(`${secret}${keyPepper()}`).digest("hex");
59254
+ function hashSecret(secret, scope) {
59255
+ return (0, import_crypto4.createHash)("sha256").update(`${secret}${keyPepper(scope)}`).digest("hex");
58951
59256
  }
58952
59257
  function generateSecret(length = 32) {
58953
59258
  return (0, import_crypto4.randomBytes)(length).toString("base64url");
@@ -58990,8 +59295,21 @@ function validateScopeList(scopes) {
58990
59295
  }
58991
59296
  }
58992
59297
  }
58993
- async function loadRegistry() {
58994
- const result = await query(
59298
+ async function withScopedPool5(scope, fn) {
59299
+ const pool2 = new Pool10({
59300
+ connectionString: scope.databaseUrl,
59301
+ max: 1,
59302
+ idleTimeoutMillis: 3e4,
59303
+ connectionTimeoutMillis: 5e3
59304
+ });
59305
+ try {
59306
+ return await fn(pool2);
59307
+ } finally {
59308
+ await Promise.resolve(pool2.end()).catch(() => void 0);
59309
+ }
59310
+ }
59311
+ async function loadRegistry(pool2) {
59312
+ const result = await pool2.query(
58995
59313
  `SELECT "valueRaw" FROM knowledge_base
58996
59314
  WHERE "entityType" = $1 AND "entityId" = $2 AND key = $3
58997
59315
  LIMIT 1`,
@@ -59000,13 +59318,12 @@ async function loadRegistry() {
59000
59318
  if (result.rows.length === 0) {
59001
59319
  return { version: 1, keys: [] };
59002
59320
  }
59003
- const raw = result.rows[0].valueRaw;
59004
- return normalizeRegistry(raw);
59321
+ return normalizeRegistry(result.rows[0]?.valueRaw);
59005
59322
  }
59006
- async function saveRegistry(registry2) {
59323
+ async function saveRegistry(pool2, registry2) {
59007
59324
  const normalized = normalizeRegistry(registry2);
59008
59325
  const valueSummary = `API key registry (${normalized.keys.length} keys)`;
59009
- await query(
59326
+ await pool2.query(
59010
59327
  `INSERT INTO knowledge_base
59011
59328
  ("entityType", "entityId", key, "valueRaw", "valueSummary",
59012
59329
  confidence, source, "createdBy", "isProtected", "conflictLog", "createdAt", "updatedAt")
@@ -59056,10 +59373,10 @@ function normalizeRegistry(raw) {
59056
59373
  };
59057
59374
  }
59058
59375
  function syncTokenToProject(projectRoot, token) {
59059
- const envPath = (0, import_path12.resolve)(projectRoot, ".env.iranti");
59376
+ const envPath = (0, import_path13.resolve)(projectRoot, ".env.iranti");
59060
59377
  let lines = [];
59061
- if ((0, import_fs7.existsSync)(envPath)) {
59062
- lines = (0, import_fs7.readFileSync)(envPath, "utf8").split("\n");
59378
+ if ((0, import_fs8.existsSync)(envPath)) {
59379
+ lines = (0, import_fs8.readFileSync)(envPath, "utf8").split("\n");
59063
59380
  }
59064
59381
  let found = false;
59065
59382
  const updated = [];
@@ -59078,25 +59395,32 @@ function syncTokenToProject(projectRoot, token) {
59078
59395
  if (!found) {
59079
59396
  updated.push(`IRANTI_API_KEY=${token}`);
59080
59397
  }
59081
- (0, import_fs7.writeFileSync)(envPath, updated.join("\n"), "utf8");
59398
+ (0, import_fs8.writeFileSync)(envPath, updated.join("\n"), "utf8");
59082
59399
  }
59083
- function getDatabaseUrl() {
59084
- return env["DATABASE_URL"] ?? process.env.DATABASE_URL ?? null;
59085
- }
59086
- function checkDbConfigured(res) {
59087
- if (!getDatabaseUrl()) {
59400
+ async function resolveScopedInstance(req, res) {
59401
+ const instanceId = typeof req.query.instanceId === "string" ? req.query.instanceId.trim() : "";
59402
+ const scope = await resolveInstanceAuthority(instanceId || void 0);
59403
+ if (!scope) {
59404
+ res.status(404).json({
59405
+ error: instanceId ? `Iranti instance not found: ${instanceId}` : "No Iranti instance could be resolved. Select an instance first.",
59406
+ code: "INSTANCE_NOT_FOUND"
59407
+ });
59408
+ return null;
59409
+ }
59410
+ if (!scope.databaseUrl) {
59088
59411
  res.status(503).json({
59089
- error: "No DATABASE_URL configured. Start an Iranti instance first.",
59412
+ error: `No DATABASE_URL configured for instance ${scope.instanceName}. Configure the instance database first.`,
59090
59413
  code: "NO_DATABASE_URL"
59091
59414
  });
59092
- return false;
59415
+ return null;
59093
59416
  }
59094
- return true;
59417
+ return scope;
59095
59418
  }
59096
- authKeysRouter.get("/", async (_req, res) => {
59097
- if (!checkDbConfigured(res)) return;
59419
+ authKeysRouter.get("/", async (req, res) => {
59420
+ const scope = await resolveScopedInstance(req, res);
59421
+ if (!scope) return;
59098
59422
  try {
59099
- const registry2 = await loadRegistry();
59423
+ const registry2 = await withScopedPool5(scope, (pool2) => loadRegistry(pool2));
59100
59424
  const keys = registry2.keys.map((k) => ({
59101
59425
  keyId: k.keyId,
59102
59426
  owner: k.owner,
@@ -59115,7 +59439,8 @@ authKeysRouter.get("/", async (_req, res) => {
59115
59439
  }
59116
59440
  });
59117
59441
  authKeysRouter.post("/", async (req, res) => {
59118
- if (!checkDbConfigured(res)) return;
59442
+ const scope = await resolveScopedInstance(req, res);
59443
+ if (!scope) return;
59119
59444
  const { keyId: keyIdRaw, owner, scopes, description, syncToProject } = req.body;
59120
59445
  if (!keyIdRaw || typeof keyIdRaw !== "string") {
59121
59446
  res.status(400).json({ error: "keyId is required and must be a string." });
@@ -59123,7 +59448,7 @@ authKeysRouter.post("/", async (req, res) => {
59123
59448
  }
59124
59449
  const keyId = sanitizeKeyId(keyIdRaw);
59125
59450
  if (!keyId) {
59126
- res.status(400).json({ error: 'keyId is invalid \u2014 only letters, numbers, "_" and "-" are allowed.' });
59451
+ res.status(400).json({ error: 'keyId is invalid - only letters, numbers, "_" and "-" are allowed.' });
59127
59452
  return;
59128
59453
  }
59129
59454
  if (!owner || typeof owner !== "string" || !owner.trim()) {
@@ -59138,25 +59463,27 @@ authKeysRouter.post("/", async (req, res) => {
59138
59463
  return;
59139
59464
  }
59140
59465
  try {
59141
- const registry2 = await loadRegistry();
59142
59466
  const now = (/* @__PURE__ */ new Date()).toISOString();
59143
59467
  const secret = generateSecret();
59144
- const secretHash = hashSecret(secret);
59145
- const record2 = {
59146
- keyId,
59147
- owner: owner.trim(),
59148
- secretHash,
59149
- scopes: scopeList,
59150
- isActive: true,
59151
- createdAt: now,
59152
- updatedAt: now,
59153
- revokedAt: null,
59154
- description: typeof description === "string" ? description.trim() || void 0 : void 0
59155
- };
59156
- const withoutExisting = registry2.keys.filter((k) => k.keyId !== keyId);
59157
- withoutExisting.push(record2);
59158
- await saveRegistry({ ...registry2, keys: withoutExisting });
59468
+ const secretHash = hashSecret(secret, scope);
59159
59469
  const token = formatToken(keyId, secret);
59470
+ await withScopedPool5(scope, async (pool2) => {
59471
+ const registry2 = await loadRegistry(pool2);
59472
+ const record2 = {
59473
+ keyId,
59474
+ owner: owner.trim(),
59475
+ secretHash,
59476
+ scopes: scopeList,
59477
+ isActive: true,
59478
+ createdAt: now,
59479
+ updatedAt: now,
59480
+ revokedAt: null,
59481
+ description: typeof description === "string" ? description.trim() || void 0 : void 0
59482
+ };
59483
+ const withoutExisting = registry2.keys.filter((key) => key.keyId !== keyId);
59484
+ withoutExisting.push(record2);
59485
+ await saveRegistry(pool2, { ...registry2, keys: withoutExisting });
59486
+ });
59160
59487
  if (typeof syncToProject === "string" && syncToProject.trim()) {
59161
59488
  try {
59162
59489
  syncTokenToProject(syncToProject.trim(), token);
@@ -59172,12 +59499,7 @@ authKeysRouter.post("/", async (req, res) => {
59172
59499
  return;
59173
59500
  }
59174
59501
  }
59175
- res.status(201).json({
59176
- ok: true,
59177
- keyId,
59178
- token,
59179
- scopes: scopeList
59180
- });
59502
+ res.status(201).json({ ok: true, keyId, token, scopes: scopeList });
59181
59503
  } catch (err) {
59182
59504
  const message = err instanceof Error ? err.message : String(err);
59183
59505
  console.error("[auth-keys] POST failed:", message);
@@ -59185,22 +59507,30 @@ authKeysRouter.post("/", async (req, res) => {
59185
59507
  }
59186
59508
  });
59187
59509
  authKeysRouter.delete("/:keyId", async (req, res) => {
59188
- if (!checkDbConfigured(res)) return;
59510
+ const scope = await resolveScopedInstance(req, res);
59511
+ if (!scope) return;
59189
59512
  const keyId = sanitizeKeyId(req.params.keyId ?? "");
59190
59513
  if (!keyId) {
59191
59514
  res.status(400).json({ error: "keyId is required." });
59192
59515
  return;
59193
59516
  }
59194
59517
  try {
59195
- const registry2 = await loadRegistry();
59196
- const target = registry2.keys.find((k) => k.keyId === keyId);
59197
- if (!target) {
59518
+ const revoked = await withScopedPool5(scope, async (pool2) => {
59519
+ const registry2 = await loadRegistry(pool2);
59520
+ const target = registry2.keys.find((key) => key.keyId === keyId);
59521
+ if (!target) {
59522
+ return false;
59523
+ }
59524
+ target.isActive = false;
59525
+ target.revokedAt = (/* @__PURE__ */ new Date()).toISOString();
59526
+ target.updatedAt = target.revokedAt;
59527
+ await saveRegistry(pool2, registry2);
59528
+ return true;
59529
+ });
59530
+ if (!revoked) {
59198
59531
  res.status(404).json({ error: `API key not found: ${keyId}` });
59199
59532
  return;
59200
59533
  }
59201
- target.isActive = false;
59202
- target.revokedAt = (/* @__PURE__ */ new Date()).toISOString();
59203
- await saveRegistry(registry2);
59204
59534
  res.json({ ok: true, keyId });
59205
59535
  } catch (err) {
59206
59536
  const message = err instanceof Error ? err.message : String(err);
@@ -59212,16 +59542,16 @@ authKeysRouter.delete("/:keyId", async (req, res) => {
59212
59542
  // src/server/routes/control-plane/instance-lifecycle.ts
59213
59543
  var import_express24 = __toESM(require_express2(), 1);
59214
59544
  var import_crypto5 = require("crypto");
59215
- var import_fs8 = require("fs");
59216
- var import_promises7 = require("fs/promises");
59217
- var import_path13 = require("path");
59545
+ var import_fs9 = require("fs");
59546
+ var import_promises8 = require("fs/promises");
59547
+ var import_path14 = require("path");
59218
59548
  var import_os3 = require("os");
59219
59549
  init_esm();
59220
59550
  init_db();
59221
59551
  var INSTANCE_NAME_RE3 = /^[a-zA-Z0-9_-]{1,64}$/;
59222
59552
  var ALLOWED_PROVIDERS = ["openai", "claude", "gemini", "groq", "mistral", "ollama", "mock"];
59223
59553
  var ALLOWED_DB_INTENTS = ["dedicated", "shared", "external"];
59224
- var { Pool: Pool8 } = esm_default;
59554
+ var { Pool: Pool11 } = esm_default;
59225
59555
  function isValidInstanceName2(name) {
59226
59556
  return INSTANCE_NAME_RE3.test(name);
59227
59557
  }
@@ -59229,16 +59559,16 @@ function normalizedInstanceCollisionKey(name) {
59229
59559
  return name.trim().toLowerCase().replace(/[-_]+/g, "_");
59230
59560
  }
59231
59561
  function findSiblingInstanceCollision(runtimeRoot, desiredName) {
59232
- const instancesDir = (0, import_path13.join)(runtimeRoot, "instances");
59233
- if (!(0, import_fs8.existsSync)(instancesDir)) return null;
59562
+ const instancesDir = (0, import_path14.join)(runtimeRoot, "instances");
59563
+ if (!(0, import_fs9.existsSync)(instancesDir)) return null;
59234
59564
  const desiredKey = normalizedInstanceCollisionKey(desiredName);
59235
- for (const entry of (0, import_fs8.readdirSync)(instancesDir, { withFileTypes: true })) {
59565
+ for (const entry of (0, import_fs9.readdirSync)(instancesDir, { withFileTypes: true })) {
59236
59566
  if (!entry.isDirectory()) continue;
59237
59567
  if (entry.name === desiredName) continue;
59238
59568
  if (normalizedInstanceCollisionKey(entry.name) !== desiredKey) continue;
59239
59569
  return {
59240
59570
  name: entry.name,
59241
- dir: (0, import_path13.join)(instancesDir, entry.name)
59571
+ dir: (0, import_path14.join)(instancesDir, entry.name)
59242
59572
  };
59243
59573
  }
59244
59574
  return null;
@@ -59264,21 +59594,28 @@ function validateDbUrl(dbUrl) {
59264
59594
  }
59265
59595
  return null;
59266
59596
  }
59597
+ function sanitizeLegacyApiKeyId(input) {
59598
+ return input.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
59599
+ }
59600
+ function generateInstanceApiKey(name) {
59601
+ const keyId = sanitizeLegacyApiKeyId(name + "_control_plane") || "iranti";
59602
+ return keyId + "." + (0, import_crypto5.randomBytes)(32).toString("base64url");
59603
+ }
59267
59604
  function preferredRuntimeRoot() {
59268
59605
  const explicit = env["IRANTI_HOME"]?.trim() ?? process.env["IRANTI_HOME"]?.trim() ?? "";
59269
- if (explicit) return (0, import_path13.resolve)(explicit);
59606
+ if (explicit) return (0, import_path14.resolve)(explicit);
59270
59607
  const configuredInstanceEnv = env["IRANTI_INSTANCE_ENV"]?.trim() ?? process.env["IRANTI_INSTANCE_ENV"]?.trim() ?? "";
59271
59608
  if (configuredInstanceEnv) {
59272
- return (0, import_path13.resolve)((0, import_path13.dirname)(configuredInstanceEnv), "..", "..");
59609
+ return (0, import_path14.resolve)((0, import_path14.dirname)(configuredInstanceEnv), "..", "..");
59273
59610
  }
59274
59611
  const candidates = runtimeRootCandidates();
59275
59612
  if (candidates.length > 0) return candidates[0];
59276
- return (0, import_path13.join)((0, import_os3.homedir)(), ".iranti-runtime");
59613
+ return (0, import_path14.join)((0, import_os3.homedir)(), ".iranti-runtime");
59277
59614
  }
59278
59615
  function parseEnvFile2(filePath) {
59279
- if (!(0, import_fs8.existsSync)(filePath)) return {};
59616
+ if (!(0, import_fs9.existsSync)(filePath)) return {};
59280
59617
  const parsed = {};
59281
- const lines = (0, import_fs8.readFileSync)(filePath, "utf8").split(/\r?\n/);
59618
+ const lines = (0, import_fs9.readFileSync)(filePath, "utf8").split(/\r?\n/);
59282
59619
  for (const line of lines) {
59283
59620
  const trimmed = line.trim();
59284
59621
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -59343,65 +59680,65 @@ async function isInstanceRunning(runtimeRoot, name) {
59343
59680
  }
59344
59681
  }
59345
59682
  function instancePaths(runtimeRoot, name) {
59346
- const instanceDir = (0, import_path13.join)(runtimeRoot, "instances", name);
59683
+ const instanceDir = (0, import_path14.join)(runtimeRoot, "instances", name);
59347
59684
  return {
59348
59685
  instanceDir,
59349
- envFile: (0, import_path13.join)(instanceDir, ".env")
59686
+ envFile: (0, import_path14.join)(instanceDir, ".env")
59350
59687
  };
59351
59688
  }
59352
59689
  async function moveDirectory(fromPath, toPath) {
59353
59690
  try {
59354
- await (0, import_promises7.rename)(fromPath, toPath);
59691
+ await (0, import_promises8.rename)(fromPath, toPath);
59355
59692
  } catch (error2) {
59356
59693
  const code = error2.code;
59357
59694
  if (code !== "EXDEV") throw error2;
59358
- await (0, import_promises7.cp)(fromPath, toPath, { recursive: true, force: true });
59359
- await (0, import_promises7.rm)(fromPath, { recursive: true, force: true });
59695
+ await (0, import_promises8.cp)(fromPath, toPath, { recursive: true, force: true });
59696
+ await (0, import_promises8.rm)(fromPath, { recursive: true, force: true });
59360
59697
  }
59361
59698
  }
59362
59699
  async function rewriteProjectBindingInstanceEnv(projectPath, oldEnvPath, newEnvPath) {
59363
- const bindingPath = (0, import_path13.join)(projectPath, ".env.iranti");
59364
- if (!(0, import_fs8.existsSync)(bindingPath)) return;
59365
- const parsed = parseSimpleEnv2(await (0, import_promises7.readFile)(bindingPath, "utf8"));
59700
+ const bindingPath = (0, import_path14.join)(projectPath, ".env.iranti");
59701
+ if (!(0, import_fs9.existsSync)(bindingPath)) return;
59702
+ const parsed = parseSimpleEnv2(await (0, import_promises8.readFile)(bindingPath, "utf8"));
59366
59703
  const current = parsed["IRANTI_INSTANCE_ENV"]?.trim() ?? "";
59367
59704
  if (!current) return;
59368
- if ((0, import_path13.resolve)(current) !== (0, import_path13.resolve)(oldEnvPath)) return;
59705
+ if ((0, import_path14.resolve)(current) !== (0, import_path14.resolve)(oldEnvPath)) return;
59369
59706
  parsed["IRANTI_INSTANCE_ENV"] = newEnvPath;
59370
- await (0, import_promises7.writeFile)(bindingPath, buildSimpleEnvFile(parsed), "utf8");
59707
+ await (0, import_promises8.writeFile)(bindingPath, buildSimpleEnvFile(parsed), "utf8");
59371
59708
  }
59372
59709
  async function rewriteMovedInstanceEnv(instanceDir, previousInstanceDir) {
59373
- const envPath = (0, import_path13.join)(instanceDir, ".env");
59374
- if (!(0, import_fs8.existsSync)(envPath)) return;
59375
- const parsed = parseSimpleEnv2(await (0, import_promises7.readFile)(envPath, "utf8"));
59376
- const oldBase = (0, import_path13.resolve)(previousInstanceDir);
59377
- const nextEscalation = (0, import_path13.join)(instanceDir, "escalation");
59378
- const nextRequestLog = (0, import_path13.join)(instanceDir, "logs", "api-requests.log");
59710
+ const envPath = (0, import_path14.join)(instanceDir, ".env");
59711
+ if (!(0, import_fs9.existsSync)(envPath)) return;
59712
+ const parsed = parseSimpleEnv2(await (0, import_promises8.readFile)(envPath, "utf8"));
59713
+ const oldBase = (0, import_path14.resolve)(previousInstanceDir);
59714
+ const nextEscalation = (0, import_path14.join)(instanceDir, "escalation");
59715
+ const nextRequestLog = (0, import_path14.join)(instanceDir, "logs", "api-requests.log");
59379
59716
  const currentEscalation = parsed["IRANTI_ESCALATION_DIR"]?.trim() ?? "";
59380
59717
  const currentRequestLog = parsed["IRANTI_REQUEST_LOG_FILE"]?.trim() ?? "";
59381
- if (!currentEscalation || (0, import_path13.resolve)(currentEscalation).startsWith(oldBase)) {
59718
+ if (!currentEscalation || (0, import_path14.resolve)(currentEscalation).startsWith(oldBase)) {
59382
59719
  parsed["IRANTI_ESCALATION_DIR"] = nextEscalation;
59383
59720
  }
59384
- if (!currentRequestLog || (0, import_path13.resolve)((0, import_path13.dirname)(currentRequestLog)).startsWith((0, import_path13.resolve)((0, import_path13.join)(previousInstanceDir, "logs")))) {
59721
+ if (!currentRequestLog || (0, import_path14.resolve)((0, import_path14.dirname)(currentRequestLog)).startsWith((0, import_path14.resolve)((0, import_path14.join)(previousInstanceDir, "logs")))) {
59385
59722
  parsed["IRANTI_REQUEST_LOG_FILE"] = nextRequestLog;
59386
59723
  }
59387
- await (0, import_promises7.writeFile)(envPath, buildSimpleEnvFile(parsed), "utf8");
59724
+ await (0, import_promises8.writeFile)(envPath, buildSimpleEnvFile(parsed), "utf8");
59388
59725
  }
59389
59726
  async function rewriteMovedInstanceMeta(instanceDir, name) {
59390
- const metaPath = (0, import_path13.join)(instanceDir, "instance.json");
59391
- if (!(0, import_fs8.existsSync)(metaPath)) return;
59392
- const parsed = JSON.parse(await (0, import_promises7.readFile)(metaPath, "utf8"));
59727
+ const metaPath = (0, import_path14.join)(instanceDir, "instance.json");
59728
+ if (!(0, import_fs9.existsSync)(metaPath)) return;
59729
+ const parsed = JSON.parse(await (0, import_promises8.readFile)(metaPath, "utf8"));
59393
59730
  parsed["name"] = name;
59394
59731
  parsed["instanceDir"] = instanceDir;
59395
- parsed["envFile"] = (0, import_path13.join)(instanceDir, ".env");
59396
- await (0, import_promises7.writeFile)(metaPath, `${JSON.stringify(parsed, null, 2)}
59732
+ parsed["envFile"] = (0, import_path14.join)(instanceDir, ".env");
59733
+ await (0, import_promises8.writeFile)(metaPath, `${JSON.stringify(parsed, null, 2)}
59397
59734
  `, "utf8");
59398
59735
  }
59399
59736
  async function clearMovedRuntimeMetadata(instanceDir) {
59400
- const runtimePath = (0, import_path13.join)(instanceDir, "runtime.json");
59401
- if (!(0, import_fs8.existsSync)(runtimePath)) return;
59402
- await (0, import_promises7.rm)(runtimePath, { force: true });
59737
+ const runtimePath = (0, import_path14.join)(instanceDir, "runtime.json");
59738
+ if (!(0, import_fs9.existsSync)(runtimePath)) return;
59739
+ await (0, import_promises8.rm)(runtimePath, { force: true });
59403
59740
  }
59404
- function escapePgIdentifier(value) {
59741
+ function escapePgIdentifier2(value) {
59405
59742
  return `"${value.replace(/"/g, '""')}"`;
59406
59743
  }
59407
59744
  function parseDatabaseTarget2(dbUrl) {
@@ -59421,13 +59758,13 @@ function parseDatabaseTarget2(dbUrl) {
59421
59758
  }
59422
59759
  async function dropDatabase(dbUrl) {
59423
59760
  const { adminUrl, databaseName } = parseDatabaseTarget2(dbUrl);
59424
- const pool2 = new Pool8({ connectionString: adminUrl });
59761
+ const pool2 = new Pool11({ connectionString: adminUrl });
59425
59762
  try {
59426
59763
  await pool2.query(
59427
59764
  "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = $1 AND pid <> pg_backend_pid()",
59428
59765
  [databaseName]
59429
59766
  );
59430
- await pool2.query(`DROP DATABASE IF EXISTS ${escapePgIdentifier(databaseName)}`);
59767
+ await pool2.query(`DROP DATABASE IF EXISTS ${escapePgIdentifier2(databaseName)}`);
59431
59768
  } finally {
59432
59769
  await pool2.end();
59433
59770
  }
@@ -59436,8 +59773,8 @@ async function dropDatabase(dbUrl) {
59436
59773
  async function deleteBoundProjectFiles(projectPaths) {
59437
59774
  const removed = [];
59438
59775
  for (const projectPath of projectPaths) {
59439
- const bindingPath = (0, import_path13.join)(projectPath, ".env.iranti");
59440
- await (0, import_promises7.rm)(bindingPath, { force: true });
59776
+ const bindingPath = (0, import_path14.join)(projectPath, ".env.iranti");
59777
+ await (0, import_promises8.rm)(bindingPath, { force: true });
59441
59778
  removed.push(bindingPath);
59442
59779
  }
59443
59780
  return removed;
@@ -59493,8 +59830,9 @@ instanceLifecycleRouter.post("/instances", async (req, res) => {
59493
59830
  return;
59494
59831
  }
59495
59832
  const runtimeRoot = preferredRuntimeRoot();
59833
+ const generatedApiKey = generateInstanceApiKey(name);
59496
59834
  const { instanceDir, envFile } = instancePaths(runtimeRoot, name);
59497
- if ((0, import_fs8.existsSync)(instanceDir)) {
59835
+ if ((0, import_fs9.existsSync)(instanceDir)) {
59498
59836
  res.status(409).json({
59499
59837
  error: "Instance already exists.",
59500
59838
  code: "INSTANCE_EXISTS"
@@ -59519,6 +59857,8 @@ instanceLifecycleRouter.post("/instances", async (req, res) => {
59519
59857
  String(port),
59520
59858
  "--db-url",
59521
59859
  dbUrl.trim(),
59860
+ "--api-key",
59861
+ generatedApiKey,
59522
59862
  "--provider",
59523
59863
  provider
59524
59864
  ];
@@ -59539,7 +59879,7 @@ instanceLifecycleRouter.post("/instances", async (req, res) => {
59539
59879
  try {
59540
59880
  let envContent;
59541
59881
  try {
59542
- envContent = await (0, import_promises7.readFile)(envFile, "utf8");
59882
+ envContent = await (0, import_promises8.readFile)(envFile, "utf8");
59543
59883
  } catch {
59544
59884
  envContent = "";
59545
59885
  }
@@ -59547,12 +59887,22 @@ instanceLifecycleRouter.post("/instances", async (req, res) => {
59547
59887
  const pepper = (0, import_crypto5.randomBytes)(32).toString("hex");
59548
59888
  const pepperLine = `IRANTI_API_KEY_PEPPER=${pepper}`;
59549
59889
  envContent = envContent.endsWith("\n") ? envContent + pepperLine + "\n" : envContent + "\n" + pepperLine + "\n";
59550
- await (0, import_promises7.writeFile)(envFile, envContent, "utf8");
59890
+ await (0, import_promises8.writeFile)(envFile, envContent, "utf8");
59551
59891
  }
59552
59892
  } catch {
59553
59893
  console.warn(`[instance-lifecycle] Failed to write IRANTI_API_KEY_PEPPER to ${envFile}`);
59554
59894
  }
59555
59895
  const instanceEnv = parseEnvFile2(envFile);
59896
+ let note = "Instance created. Open it in Control Plane to start the runtime, finish provider setup, and bind projects.";
59897
+ try {
59898
+ const prov = await provisionDatabase(dbUrl.trim(), instanceDir);
59899
+ if (prov.migrated) {
59900
+ note = prov.created ? `Instance and database "${prov.databaseName}" created \u2014 ready to start.` : `Instance created. Database "${prov.databaseName}" already existed \u2014 migrations applied.`;
59901
+ } else if (prov.created) {
59902
+ note = `Instance and database "${prov.databaseName}" created. ${prov.migrationNote} Use the setup or repair flow if migrations are still needed.`;
59903
+ }
59904
+ } catch {
59905
+ }
59556
59906
  res.status(201).json({
59557
59907
  ok: true,
59558
59908
  name,
@@ -59560,7 +59910,7 @@ instanceLifecycleRouter.post("/instances", async (req, res) => {
59560
59910
  envFile,
59561
59911
  port,
59562
59912
  provider: instanceEnv["LLM_PROVIDER"] ?? provider,
59563
- note: "Instance created. Open it in Control Plane to start the runtime, finish provider setup, and bind projects."
59913
+ note
59564
59914
  });
59565
59915
  });
59566
59916
  instanceLifecycleRouter.patch("/instances/:name", async (req, res) => {
@@ -59574,7 +59924,7 @@ instanceLifecycleRouter.patch("/instances/:name", async (req, res) => {
59574
59924
  }
59575
59925
  const runtimeRoot = preferredRuntimeRoot();
59576
59926
  const { instanceDir, envFile } = instancePaths(runtimeRoot, name);
59577
- if (!(0, import_fs8.existsSync)(instanceDir)) {
59927
+ if (!(0, import_fs9.existsSync)(instanceDir)) {
59578
59928
  res.status(404).json({
59579
59929
  error: `Instance "${name}" not found.`,
59580
59930
  code: "NOT_FOUND"
@@ -59704,7 +60054,7 @@ instanceLifecycleRouter.delete("/instances/:name", async (req, res) => {
59704
60054
  const resolvedAuthority = await resolveInstanceAuthority(name);
59705
60055
  const runtimeRoot = resolvedAuthority?.runtimeRoot ?? preferredRuntimeRoot();
59706
60056
  const { instanceDir, envFile } = resolvedAuthority ? { instanceDir: resolvedAuthority.instanceDir, envFile: resolvedAuthority.instanceEnvPath } : instancePaths(runtimeRoot, name);
59707
- if (!(0, import_fs8.existsSync)(instanceDir)) {
60057
+ if (!(0, import_fs9.existsSync)(instanceDir)) {
59708
60058
  res.status(404).json({
59709
60059
  error: `Instance "${name}" not found.`,
59710
60060
  code: "NOT_FOUND"
@@ -59737,7 +60087,7 @@ instanceLifecycleRouter.delete("/instances/:name", async (req, res) => {
59737
60087
  if (dropDatabaseRequested) {
59738
60088
  droppedDatabaseName = await dropDatabase(databaseUrl2);
59739
60089
  }
59740
- await (0, import_promises7.rm)(instanceDir, { recursive: true, force: true });
60090
+ await (0, import_promises8.rm)(instanceDir, { recursive: true, force: true });
59741
60091
  } catch (error2) {
59742
60092
  res.status(500).json({
59743
60093
  error: commandFailureMessage(error2),
@@ -59774,7 +60124,7 @@ instanceLifecycleRouter.post("/instances/:name/migrate-root", async (req, res) =
59774
60124
  }
59775
60125
  const currentRoot = resolvedAuthority.runtimeRoot;
59776
60126
  const targetRoot = preferredRuntimeRoot();
59777
- if ((0, import_path13.resolve)(currentRoot) === (0, import_path13.resolve)(targetRoot)) {
60127
+ if ((0, import_path14.resolve)(currentRoot) === (0, import_path14.resolve)(targetRoot)) {
59778
60128
  res.status(200).json({
59779
60129
  ok: true,
59780
60130
  name,
@@ -59794,8 +60144,8 @@ instanceLifecycleRouter.post("/instances/:name/migrate-root", async (req, res) =
59794
60144
  });
59795
60145
  return;
59796
60146
  }
59797
- const targetInstanceDir = (0, import_path13.join)(targetRoot, "instances", name);
59798
- if ((0, import_fs8.existsSync)(targetInstanceDir)) {
60147
+ const targetInstanceDir = (0, import_path14.join)(targetRoot, "instances", name);
60148
+ if ((0, import_fs9.existsSync)(targetInstanceDir)) {
59799
60149
  res.status(409).json({
59800
60150
  error: `Target runtime root already contains an instance named "${name}".`,
59801
60151
  code: "TARGET_EXISTS"
@@ -59803,9 +60153,9 @@ instanceLifecycleRouter.post("/instances/:name/migrate-root", async (req, res) =
59803
60153
  return;
59804
60154
  }
59805
60155
  const oldEnvPath = resolvedAuthority.instanceEnvPath;
59806
- const newEnvPath = (0, import_path13.join)(targetInstanceDir, ".env");
60156
+ const newEnvPath = (0, import_path14.join)(targetInstanceDir, ".env");
59807
60157
  try {
59808
- await (0, import_promises7.mkdir)((0, import_path13.join)(targetRoot, "instances"), { recursive: true });
60158
+ await (0, import_promises8.mkdir)((0, import_path14.join)(targetRoot, "instances"), { recursive: true });
59809
60159
  await moveDirectory(resolvedAuthority.instanceDir, targetInstanceDir);
59810
60160
  await rewriteMovedInstanceEnv(targetInstanceDir, resolvedAuthority.instanceDir);
59811
60161
  await rewriteMovedInstanceMeta(targetInstanceDir, name);
@@ -59835,20 +60185,20 @@ instanceLifecycleRouter.post("/instances/:name/migrate-root", async (req, res) =
59835
60185
 
59836
60186
  // src/server/routes/control-plane/project-bindings.ts
59837
60187
  var import_express25 = __toESM(require_express2(), 1);
59838
- var import_fs9 = require("fs");
59839
- var import_path14 = require("path");
60188
+ var import_fs10 = require("fs");
60189
+ var import_path15 = require("path");
59840
60190
  var import_os4 = require("os");
59841
60191
  var projectBindingsRouter = (0, import_express25.Router)();
59842
60192
  var INSTANCE_NAME_RE4 = /^[a-zA-Z0-9_-]{1,64}$/;
59843
60193
  function getRuntimeRoot() {
59844
- return process.env.IRANTI_HOME ?? (0, import_path14.join)((0, import_os4.homedir)(), ".iranti-runtime");
60194
+ return process.env.IRANTI_HOME ?? (0, import_path15.join)((0, import_os4.homedir)(), ".iranti-runtime");
59845
60195
  }
59846
60196
  function getInstanceEnvPath(runtimeRoot, instanceName) {
59847
- return (0, import_path14.join)(runtimeRoot, "instances", instanceName, ".env");
60197
+ return (0, import_path15.join)(runtimeRoot, "instances", instanceName, ".env");
59848
60198
  }
59849
60199
  function parseEnvFile3(filePath) {
59850
60200
  const result = {};
59851
- const lines = (0, import_fs9.readFileSync)(filePath, "utf8").split("\n");
60201
+ const lines = (0, import_fs10.readFileSync)(filePath, "utf8").split("\n");
59852
60202
  for (const line of lines) {
59853
60203
  const trimmed = line.trim();
59854
60204
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -59864,12 +60214,12 @@ function buildEnvFileContent(pairs) {
59864
60214
  return Object.entries(pairs).map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
59865
60215
  }
59866
60216
  function getRegistryPath(runtimeRoot, instanceName) {
59867
- return (0, import_path14.join)(runtimeRoot, "instances", instanceName, "projects.json");
60217
+ return (0, import_path15.join)(runtimeRoot, "instances", instanceName, "projects.json");
59868
60218
  }
59869
60219
  function readRegistry2(registryPath) {
59870
- if (!(0, import_fs9.existsSync)(registryPath)) return { projects: [] };
60220
+ if (!(0, import_fs10.existsSync)(registryPath)) return { projects: [] };
59871
60221
  try {
59872
- const raw = (0, import_fs9.readFileSync)(registryPath, "utf8");
60222
+ const raw = (0, import_fs10.readFileSync)(registryPath, "utf8");
59873
60223
  const parsed = JSON.parse(raw);
59874
60224
  if (!Array.isArray(parsed.projects)) return { projects: [] };
59875
60225
  return parsed;
@@ -59879,8 +60229,8 @@ function readRegistry2(registryPath) {
59879
60229
  }
59880
60230
  function writeRegistry(registryPath, registry2) {
59881
60231
  const dir = registryPath.replace(/[/\\][^/\\]+$/, "");
59882
- (0, import_fs9.mkdirSync)(dir, { recursive: true });
59883
- (0, import_fs9.writeFileSync)(registryPath, JSON.stringify(registry2, null, 2) + "\n", "utf8");
60232
+ (0, import_fs10.mkdirSync)(dir, { recursive: true });
60233
+ (0, import_fs10.writeFileSync)(registryPath, JSON.stringify(registry2, null, 2) + "\n", "utf8");
59884
60234
  }
59885
60235
  function removeProjectFromRegistry(registryPath, projectPath) {
59886
60236
  const registry2 = readRegistry2(registryPath);
@@ -59952,37 +60302,37 @@ function removeIrantiClaudeHooksFromValue(value) {
59952
60302
  function cleanupProjectIntegrations(projectPath) {
59953
60303
  const result = { removed: [], updated: [], warnings: [] };
59954
60304
  const candidates = [
59955
- (0, import_path14.join)(projectPath, ".mcp.json"),
59956
- (0, import_path14.join)(projectPath, ".vscode", "mcp.json")
60305
+ (0, import_path15.join)(projectPath, ".mcp.json"),
60306
+ (0, import_path15.join)(projectPath, ".vscode", "mcp.json")
59957
60307
  ];
59958
60308
  for (const candidate of candidates) {
59959
- if (!(0, import_fs9.existsSync)(candidate)) continue;
60309
+ if (!(0, import_fs10.existsSync)(candidate)) continue;
59960
60310
  try {
59961
- const parsed = JSON.parse((0, import_fs9.readFileSync)(candidate, "utf8"));
60311
+ const parsed = JSON.parse((0, import_fs10.readFileSync)(candidate, "utf8"));
59962
60312
  const next = removeIrantiMcpServerFromValue(parsed);
59963
60313
  if (next === parsed) continue;
59964
60314
  if (!next) {
59965
- (0, import_fs9.rmSync)(candidate, { force: true });
60315
+ (0, import_fs10.rmSync)(candidate, { force: true });
59966
60316
  result.removed.push(candidate);
59967
60317
  } else {
59968
- (0, import_fs9.writeFileSync)(candidate, JSON.stringify(next, null, 2) + "\n", "utf8");
60318
+ (0, import_fs10.writeFileSync)(candidate, JSON.stringify(next, null, 2) + "\n", "utf8");
59969
60319
  result.updated.push(candidate);
59970
60320
  }
59971
60321
  } catch {
59972
60322
  result.warnings.push(`Skipped unreadable MCP file ${candidate}`);
59973
60323
  }
59974
60324
  }
59975
- const claudeSettingsFile = (0, import_path14.join)(projectPath, ".claude", "settings.local.json");
59976
- if ((0, import_fs9.existsSync)(claudeSettingsFile)) {
60325
+ const claudeSettingsFile = (0, import_path15.join)(projectPath, ".claude", "settings.local.json");
60326
+ if ((0, import_fs10.existsSync)(claudeSettingsFile)) {
59977
60327
  try {
59978
- const parsed = JSON.parse((0, import_fs9.readFileSync)(claudeSettingsFile, "utf8"));
60328
+ const parsed = JSON.parse((0, import_fs10.readFileSync)(claudeSettingsFile, "utf8"));
59979
60329
  const next = removeIrantiClaudeHooksFromValue(parsed);
59980
60330
  if (next !== parsed) {
59981
60331
  if (!next) {
59982
- (0, import_fs9.rmSync)(claudeSettingsFile, { force: true });
60332
+ (0, import_fs10.rmSync)(claudeSettingsFile, { force: true });
59983
60333
  result.removed.push(claudeSettingsFile);
59984
60334
  } else {
59985
- (0, import_fs9.writeFileSync)(claudeSettingsFile, JSON.stringify(next, null, 2) + "\n", "utf8");
60335
+ (0, import_fs10.writeFileSync)(claudeSettingsFile, JSON.stringify(next, null, 2) + "\n", "utf8");
59986
60336
  result.updated.push(claudeSettingsFile);
59987
60337
  }
59988
60338
  }
@@ -59993,15 +60343,15 @@ function cleanupProjectIntegrations(projectPath) {
59993
60343
  return result;
59994
60344
  }
59995
60345
  function ensureGitignoreEntry(projectPath, entry) {
59996
- const gitignorePath = (0, import_path14.join)(projectPath, ".gitignore");
59997
- if ((0, import_fs9.existsSync)(gitignorePath)) {
59998
- const content = (0, import_fs9.readFileSync)(gitignorePath, "utf8");
60346
+ const gitignorePath = (0, import_path15.join)(projectPath, ".gitignore");
60347
+ if ((0, import_fs10.existsSync)(gitignorePath)) {
60348
+ const content = (0, import_fs10.readFileSync)(gitignorePath, "utf8");
59999
60349
  const lines = content.split("\n").map((l) => l.trim());
60000
60350
  if (lines.includes(entry)) return;
60001
60351
  const updated = content.endsWith("\n") ? content + entry + "\n" : content + "\n" + entry + "\n";
60002
- (0, import_fs9.writeFileSync)(gitignorePath, updated, "utf8");
60352
+ (0, import_fs10.writeFileSync)(gitignorePath, updated, "utf8");
60003
60353
  } else {
60004
- (0, import_fs9.writeFileSync)(gitignorePath, entry + "\n", "utf8");
60354
+ (0, import_fs10.writeFileSync)(gitignorePath, entry + "\n", "utf8");
60005
60355
  }
60006
60356
  }
60007
60357
  projectBindingsRouter.post(
@@ -60025,16 +60375,16 @@ projectBindingsRouter.post(
60025
60375
  res.status(400).json({ error: "projectPath is required" });
60026
60376
  return;
60027
60377
  }
60028
- if (!(0, import_path14.isAbsolute)(projectPath)) {
60378
+ if (!(0, import_path15.isAbsolute)(projectPath)) {
60029
60379
  res.status(400).json({ error: "projectPath must be an absolute path" });
60030
60380
  return;
60031
60381
  }
60032
- if (!(0, import_fs9.existsSync)(projectPath)) {
60382
+ if (!(0, import_fs10.existsSync)(projectPath)) {
60033
60383
  res.status(400).json({ error: "projectPath does not exist" });
60034
60384
  return;
60035
60385
  }
60036
60386
  try {
60037
- const stat2 = (0, import_fs9.statSync)(projectPath);
60387
+ const stat2 = (0, import_fs10.statSync)(projectPath);
60038
60388
  if (!stat2.isDirectory()) {
60039
60389
  res.status(400).json({ error: "projectPath must be a directory" });
60040
60390
  return;
@@ -60045,7 +60395,7 @@ projectBindingsRouter.post(
60045
60395
  }
60046
60396
  const runtimeRoot = getRuntimeRoot();
60047
60397
  const instanceEnvPath = getInstanceEnvPath(runtimeRoot, instanceName);
60048
- if (!(0, import_fs9.existsSync)(instanceEnvPath)) {
60398
+ if (!(0, import_fs10.existsSync)(instanceEnvPath)) {
60049
60399
  res.status(404).json({
60050
60400
  error: `Instance '${instanceName}' not found`,
60051
60401
  instanceEnvPath
@@ -60070,7 +60420,7 @@ projectBindingsRouter.post(
60070
60420
  return;
60071
60421
  }
60072
60422
  const resolvedAgentId = agentId ?? "main_agent";
60073
- const resolvedMemoryEntity = memoryEntity ?? `project/${(0, import_path14.basename)(projectPath)}`;
60423
+ const resolvedMemoryEntity = memoryEntity ?? `project/${(0, import_path15.basename)(projectPath)}`;
60074
60424
  const resolvedPersonalMemoryEntity = personalMemoryEntity ?? "user/main";
60075
60425
  const resolvedMode = mode === "isolated" || mode === "shared" ? mode : "isolated";
60076
60426
  const resolvedAutoRemember = autoRemember === true ? "true" : "false";
@@ -60085,9 +60435,9 @@ projectBindingsRouter.post(
60085
60435
  IRANTI_INSTANCE_ENV: instanceEnvPath,
60086
60436
  IRANTI_AUTO_REMEMBER: resolvedAutoRemember
60087
60437
  });
60088
- const envIrantiPath = (0, import_path14.join)(projectPath, ".env.iranti");
60438
+ const envIrantiPath = (0, import_path15.join)(projectPath, ".env.iranti");
60089
60439
  try {
60090
- (0, import_fs9.writeFileSync)(envIrantiPath, envIrantiContent, "utf8");
60440
+ (0, import_fs10.writeFileSync)(envIrantiPath, envIrantiContent, "utf8");
60091
60441
  } catch (err) {
60092
60442
  res.status(500).json({ error: "Failed to write .env.iranti", detail: String(err) });
60093
60443
  return;
@@ -60147,18 +60497,18 @@ projectBindingsRouter.patch(
60147
60497
  res.status(400).json({ error: "projectPath query param is required" });
60148
60498
  return;
60149
60499
  }
60150
- if (!(0, import_path14.isAbsolute)(projectPath)) {
60500
+ if (!(0, import_path15.isAbsolute)(projectPath)) {
60151
60501
  res.status(400).json({ error: "projectPath must be an absolute path" });
60152
60502
  return;
60153
60503
  }
60154
60504
  const runtimeRoot = getRuntimeRoot();
60155
60505
  const currentInstanceEnvPath = getInstanceEnvPath(runtimeRoot, instanceName);
60156
- if (!(0, import_fs9.existsSync)(currentInstanceEnvPath)) {
60506
+ if (!(0, import_fs10.existsSync)(currentInstanceEnvPath)) {
60157
60507
  res.status(404).json({ error: `Instance '${instanceName}' not found` });
60158
60508
  return;
60159
60509
  }
60160
- const envIrantiPath = (0, import_path14.join)(projectPath, ".env.iranti");
60161
- if (!(0, import_fs9.existsSync)(envIrantiPath)) {
60510
+ const envIrantiPath = (0, import_path15.join)(projectPath, ".env.iranti");
60511
+ if (!(0, import_fs10.existsSync)(envIrantiPath)) {
60162
60512
  res.status(404).json({
60163
60513
  error: ".env.iranti not found at projectPath \u2014 bind the project first",
60164
60514
  envIrantiPath
@@ -60181,7 +60531,7 @@ projectBindingsRouter.patch(
60181
60531
  return;
60182
60532
  }
60183
60533
  const targetEnvPath = getInstanceEnvPath(runtimeRoot, targetInstanceName);
60184
- if (!(0, import_fs9.existsSync)(targetEnvPath)) {
60534
+ if (!(0, import_fs10.existsSync)(targetEnvPath)) {
60185
60535
  res.status(404).json({ error: `Target instance '${targetInstanceName}' not found` });
60186
60536
  return;
60187
60537
  }
@@ -60253,7 +60603,7 @@ projectBindingsRouter.patch(
60253
60603
  if (!canonicalKeys.includes(k)) reordered[k] = v;
60254
60604
  }
60255
60605
  try {
60256
- (0, import_fs9.writeFileSync)(envIrantiPath, buildEnvFileContent(reordered), "utf8");
60606
+ (0, import_fs10.writeFileSync)(envIrantiPath, buildEnvFileContent(reordered), "utf8");
60257
60607
  } catch (err) {
60258
60608
  res.status(500).json({ error: "Failed to write updated .env.iranti", detail: String(err) });
60259
60609
  return;
@@ -60275,7 +60625,7 @@ projectBindingsRouter.patch(
60275
60625
  const updatedEntry = {
60276
60626
  projectPath,
60277
60627
  agentId: movedEntry?.agentId ?? updated["IRANTI_AGENT_ID"] ?? "main_agent",
60278
- memoryEntity: movedEntry?.memoryEntity ?? updated["IRANTI_MEMORY_ENTITY"] ?? `project/${(0, import_path14.basename)(projectPath)}`,
60628
+ memoryEntity: movedEntry?.memoryEntity ?? updated["IRANTI_MEMORY_ENTITY"] ?? `project/${(0, import_path15.basename)(projectPath)}`,
60279
60629
  mode: mode ?? movedEntry?.mode ?? "isolated",
60280
60630
  boundAt: movedEntry?.boundAt ?? (/* @__PURE__ */ new Date()).toISOString()
60281
60631
  };
@@ -60314,18 +60664,18 @@ projectBindingsRouter.delete(
60314
60664
  res.status(400).json({ error: "projectPath query param is required" });
60315
60665
  return;
60316
60666
  }
60317
- if (!(0, import_path14.isAbsolute)(projectPath)) {
60667
+ if (!(0, import_path15.isAbsolute)(projectPath)) {
60318
60668
  res.status(400).json({ error: "projectPath must be an absolute path" });
60319
60669
  return;
60320
60670
  }
60321
60671
  const runtimeRoot = getRuntimeRoot();
60322
60672
  const instanceEnvPath = getInstanceEnvPath(runtimeRoot, instanceName);
60323
- if (!(0, import_fs9.existsSync)(instanceEnvPath)) {
60673
+ if (!(0, import_fs10.existsSync)(instanceEnvPath)) {
60324
60674
  res.status(404).json({ error: `Instance '${instanceName}' not found` });
60325
60675
  return;
60326
60676
  }
60327
- const envIrantiPath = (0, import_path14.join)(projectPath, ".env.iranti");
60328
- if (!(0, import_fs9.existsSync)(envIrantiPath)) {
60677
+ const envIrantiPath = (0, import_path15.join)(projectPath, ".env.iranti");
60678
+ if (!(0, import_fs10.existsSync)(envIrantiPath)) {
60329
60679
  res.status(404).json({
60330
60680
  error: ".env.iranti not found at projectPath \u2014 bind the project first",
60331
60681
  envIrantiPath
@@ -60336,7 +60686,7 @@ projectBindingsRouter.delete(
60336
60686
  const registryPath = getRegistryPath(runtimeRoot, instanceName);
60337
60687
  const registryRemoved = removeProjectFromRegistry(registryPath, projectPath);
60338
60688
  try {
60339
- (0, import_fs9.rmSync)(envIrantiPath, { force: true });
60689
+ (0, import_fs10.rmSync)(envIrantiPath, { force: true });
60340
60690
  } catch (err) {
60341
60691
  res.status(500).json({ error: "Failed to remove .env.iranti", detail: String(err) });
60342
60692
  return;
@@ -60364,17 +60714,17 @@ projectBindingsRouter.get(
60364
60714
  }
60365
60715
  const runtimeRoot = getRuntimeRoot();
60366
60716
  const instanceEnvPath = getInstanceEnvPath(runtimeRoot, instanceName);
60367
- if (!(0, import_fs9.existsSync)(instanceEnvPath)) {
60717
+ if (!(0, import_fs10.existsSync)(instanceEnvPath)) {
60368
60718
  res.status(404).json({ error: `Instance '${instanceName}' not found` });
60369
60719
  return;
60370
60720
  }
60371
60721
  const registryPath = getRegistryPath(runtimeRoot, instanceName);
60372
60722
  const registry2 = readRegistry2(registryPath);
60373
60723
  const live = registry2.projects.filter(
60374
- (entry) => (0, import_fs9.existsSync)((0, import_path14.join)(entry.projectPath, ".env.iranti"))
60724
+ (entry) => (0, import_fs10.existsSync)((0, import_path15.join)(entry.projectPath, ".env.iranti"))
60375
60725
  );
60376
- const cwdBindingPath = (0, import_path14.join)(process.cwd(), ".env.iranti");
60377
- if ((0, import_fs9.existsSync)(cwdBindingPath)) {
60726
+ const cwdBindingPath = (0, import_path15.join)(process.cwd(), ".env.iranti");
60727
+ if ((0, import_fs10.existsSync)(cwdBindingPath)) {
60378
60728
  try {
60379
60729
  const binding = parseEnvFile3(cwdBindingPath);
60380
60730
  const bindingInstance = binding["IRANTI_INSTANCE"]?.trim() ?? "";
@@ -60384,9 +60734,9 @@ projectBindingsRouter.get(
60384
60734
  live.push({
60385
60735
  projectPath: process.cwd(),
60386
60736
  agentId: binding["IRANTI_AGENT_ID"]?.trim() || "main_agent",
60387
- memoryEntity: binding["IRANTI_MEMORY_ENTITY"]?.trim() || `project/${(0, import_path14.basename)(process.cwd())}`,
60737
+ memoryEntity: binding["IRANTI_MEMORY_ENTITY"]?.trim() || `project/${(0, import_path15.basename)(process.cwd())}`,
60388
60738
  mode: binding["IRANTI_PROJECT_MODE"]?.trim() === "shared" ? "shared" : "isolated",
60389
- boundAt: new Date((0, import_fs9.statSync)(cwdBindingPath).mtimeMs).toISOString()
60739
+ boundAt: new Date((0, import_fs10.statSync)(cwdBindingPath).mtimeMs).toISOString()
60390
60740
  });
60391
60741
  }
60392
60742
  } catch {
@@ -60407,24 +60757,24 @@ projectBindingsRouter.get(
60407
60757
 
60408
60758
  // src/server/routes/control-plane/claude-integration.ts
60409
60759
  var import_express26 = __toESM(require_express2(), 1);
60410
- var import_fs10 = require("fs");
60411
- var import_path15 = require("path");
60760
+ var import_fs11 = require("fs");
60761
+ var import_path16 = require("path");
60412
60762
  var import_os5 = require("os");
60413
60763
  var claudeIntegrationRouter = (0, import_express26.Router)();
60414
60764
  var INSTANCE_NAME_RE5 = /^[a-zA-Z0-9_-]{1,64}$/;
60415
60765
  function getRuntimeRoot2() {
60416
- return process.env.IRANTI_HOME ?? (0, import_path15.join)((0, import_os5.homedir)(), ".iranti-runtime");
60766
+ return process.env.IRANTI_HOME ?? (0, import_path16.join)((0, import_os5.homedir)(), ".iranti-runtime");
60417
60767
  }
60418
60768
  function getInstanceEnvPath2(runtimeRoot, instanceName) {
60419
- return (0, import_path15.join)(runtimeRoot, "instances", instanceName, ".env");
60769
+ return (0, import_path16.join)(runtimeRoot, "instances", instanceName, ".env");
60420
60770
  }
60421
60771
  function getRegistryPath2(runtimeRoot, instanceName) {
60422
- return (0, import_path15.join)(runtimeRoot, "instances", instanceName, "projects.json");
60772
+ return (0, import_path16.join)(runtimeRoot, "instances", instanceName, "projects.json");
60423
60773
  }
60424
60774
  function readRegistry3(registryPath) {
60425
- if (!(0, import_fs10.existsSync)(registryPath)) return { projects: [] };
60775
+ if (!(0, import_fs11.existsSync)(registryPath)) return { projects: [] };
60426
60776
  try {
60427
- const raw = (0, import_fs10.readFileSync)(registryPath, "utf8");
60777
+ const raw = (0, import_fs11.readFileSync)(registryPath, "utf8");
60428
60778
  const parsed = JSON.parse(raw);
60429
60779
  if (!Array.isArray(parsed.projects)) return { projects: [] };
60430
60780
  return parsed;
@@ -60433,9 +60783,9 @@ function readRegistry3(registryPath) {
60433
60783
  }
60434
60784
  }
60435
60785
  function readJsonFile2(filePath) {
60436
- if (!(0, import_fs10.existsSync)(filePath)) return null;
60786
+ if (!(0, import_fs11.existsSync)(filePath)) return null;
60437
60787
  try {
60438
- const raw = (0, import_fs10.readFileSync)(filePath, "utf8");
60788
+ const raw = (0, import_fs11.readFileSync)(filePath, "utf8");
60439
60789
  const parsed = JSON.parse(raw);
60440
60790
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
60441
60791
  return parsed;
@@ -60496,10 +60846,10 @@ function usesPortableIrantiCommand(command) {
60496
60846
  return /(^|\/)iranti(\.cmd|\.exe)?$/.test(normalized);
60497
60847
  }
60498
60848
  async function checkProjectIntegration2(projectPath) {
60499
- const mcpJsonPath = (0, import_path15.join)(projectPath, ".mcp.json");
60500
- const workspaceMcpJsonPath = (0, import_path15.join)(projectPath, ".vscode", "mcp.json");
60501
- const hooksJsonPath = (0, import_path15.join)(projectPath, ".claude", "settings.local.json");
60502
- const projectEnvPath = (0, import_path15.join)(projectPath, ".env.iranti");
60849
+ const mcpJsonPath = (0, import_path16.join)(projectPath, ".mcp.json");
60850
+ const workspaceMcpJsonPath = (0, import_path16.join)(projectPath, ".vscode", "mcp.json");
60851
+ const hooksJsonPath = (0, import_path16.join)(projectPath, ".claude", "settings.local.json");
60852
+ const projectEnvPath = (0, import_path16.join)(projectPath, ".env.iranti");
60503
60853
  const mcpJson = readJsonFile2(mcpJsonPath);
60504
60854
  const workspaceMcpJson = readJsonFile2(workspaceMcpJsonPath);
60505
60855
  const hooksJson = readJsonFile2(hooksJsonPath);
@@ -60507,9 +60857,9 @@ async function checkProjectIntegration2(projectPath) {
60507
60857
  const irantiWorkspaceMcpEntry = workspaceMcpJson ? extractIrantiMcpEntry(workspaceMcpJson) : null;
60508
60858
  const irantiHooks = hooksJson ? extractIrantiHooks(hooksJson) : { sessionStart: null, userPromptSubmit: null, stop: null };
60509
60859
  const issues = [];
60510
- const anyMcpPresent = (0, import_fs10.existsSync)(mcpJsonPath) || (0, import_fs10.existsSync)(workspaceMcpJsonPath);
60860
+ const anyMcpPresent = (0, import_fs11.existsSync)(mcpJsonPath) || (0, import_fs11.existsSync)(workspaceMcpJsonPath);
60511
60861
  const anyMcpHasIranti = irantiMcpEntry !== null || irantiWorkspaceMcpEntry !== null;
60512
- const mcpInitialize = anyMcpHasIranti && (0, import_fs10.existsSync)(projectEnvPath) ? await probeIrantiMcpInitialize({
60862
+ const mcpInitialize = anyMcpHasIranti && (0, import_fs11.existsSync)(projectEnvPath) ? await probeIrantiMcpInitialize({
60513
60863
  projectPath,
60514
60864
  projectEnvPath
60515
60865
  }) : null;
@@ -60525,7 +60875,7 @@ async function checkProjectIntegration2(projectPath) {
60525
60875
  }
60526
60876
  }
60527
60877
  }
60528
- if (!(0, import_fs10.existsSync)(hooksJsonPath)) {
60878
+ if (!(0, import_fs11.existsSync)(hooksJsonPath)) {
60529
60879
  issues.push(".claude/settings.local.json not found - hooks not configured");
60530
60880
  } else if (!irantiHooks.sessionStart && !irantiHooks.userPromptSubmit && !irantiHooks.stop) {
60531
60881
  issues.push("No Iranti hooks registered in settings.local.json");
@@ -60547,11 +60897,11 @@ async function checkProjectIntegration2(projectPath) {
60547
60897
  return {
60548
60898
  projectPath,
60549
60899
  mcpJson,
60550
- mcpJsonPath: (0, import_fs10.existsSync)(mcpJsonPath) ? mcpJsonPath : null,
60900
+ mcpJsonPath: (0, import_fs11.existsSync)(mcpJsonPath) ? mcpJsonPath : null,
60551
60901
  workspaceMcpJson,
60552
- workspaceMcpJsonPath: (0, import_fs10.existsSync)(workspaceMcpJsonPath) ? workspaceMcpJsonPath : null,
60902
+ workspaceMcpJsonPath: (0, import_fs11.existsSync)(workspaceMcpJsonPath) ? workspaceMcpJsonPath : null,
60553
60903
  hooksJson,
60554
- hooksJsonPath: (0, import_fs10.existsSync)(hooksJsonPath) ? hooksJsonPath : null,
60904
+ hooksJsonPath: (0, import_fs11.existsSync)(hooksJsonPath) ? hooksJsonPath : null,
60555
60905
  irantiMcpEntry,
60556
60906
  irantiWorkspaceMcpEntry,
60557
60907
  irantiHooks,
@@ -60569,10 +60919,10 @@ async function scaffoldProjectFiles(opts) {
60569
60919
  timeoutMs: 15e3
60570
60920
  });
60571
60921
  const written = [
60572
- (0, import_path15.join)(projectPath, ".mcp.json"),
60573
- (0, import_path15.join)(projectPath, ".vscode", "mcp.json"),
60574
- (0, import_path15.join)(projectPath, ".claude", "settings.local.json")
60575
- ].filter((filePath) => (0, import_fs10.existsSync)(filePath));
60922
+ (0, import_path16.join)(projectPath, ".mcp.json"),
60923
+ (0, import_path16.join)(projectPath, ".vscode", "mcp.json"),
60924
+ (0, import_path16.join)(projectPath, ".claude", "settings.local.json")
60925
+ ].filter((filePath) => (0, import_fs11.existsSync)(filePath));
60576
60926
  const output = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n");
60577
60927
  return { ok: true, written, output };
60578
60928
  } catch (err) {
@@ -60598,17 +60948,17 @@ claudeIntegrationRouter.get(
60598
60948
  res.status(400).json({ error: "Invalid projectId encoding" });
60599
60949
  return;
60600
60950
  }
60601
- if (!(0, import_path15.isAbsolute)(projectPath)) {
60951
+ if (!(0, import_path16.isAbsolute)(projectPath)) {
60602
60952
  res.status(400).json({ error: "projectPath must be an absolute path" });
60603
60953
  return;
60604
60954
  }
60605
- if (!(0, import_fs10.existsSync)(projectPath)) {
60955
+ if (!(0, import_fs11.existsSync)(projectPath)) {
60606
60956
  res.status(404).json({ error: `projectPath does not exist: ${projectPath}` });
60607
60957
  return;
60608
60958
  }
60609
60959
  const runtimeRoot = getRuntimeRoot2();
60610
60960
  const instanceEnvPath = getInstanceEnvPath2(runtimeRoot, instanceName);
60611
- if (!(0, import_fs10.existsSync)(instanceEnvPath)) {
60961
+ if (!(0, import_fs11.existsSync)(instanceEnvPath)) {
60612
60962
  res.status(404).json({ error: `Instance '${instanceName}' not found` });
60613
60963
  return;
60614
60964
  }
@@ -60631,23 +60981,23 @@ claudeIntegrationRouter.post(
60631
60981
  res.status(400).json({ error: "Invalid projectId encoding" });
60632
60982
  return;
60633
60983
  }
60634
- if (!(0, import_path15.isAbsolute)(projectPath)) {
60984
+ if (!(0, import_path16.isAbsolute)(projectPath)) {
60635
60985
  res.status(400).json({ error: "projectPath must be an absolute path" });
60636
60986
  return;
60637
60987
  }
60638
- if (!(0, import_fs10.existsSync)(projectPath)) {
60988
+ if (!(0, import_fs11.existsSync)(projectPath)) {
60639
60989
  res.status(404).json({ error: `projectPath does not exist: ${projectPath}` });
60640
60990
  return;
60641
60991
  }
60642
60992
  const runtimeRoot = getRuntimeRoot2();
60643
60993
  const instanceEnvPath = getInstanceEnvPath2(runtimeRoot, instanceName);
60644
- if (!(0, import_fs10.existsSync)(instanceEnvPath)) {
60994
+ if (!(0, import_fs11.existsSync)(instanceEnvPath)) {
60645
60995
  res.status(404).json({ error: `Instance '${instanceName}' not found` });
60646
60996
  return;
60647
60997
  }
60648
60998
  const { force = false } = req.body;
60649
- const projectEnvPath = (0, import_path15.join)(projectPath, ".env.iranti");
60650
- if (!(0, import_fs10.existsSync)(projectEnvPath)) {
60999
+ const projectEnvPath = (0, import_path16.join)(projectPath, ".env.iranti");
61000
+ if (!(0, import_fs11.existsSync)(projectEnvPath)) {
60651
61001
  res.status(400).json({
60652
61002
  error: `.env.iranti not found at ${projectEnvPath}. Bind the project first using the Project Bindings panel.`,
60653
61003
  code: "IRANTI_PROJECT_BINDING_MISSING"
@@ -60669,13 +61019,13 @@ claudeIntegrationRouter.get(
60669
61019
  }
60670
61020
  const runtimeRoot = getRuntimeRoot2();
60671
61021
  const instanceEnvPath = getInstanceEnvPath2(runtimeRoot, instanceName);
60672
- if (!(0, import_fs10.existsSync)(instanceEnvPath)) {
61022
+ if (!(0, import_fs11.existsSync)(instanceEnvPath)) {
60673
61023
  res.status(404).json({ error: `Instance '${instanceName}' not found` });
60674
61024
  return;
60675
61025
  }
60676
61026
  const registryPath = getRegistryPath2(runtimeRoot, instanceName);
60677
61027
  const registry2 = readRegistry3(registryPath);
60678
- const liveProjects = registry2.projects.filter((entry) => (0, import_fs10.existsSync)((0, import_path15.join)(entry.projectPath, ".env.iranti")));
61028
+ const liveProjects = registry2.projects.filter((entry) => (0, import_fs11.existsSync)((0, import_path16.join)(entry.projectPath, ".env.iranti")));
60679
61029
  const projects = await Promise.all(liveProjects.map(async (entry) => {
60680
61030
  const check2 = await checkProjectIntegration2(entry.projectPath);
60681
61031
  const irantiHooksCount = [check2.irantiHooks.sessionStart, check2.irantiHooks.userPromptSubmit, check2.irantiHooks.stop].filter(Boolean).length;
@@ -60700,13 +61050,13 @@ claudeIntegrationRouter.get(
60700
61050
  var import_express27 = __toESM(require_express2(), 1);
60701
61051
 
60702
61052
  // src/server/lib/codex-cli.ts
60703
- var import_child_process5 = require("child_process");
60704
- var import_promises8 = require("fs/promises");
60705
- var import_fs11 = require("fs");
60706
- var import_path16 = require("path");
61053
+ var import_child_process6 = require("child_process");
61054
+ var import_promises9 = require("fs/promises");
61055
+ var import_fs12 = require("fs");
61056
+ var import_path17 = require("path");
60707
61057
  var import_util8 = require("util");
60708
61058
  init_path_utils();
60709
- var execFileAsync3 = (0, import_util8.promisify)(import_child_process5.execFile);
61059
+ var execFileAsync3 = (0, import_util8.promisify)(import_child_process6.execFile);
60710
61060
  function candidateFromEnv2() {
60711
61061
  const raw = process.env["CODEX_CLI_PATH"]?.trim() ?? process.env["IRANTI_CP_CODEX_CLI"]?.trim() ?? "";
60712
61062
  return raw || null;
@@ -60714,7 +61064,7 @@ function candidateFromEnv2() {
60714
61064
  async function firstPathHit2() {
60715
61065
  const checker = process.platform === "win32" ? "where" : "which";
60716
61066
  return new Promise((resolveResult) => {
60717
- const child = (0, import_child_process5.spawn)(checker, ["codex"], { stdio: ["ignore", "pipe", "ignore"] });
61067
+ const child = (0, import_child_process6.spawn)(checker, ["codex"], { stdio: ["ignore", "pipe", "ignore"] });
60718
61068
  let stdout = "";
60719
61069
  let settled = false;
60720
61070
  const finish = (value) => {
@@ -60748,12 +61098,12 @@ async function firstPathHit2() {
60748
61098
  async function normalizeInvocation2(candidate, source) {
60749
61099
  const normalized = resolvePortable(candidate);
60750
61100
  const lower = normalized.toLowerCase();
60751
- const extension = (0, import_path16.extname)(lower);
61101
+ const extension = (0, import_path17.extname)(lower);
60752
61102
  if (process.platform === "win32" && !extension) {
60753
61103
  for (const suffix of [".exe", ".cmd", ".bat", ".ps1"]) {
60754
61104
  const sibling = `${normalized}${suffix}`;
60755
61105
  try {
60756
- await (0, import_promises8.access)(sibling, import_fs11.constants.F_OK);
61106
+ await (0, import_promises9.access)(sibling, import_fs12.constants.F_OK);
60757
61107
  return normalizeInvocation2(sibling, source);
60758
61108
  } catch {
60759
61109
  }
@@ -60771,7 +61121,7 @@ async function normalizeInvocation2(candidate, source) {
60771
61121
  const installDir = dirnamePortable(normalized);
60772
61122
  const cliEntry = joinPortable(installDir, "node_modules", "@openai", "codex", "bin", "codex.js");
60773
61123
  try {
60774
- await (0, import_promises8.access)(cliEntry, import_fs11.constants.F_OK);
61124
+ await (0, import_promises9.access)(cliEntry, import_fs12.constants.F_OK);
60775
61125
  return {
60776
61126
  command: process.execPath,
60777
61127
  args: [cliEntry],
@@ -60781,7 +61131,7 @@ async function normalizeInvocation2(candidate, source) {
60781
61131
  } catch {
60782
61132
  const exeSibling = normalized.replace(/\.(cmd|bat)$/i, ".exe");
60783
61133
  try {
60784
- await (0, import_promises8.access)(exeSibling, import_fs11.constants.F_OK);
61134
+ await (0, import_promises9.access)(exeSibling, import_fs12.constants.F_OK);
60785
61135
  return {
60786
61136
  command: exeSibling,
60787
61137
  args: [],
@@ -61148,20 +61498,20 @@ attendantDebugRouter.use((err, _req, res, _next) => {
61148
61498
  var import_express29 = __toESM(require_express2(), 1);
61149
61499
 
61150
61500
  // src/server/lib/local-operator-tools.ts
61151
- var import_child_process6 = require("child_process");
61152
- var import_promises9 = require("fs/promises");
61153
- var import_path17 = require("path");
61501
+ var import_child_process7 = require("child_process");
61502
+ var import_promises10 = require("fs/promises");
61503
+ var import_path18 = require("path");
61154
61504
  var RUNNABLE_NPM_SCRIPTS = /* @__PURE__ */ new Set(["migrate"]);
61155
61505
  var RUNNABLE_GLOBAL_NPM_PACKAGES = [/^iranti(?:@[\w.-]+)?$/i];
61156
61506
  function spawnAndCollect(command, args, options = {}) {
61157
61507
  return new Promise((resolve10, reject) => {
61158
61508
  const shouldUseCmdWrapper = process.platform === "win32" && /\.cmd$/i.test(command);
61159
- const child = shouldUseCmdWrapper ? (0, import_child_process6.spawn)(process.env.ComSpec ?? "cmd.exe", ["/d", "/s", "/c", command, ...args], {
61509
+ const child = shouldUseCmdWrapper ? (0, import_child_process7.spawn)(process.env.ComSpec ?? "cmd.exe", ["/d", "/s", "/c", command, ...args], {
61160
61510
  cwd: options.cwd,
61161
61511
  shell: false,
61162
61512
  windowsHide: true,
61163
61513
  env: process.env
61164
- }) : (0, import_child_process6.spawn)(command, args, {
61514
+ }) : (0, import_child_process7.spawn)(command, args, {
61165
61515
  cwd: options.cwd,
61166
61516
  shell: false,
61167
61517
  windowsHide: true,
@@ -61205,7 +61555,7 @@ function resolveWindowsCommand(executable) {
61205
61555
  }
61206
61556
  }
61207
61557
  function splitEnvPath(pathValue) {
61208
- return (pathValue ?? "").split(import_path17.delimiter).map((part) => part.trim()).filter(Boolean);
61558
+ return (pathValue ?? "").split(import_path18.delimiter).map((part) => part.trim()).filter(Boolean);
61209
61559
  }
61210
61560
  async function hasExecutableOnPath(executable) {
61211
61561
  const resolved = resolveWindowsCommand(executable);
@@ -61213,7 +61563,7 @@ async function hasExecutableOnPath(executable) {
61213
61563
  for (const dir of splitEnvPath(process.env["PATH"])) {
61214
61564
  for (const candidate of candidates) {
61215
61565
  try {
61216
- await (0, import_promises9.access)((0, import_path17.join)(dir, candidate), import_promises9.constants.F_OK);
61566
+ await (0, import_promises10.access)((0, import_path18.join)(dir, candidate), import_promises10.constants.F_OK);
61217
61567
  return true;
61218
61568
  } catch {
61219
61569
  }
@@ -61283,7 +61633,7 @@ async function validateCwd(cwd) {
61283
61633
  if (!cwd) return void 0;
61284
61634
  const pathToUse = cwd.trim();
61285
61635
  if (!pathToUse) return void 0;
61286
- const stats = await (0, import_promises9.stat)(pathToUse);
61636
+ const stats = await (0, import_promises10.stat)(pathToUse);
61287
61637
  if (!stats.isDirectory()) {
61288
61638
  throw new Error("cwd must be an existing directory.");
61289
61639
  }
@@ -61458,7 +61808,7 @@ localToolsRouter.post("/run-command", async (req, res) => {
61458
61808
  });
61459
61809
 
61460
61810
  // src/server/routes/control-plane/control-plane-self.ts
61461
- var import_child_process7 = require("child_process");
61811
+ var import_child_process8 = require("child_process");
61462
61812
  var import_express30 = __toESM(require_express2(), 1);
61463
61813
  var controlPlaneSelfRouter = (0, import_express30.Router)();
61464
61814
  var shutdownListeners = /* @__PURE__ */ new Set();
@@ -61491,7 +61841,7 @@ controlPlaneSelfRouter.get("/self/shutdown-stream", (req, res) => {
61491
61841
  });
61492
61842
  function scheduleSelfUninstall() {
61493
61843
  if (process.platform === "win32") {
61494
- const child2 = (0, import_child_process7.spawn)("cmd", ["/d", "/s", "/c", "ping 127.0.0.1 -n 3 >nul && npm.cmd uninstall -g iranti-control-plane"], {
61844
+ const child2 = (0, import_child_process8.spawn)("cmd", ["/d", "/s", "/c", "ping 127.0.0.1 -n 3 >nul && npm.cmd uninstall -g iranti-control-plane"], {
61495
61845
  detached: true,
61496
61846
  stdio: "ignore",
61497
61847
  windowsHide: true
@@ -61499,7 +61849,7 @@ function scheduleSelfUninstall() {
61499
61849
  child2.unref();
61500
61850
  return;
61501
61851
  }
61502
- const child = (0, import_child_process7.spawn)("sh", ["-lc", "sleep 2 && npm uninstall -g iranti-control-plane"], {
61852
+ const child = (0, import_child_process8.spawn)("sh", ["-lc", "sleep 2 && npm uninstall -g iranti-control-plane"], {
61503
61853
  detached: true,
61504
61854
  stdio: "ignore"
61505
61855
  });
@@ -61525,8 +61875,316 @@ controlPlaneSelfRouter.post("/self/uninstall", (_req, res) => {
61525
61875
  scheduleSelfStop();
61526
61876
  });
61527
61877
 
61878
+ // src/server/routes/control-plane/session-ledger.ts
61879
+ var import_express31 = __toESM(require_express2(), 1);
61880
+
61881
+ // src/server/lib/fleet-ledger-repo.ts
61882
+ init_db();
61883
+ function toIso4(value) {
61884
+ if (value === null || value === void 0) return null;
61885
+ if (value instanceof Date) return value.toISOString();
61886
+ if (typeof value === "string") return value;
61887
+ return String(value);
61888
+ }
61889
+ function mapRowToMirroredEvent(row) {
61890
+ return {
61891
+ id: row.id,
61892
+ instanceId: row.instance_id,
61893
+ remoteEventId: row.remote_event_id,
61894
+ timestamp: toIso4(row.timestamp),
61895
+ staffComponent: row.staff_component ?? "",
61896
+ actionType: row.action_type ?? "",
61897
+ agentId: row.agent_id ?? null,
61898
+ source: row.source ?? null,
61899
+ host: row.host ?? null,
61900
+ sessionId: row.session_id ?? null,
61901
+ entityType: row.entity_type ?? null,
61902
+ entityId: row.entity_id ?? null,
61903
+ key: row.key ?? null,
61904
+ reason: row.reason ?? null,
61905
+ level: row.level,
61906
+ metadata: row.metadata ?? null,
61907
+ ingestedAt: toIso4(row.ingested_at)
61908
+ };
61909
+ }
61910
+ function mapRowToWatermark(row) {
61911
+ const lastRemoteEventId = row.last_remote_event_id ?? null;
61912
+ return {
61913
+ instanceId: row.instance_id,
61914
+ lastEventTimestamp: toIso4(row.last_event_timestamp),
61915
+ lastEventId: lastRemoteEventId,
61916
+ lastRemoteEventId,
61917
+ lastPolledAt: toIso4(row.last_polled_at),
61918
+ lastPollSucceeded: row.last_poll_succeeded,
61919
+ lastPollError: row.last_poll_error ?? null,
61920
+ consecutiveFailures: row.consecutive_failures,
61921
+ totalEventsIngested: Number(row.total_events_ingested),
61922
+ createdAt: toIso4(row.created_at),
61923
+ updatedAt: toIso4(row.updated_at)
61924
+ };
61925
+ }
61926
+ async function upsertMirroredEvents(events) {
61927
+ if (events.length === 0) return 0;
61928
+ const COLS = 15;
61929
+ const params = [];
61930
+ const valueClauses = [];
61931
+ for (const ev of events) {
61932
+ const host = ev.host !== null && ev.host !== void 0 ? ev.host : typeof ev.metadata?.host === "string" ? ev.metadata.host : null;
61933
+ const base = params.length + 1;
61934
+ valueClauses.push(
61935
+ `($${base},$${base + 1},$${base + 2}::TIMESTAMPTZ,$${base + 3},$${base + 4},$${base + 5},$${base + 6},$${base + 7},$${base + 8},$${base + 9},$${base + 10},$${base + 11},$${base + 12},$${base + 13},$${base + 14}::jsonb)`
61936
+ );
61937
+ params.push(
61938
+ ev.instanceId,
61939
+ // $base
61940
+ ev.remoteEventId,
61941
+ // $base+1
61942
+ ev.timestamp,
61943
+ // $base+2
61944
+ ev.staffComponent,
61945
+ // $base+3
61946
+ ev.actionType,
61947
+ // $base+4
61948
+ ev.agentId,
61949
+ // $base+5
61950
+ ev.source,
61951
+ // $base+6
61952
+ host,
61953
+ // $base+7
61954
+ ev.sessionId,
61955
+ // $base+8
61956
+ ev.entityType,
61957
+ // $base+9
61958
+ ev.entityId,
61959
+ // $base+10
61960
+ ev.key,
61961
+ // $base+11
61962
+ ev.reason,
61963
+ // $base+12
61964
+ ev.level,
61965
+ // $base+13
61966
+ ev.metadata !== null && ev.metadata !== void 0 ? JSON.stringify(ev.metadata) : null
61967
+ // $base+14
61968
+ );
61969
+ void COLS;
61970
+ }
61971
+ const sql = `
61972
+ INSERT INTO mirrored_staff_events
61973
+ (instance_id, remote_event_id, timestamp, staff_component, action_type,
61974
+ agent_id, source, host, session_id, entity_type, entity_id, key, reason,
61975
+ level, metadata)
61976
+ VALUES ${valueClauses.join(", ")}
61977
+ ON CONFLICT (instance_id, remote_event_id) DO NOTHING
61978
+ `;
61979
+ const result = await query(sql, params);
61980
+ return result.rowCount ?? 0;
61981
+ }
61982
+ async function advanceWatermark(instanceId, lastTimestamp, lastEventId, eventsCount) {
61983
+ await query(
61984
+ `
61985
+ INSERT INTO instance_ledger_watermarks
61986
+ (instance_id, last_event_timestamp, last_remote_event_id,
61987
+ last_polled_at, last_poll_succeeded, last_poll_error,
61988
+ consecutive_failures, total_events_ingested,
61989
+ created_at, updated_at)
61990
+ VALUES
61991
+ ($1, $2::TIMESTAMPTZ, $3,
61992
+ now(), true, null,
61993
+ 0, $4,
61994
+ now(), now())
61995
+ ON CONFLICT (instance_id) DO UPDATE SET
61996
+ last_event_timestamp = EXCLUDED.last_event_timestamp,
61997
+ last_remote_event_id = EXCLUDED.last_remote_event_id,
61998
+ last_polled_at = now(),
61999
+ last_poll_succeeded = true,
62000
+ last_poll_error = null,
62001
+ consecutive_failures = 0,
62002
+ total_events_ingested = instance_ledger_watermarks.total_events_ingested + $4,
62003
+ updated_at = now()
62004
+ `,
62005
+ [instanceId, lastTimestamp, lastEventId, eventsCount]
62006
+ );
62007
+ }
62008
+ async function recordPollFailure(instanceId, error2) {
62009
+ await query(
62010
+ `
62011
+ INSERT INTO instance_ledger_watermarks
62012
+ (instance_id, last_polled_at, last_poll_succeeded, last_poll_error,
62013
+ consecutive_failures, total_events_ingested,
62014
+ created_at, updated_at)
62015
+ VALUES
62016
+ ($1, now(), false, $2,
62017
+ 1, 0,
62018
+ now(), now())
62019
+ ON CONFLICT (instance_id) DO UPDATE SET
62020
+ last_polled_at = now(),
62021
+ last_poll_succeeded = false,
62022
+ last_poll_error = $2,
62023
+ consecutive_failures = instance_ledger_watermarks.consecutive_failures + 1,
62024
+ updated_at = now()
62025
+ `,
62026
+ [instanceId, error2]
62027
+ );
62028
+ }
62029
+ async function getWatermark(instanceId) {
62030
+ const result = await query(
62031
+ "SELECT * FROM instance_ledger_watermarks WHERE instance_id = $1",
62032
+ [instanceId]
62033
+ );
62034
+ if (result.rows.length === 0) return null;
62035
+ return mapRowToWatermark(result.rows[0]);
62036
+ }
62037
+ async function getAllWatermarks() {
62038
+ const result = await query(
62039
+ "SELECT * FROM instance_ledger_watermarks ORDER BY instance_id"
62040
+ );
62041
+ return result.rows.map(mapRowToWatermark);
62042
+ }
62043
+ async function queryFleetLedger(filters) {
62044
+ const limit = Math.min(filters.limit ?? 100, 250);
62045
+ const params = [];
62046
+ const conditions = [];
62047
+ function addFilter(col, value, cast) {
62048
+ if (value === void 0) return;
62049
+ params.push(value);
62050
+ const placeholder = cast ? `$${params.length}::${cast}` : `$${params.length}`;
62051
+ conditions.push(`${col} = ${placeholder}`);
62052
+ }
62053
+ addFilter("instance_id", filters.instanceId);
62054
+ addFilter("source", filters.source);
62055
+ addFilter("host", filters.host);
62056
+ addFilter("agent_id", filters.agentId);
62057
+ addFilter("session_id", filters.sessionId);
62058
+ addFilter("action_type", filters.actionType);
62059
+ addFilter("level", filters.level);
62060
+ if (filters.since !== void 0) {
62061
+ params.push(filters.since);
62062
+ conditions.push(`timestamp >= $${params.length}::TIMESTAMPTZ`);
62063
+ }
62064
+ if (filters.until !== void 0) {
62065
+ params.push(filters.until);
62066
+ conditions.push(`timestamp <= $${params.length}::TIMESTAMPTZ`);
62067
+ }
62068
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
62069
+ const countSql = `
62070
+ SELECT COUNT(*) AS total
62071
+ FROM mirrored_staff_events
62072
+ ${whereClause}
62073
+ `;
62074
+ const countResult = await query(countSql, params);
62075
+ const total = parseInt(countResult.rows[0]?.total ?? "0", 10);
62076
+ params.push(limit);
62077
+ const dataSql = `
62078
+ SELECT id, instance_id, remote_event_id, timestamp, staff_component, action_type,
62079
+ agent_id, source, host, session_id, entity_type, entity_id, key, reason,
62080
+ level, metadata, ingested_at
62081
+ FROM mirrored_staff_events
62082
+ ${whereClause}
62083
+ ORDER BY timestamp DESC, remote_event_id DESC
62084
+ LIMIT $${params.length}
62085
+ `;
62086
+ const dataResult = await query(dataSql, params);
62087
+ const items = dataResult.rows.map(mapRowToMirroredEvent);
62088
+ return { items, total };
62089
+ }
62090
+
62091
+ // src/server/routes/control-plane/session-ledger.ts
62092
+ var sessionLedgerRouter = (0, import_express31.Router)();
62093
+ function parseLimit(value, defaultVal = 100, max = 250) {
62094
+ if (value === void 0 || value === null || String(value).trim() === "") return defaultVal;
62095
+ const n = Number.parseInt(String(value), 10);
62096
+ if (!Number.isFinite(n) || n < 1) throw new Error("limit must be a positive integer");
62097
+ return Math.min(n, max);
62098
+ }
62099
+ function deriveIngestionStatus(w) {
62100
+ if (!w.lastPolledAt) return "never_polled";
62101
+ const failures = w.consecutiveFailures ?? 0;
62102
+ if (failures === 0) return "healthy";
62103
+ if (failures < 3) return "degraded";
62104
+ return "failing";
62105
+ }
62106
+ sessionLedgerRouter.get("/", async (req, res) => {
62107
+ let limit;
62108
+ try {
62109
+ limit = parseLimit(req.query["limit"]);
62110
+ } catch (err) {
62111
+ res.status(400).json({ error: err instanceof Error ? err.message : String(err) });
62112
+ return;
62113
+ }
62114
+ try {
62115
+ const rawLevel = String(req.query["level"] ?? "").trim().toLowerCase();
62116
+ let level;
62117
+ if (rawLevel === "audit") level = "audit";
62118
+ else if (rawLevel === "debug") level = "debug";
62119
+ else if (rawLevel !== "") {
62120
+ res.status(400).json({ error: "level must be 'audit' or 'debug'" });
62121
+ return;
62122
+ }
62123
+ const filters = {
62124
+ limit,
62125
+ instanceId: typeof req.query["instanceId"] === "string" && req.query["instanceId"].trim() ? req.query["instanceId"].trim() : void 0,
62126
+ source: typeof req.query["source"] === "string" && req.query["source"].trim() ? req.query["source"].trim() : void 0,
62127
+ host: typeof req.query["host"] === "string" && req.query["host"].trim() ? req.query["host"].trim() : void 0,
62128
+ agentId: typeof req.query["agentId"] === "string" && req.query["agentId"].trim() ? req.query["agentId"].trim() : void 0,
62129
+ sessionId: typeof req.query["sessionId"] === "string" && req.query["sessionId"].trim() ? req.query["sessionId"].trim() : void 0,
62130
+ actionType: typeof req.query["actionType"] === "string" && req.query["actionType"].trim() ? req.query["actionType"].trim() : void 0,
62131
+ since: typeof req.query["since"] === "string" && req.query["since"].trim() ? req.query["since"].trim() : void 0,
62132
+ until: typeof req.query["until"] === "string" && req.query["until"].trim() ? req.query["until"].trim() : void 0,
62133
+ level
62134
+ };
62135
+ const { items, total } = await queryFleetLedger(filters);
62136
+ const body = {
62137
+ items,
62138
+ total,
62139
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
62140
+ };
62141
+ res.json(body);
62142
+ } catch (error2) {
62143
+ res.json({
62144
+ items: [],
62145
+ total: 0,
62146
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
62147
+ note: error2 instanceof Error ? error2.message : String(error2)
62148
+ });
62149
+ }
62150
+ });
62151
+ sessionLedgerRouter.get("/ingestion-health", async (_req, res) => {
62152
+ try {
62153
+ const watermarks = await getAllWatermarks();
62154
+ const instances = watermarks.map((w) => ({
62155
+ instanceId: w.instanceId,
62156
+ lastEventTimestamp: w.lastEventTimestamp ?? null,
62157
+ lastRemoteEventId: w.lastRemoteEventId ?? null,
62158
+ lastPolledAt: w.lastPolledAt ?? null,
62159
+ lastPollSucceeded: w.lastPollSucceeded ?? false,
62160
+ lastPollError: w.lastPollError ?? null,
62161
+ consecutiveFailures: w.consecutiveFailures ?? 0,
62162
+ totalEventsIngested: w.totalEventsIngested ?? 0,
62163
+ status: deriveIngestionStatus(w)
62164
+ }));
62165
+ const body = {
62166
+ instances,
62167
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
62168
+ };
62169
+ res.json(body);
62170
+ } catch (error2) {
62171
+ res.json({
62172
+ instances: [],
62173
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
62174
+ note: error2 instanceof Error ? error2.message : String(error2)
62175
+ });
62176
+ }
62177
+ });
62178
+ sessionLedgerRouter.use((err, _req, res, _next) => {
62179
+ const apiErr = err;
62180
+ res.status(apiErr.statusCode ?? 500).json({
62181
+ error: apiErr.message ?? "Internal server error",
62182
+ code: apiErr.code ?? "INTERNAL_ERROR"
62183
+ });
62184
+ });
62185
+
61528
62186
  // src/server/routes/control-plane/index.ts
61529
- var controlPlaneRouter = (0, import_express31.Router)();
62187
+ var controlPlaneRouter = (0, import_express32.Router)();
61530
62188
  controlPlaneRouter.use("/", archivistRouter);
61531
62189
  controlPlaneRouter.use("/", kbRouter);
61532
62190
  controlPlaneRouter.use("/", whoknowsRouter);
@@ -61547,6 +62205,7 @@ controlPlaneRouter.use("/diagnostics", diagnosticsRouter);
61547
62205
  controlPlaneRouter.use("/metrics", metricsRouter);
61548
62206
  controlPlaneRouter.use("/overview", overviewRouter);
61549
62207
  controlPlaneRouter.use("/sessions", sessionsRouter);
62208
+ controlPlaneRouter.use("/session-ledger", sessionLedgerRouter);
61550
62209
  controlPlaneRouter.use("/instances", upgradeRouter);
61551
62210
  controlPlaneRouter.use("/version-sync", versionSyncRouter);
61552
62211
  controlPlaneRouter.use("/install-state", installStateRouter);
@@ -61746,6 +62405,200 @@ function stopAdapter() {
61746
62405
  }
61747
62406
  }
61748
62407
 
62408
+ // src/server/lib/fleet-ledger-poller.ts
62409
+ var FLEET_LEDGER_POLL_INTERVAL_MS = typeof process.env["FLEET_LEDGER_POLL_INTERVAL_MS"] === "string" && process.env["FLEET_LEDGER_POLL_INTERVAL_MS"].trim() !== "" ? parseInt(process.env["FLEET_LEDGER_POLL_INTERVAL_MS"], 10) : 6e4;
62410
+ var POLL_BATCH_LIMIT = 250;
62411
+ var FETCH_TIMEOUT_MS = 1e4;
62412
+ async function discoverInstances() {
62413
+ const discovered = await discoverAndAggregate();
62414
+ const instances = [];
62415
+ const seenIds = /* @__PURE__ */ new Set();
62416
+ for (const candidate of discovered.instances) {
62417
+ const authority = await resolveInstanceAuthority(candidate.instanceId) ?? await resolveInstanceAuthority(candidate.name);
62418
+ if (!authority) continue;
62419
+ if (seenIds.has(authority.instanceId)) continue;
62420
+ seenIds.add(authority.instanceId);
62421
+ instances.push({
62422
+ instanceId: authority.instanceId,
62423
+ apiBaseUrl: authority.apiBaseUrl,
62424
+ apiKey: authority.apiKey
62425
+ });
62426
+ }
62427
+ return instances;
62428
+ }
62429
+ function isRecord3(val) {
62430
+ return typeof val === "object" && val !== null;
62431
+ }
62432
+ function toLedgerResponse(raw) {
62433
+ if (!isRecord3(raw)) return null;
62434
+ const items = raw["items"];
62435
+ if (!Array.isArray(items)) return null;
62436
+ return { items };
62437
+ }
62438
+ function itemTimestamp(item) {
62439
+ return item?.timestamp ?? null;
62440
+ }
62441
+ function itemEventId(item) {
62442
+ return item?.eventId ?? item?.event_id ?? item?.id ?? null;
62443
+ }
62444
+ function decrementIsoTimestamp(iso) {
62445
+ if (!iso) return null;
62446
+ const millis = Date.parse(iso);
62447
+ if (Number.isNaN(millis)) return null;
62448
+ return new Date(millis - 1).toISOString();
62449
+ }
62450
+ function buildLedgerPollUrl(apiBaseUrl, since, until) {
62451
+ const params = new URLSearchParams({
62452
+ limit: String(POLL_BATCH_LIMIT),
62453
+ level: "audit"
62454
+ });
62455
+ if (since) params.set("since", since);
62456
+ if (until) params.set("until", until);
62457
+ return `${apiBaseUrl}/memory/ledger?${params.toString()}`;
62458
+ }
62459
+ function mapItem(item, instanceId) {
62460
+ const remoteEventId = item.eventId ?? item.event_id ?? item.id ?? null;
62461
+ const timestamp = item.timestamp ?? null;
62462
+ if (!remoteEventId || !timestamp) return null;
62463
+ const rawLevel = (item.level ?? "audit").toLowerCase();
62464
+ const level = rawLevel === "debug" ? "debug" : "audit";
62465
+ const metadata = isRecord3(item.metadata) ? item.metadata : null;
62466
+ return {
62467
+ instanceId,
62468
+ remoteEventId,
62469
+ timestamp,
62470
+ staffComponent: item.staffComponent ?? item.staff_component ?? null,
62471
+ actionType: item.actionType ?? item.action_type ?? null,
62472
+ agentId: item.agentId ?? item.agent_id ?? null,
62473
+ source: item.source ?? null,
62474
+ host: typeof metadata?.["host"] === "string" ? metadata["host"] : null,
62475
+ sessionId: typeof metadata?.["sessionId"] === "string" ? metadata["sessionId"] : null,
62476
+ entityType: item.entityType ?? item.entity_type ?? null,
62477
+ entityId: item.entityId ?? item.entity_id ?? null,
62478
+ key: item.key ?? null,
62479
+ reason: item.reason ?? null,
62480
+ level,
62481
+ metadata
62482
+ };
62483
+ }
62484
+ async function pollInstance(instanceId, apiBaseUrl, apiKey) {
62485
+ const watermark = await getWatermark(instanceId);
62486
+ const lowerBoundSince = decrementIsoTimestamp(watermark?.lastEventTimestamp ?? null);
62487
+ let upperBoundUntil = null;
62488
+ let latestTimestampSeen = watermark?.lastEventTimestamp ?? null;
62489
+ let latestEventIdSeen = watermark?.lastEventId ?? null;
62490
+ let capturedLatestFromPoll = false;
62491
+ while (true) {
62492
+ const url2 = buildLedgerPollUrl(apiBaseUrl, lowerBoundSince, upperBoundUntil);
62493
+ const headers = {};
62494
+ if (apiKey) headers["X-Iranti-Key"] = apiKey;
62495
+ let response;
62496
+ try {
62497
+ const controller = new AbortController();
62498
+ const timeoutHandle = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
62499
+ try {
62500
+ response = await fetch(url2, { headers, signal: controller.signal });
62501
+ } finally {
62502
+ clearTimeout(timeoutHandle);
62503
+ }
62504
+ } catch (err) {
62505
+ const message = err instanceof Error ? err.message : String(err);
62506
+ await recordPollFailure(instanceId, `Network error: ${message}`);
62507
+ return;
62508
+ }
62509
+ if (!response.ok) {
62510
+ await recordPollFailure(
62511
+ instanceId,
62512
+ `HTTP ${response.status} ${response.statusText} from ${url2}`
62513
+ );
62514
+ return;
62515
+ }
62516
+ let raw;
62517
+ try {
62518
+ raw = await response.json();
62519
+ } catch (err) {
62520
+ const message = err instanceof Error ? err.message : String(err);
62521
+ await recordPollFailure(instanceId, `JSON parse error: ${message}`);
62522
+ return;
62523
+ }
62524
+ const ledger = toLedgerResponse(raw);
62525
+ if (!ledger) {
62526
+ await recordPollFailure(instanceId, "Unexpected ledger response shape (missing items array)");
62527
+ return;
62528
+ }
62529
+ const { items } = ledger;
62530
+ if (items.length === 0) {
62531
+ await advanceWatermark(instanceId, latestTimestampSeen, latestEventIdSeen, 0);
62532
+ return;
62533
+ }
62534
+ const events = [];
62535
+ for (const item of items) {
62536
+ const mapped = mapItem(item, instanceId);
62537
+ if (mapped) events.push(mapped);
62538
+ }
62539
+ const insertedCount = events.length > 0 ? await upsertMirroredEvents(events) : 0;
62540
+ if (!capturedLatestFromPoll) {
62541
+ latestTimestampSeen = itemTimestamp(items[0]);
62542
+ latestEventIdSeen = itemEventId(items[0]);
62543
+ capturedLatestFromPoll = true;
62544
+ }
62545
+ await advanceWatermark(instanceId, latestTimestampSeen, latestEventIdSeen, insertedCount);
62546
+ if (items.length < POLL_BATCH_LIMIT) break;
62547
+ const oldestTimestamp = itemTimestamp(items[items.length - 1]);
62548
+ const nextUpperBoundUntil = decrementIsoTimestamp(oldestTimestamp);
62549
+ if (!nextUpperBoundUntil) break;
62550
+ if (lowerBoundSince && Date.parse(nextUpperBoundUntil) <= Date.parse(lowerBoundSince)) {
62551
+ break;
62552
+ }
62553
+ upperBoundUntil = nextUpperBoundUntil;
62554
+ }
62555
+ }
62556
+ async function pollAllInstancesOnce() {
62557
+ let instances;
62558
+ try {
62559
+ instances = await discoverInstances();
62560
+ } catch (err) {
62561
+ console.warn("[fleet-poller] Failed to discover instances:", err);
62562
+ return;
62563
+ }
62564
+ if (instances.length === 0) return;
62565
+ for (const instance of instances) {
62566
+ try {
62567
+ await pollInstance(instance.instanceId, instance.apiBaseUrl, instance.apiKey);
62568
+ } catch (err) {
62569
+ console.warn(
62570
+ `[fleet-poller] Error polling instance ${instance.instanceId} (${instance.apiBaseUrl}):`,
62571
+ err
62572
+ );
62573
+ }
62574
+ }
62575
+ }
62576
+ var _pollInterval = null;
62577
+ function startFleetLedgerPoller() {
62578
+ if (_pollInterval !== null) {
62579
+ console.warn("[fleet-poller] Already running - startFleetLedgerPoller() called twice.");
62580
+ return;
62581
+ }
62582
+ console.log(
62583
+ `[fleet-poller] Starting. Poll interval: ${FLEET_LEDGER_POLL_INTERVAL_MS}ms.`
62584
+ );
62585
+ pollAllInstancesOnce().catch((err) => {
62586
+ console.warn("[fleet-poller] Initial poll error:", err);
62587
+ });
62588
+ _pollInterval = setInterval(() => {
62589
+ pollAllInstancesOnce().catch((err) => {
62590
+ console.warn("[fleet-poller] Poll error:", err);
62591
+ });
62592
+ }, FLEET_LEDGER_POLL_INTERVAL_MS);
62593
+ }
62594
+ function stopFleetLedgerPoller() {
62595
+ if (_pollInterval !== null) {
62596
+ clearInterval(_pollInterval);
62597
+ _pollInterval = null;
62598
+ console.log("[fleet-poller] Stopped.");
62599
+ }
62600
+ }
62601
+
61749
62602
  // src/server/index.ts
61750
62603
  init_db();
61751
62604
 
@@ -61766,18 +62619,18 @@ function buildPortSelectionPlan(params) {
61766
62619
 
61767
62620
  // src/server/index.ts
61768
62621
  var _isSea = typeof process.isSea === "function" && process.isSea();
61769
- var __dirname3 = _isSea ? (0, import_path19.dirname)(process.execPath) : (0, import_path19.dirname)((0, import_url3.fileURLToPath)(__importmeta_url));
61770
- var clientDistCandidates = process.env.IRANTI_CP_ASSETS_DIR ? [(0, import_path19.resolve)(process.env.IRANTI_CP_ASSETS_DIR)] : _isSea ? [(0, import_path19.resolve)((0, import_path19.dirname)(process.execPath), "public", "control-plane")] : [
61771
- (0, import_path19.resolve)(__dirname3, "../../../public/control-plane"),
61772
- (0, import_path19.resolve)(__dirname3, "../../public/control-plane"),
61773
- (0, import_path19.resolve)(process.cwd(), "../../public/control-plane"),
61774
- (0, import_path19.resolve)(process.cwd(), "../public/control-plane")
62622
+ var __dirname3 = _isSea ? (0, import_path20.dirname)(process.execPath) : (0, import_path20.dirname)((0, import_url3.fileURLToPath)(__importmeta_url));
62623
+ var clientDistCandidates = process.env.IRANTI_CP_ASSETS_DIR ? [(0, import_path20.resolve)(process.env.IRANTI_CP_ASSETS_DIR)] : _isSea ? [(0, import_path20.resolve)((0, import_path20.dirname)(process.execPath), "public", "control-plane")] : [
62624
+ (0, import_path20.resolve)(__dirname3, "../../../public/control-plane"),
62625
+ (0, import_path20.resolve)(__dirname3, "../../public/control-plane"),
62626
+ (0, import_path20.resolve)(process.cwd(), "../../public/control-plane"),
62627
+ (0, import_path20.resolve)(process.cwd(), "../public/control-plane")
61775
62628
  ];
61776
- var clientDist = clientDistCandidates.find((candidate) => (0, import_fs13.existsSync)((0, import_path19.resolve)(candidate, "index.html"))) ?? clientDistCandidates[0];
62629
+ var clientDist = clientDistCandidates.find((candidate) => (0, import_fs14.existsSync)((0, import_path20.resolve)(candidate, "index.html"))) ?? clientDistCandidates[0];
61777
62630
  var _version = "0.0.0";
61778
62631
  try {
61779
62632
  if (_isSea) {
61780
- const pkgPath = (0, import_path19.resolve)((0, import_path19.dirname)(process.execPath), "package.json");
62633
+ const pkgPath = (0, import_path20.resolve)((0, import_path20.dirname)(process.execPath), "package.json");
61781
62634
  const _require = (0, import_module.createRequire)((0, import_url3.pathToFileURL)(process.execPath).href);
61782
62635
  const pkg = _require(pkgPath);
61783
62636
  _version = pkg.version ?? "0.0.0";
@@ -61820,9 +62673,9 @@ async function findAvailablePort(start, end) {
61820
62673
  `[iranti-cp] No available port in range ${start}\u2013${end}. Free one of those ports and try again.`
61821
62674
  );
61822
62675
  }
61823
- var app = (0, import_express32.default)();
62676
+ var app = (0, import_express33.default)();
61824
62677
  app.use((0, import_cors.default)({ origin: `http://localhost:5173` }));
61825
- app.use(import_express32.default.json());
62678
+ app.use(import_express33.default.json());
61826
62679
  app.get("/api/control-plane/ping", (_req, res) => {
61827
62680
  res.json({
61828
62681
  ok: true,
@@ -61831,9 +62684,9 @@ app.get("/api/control-plane/ping", (_req, res) => {
61831
62684
  });
61832
62685
  });
61833
62686
  app.use("/api/control-plane", controlPlaneRouter);
61834
- app.use("/control-plane", import_express32.default.static(clientDist));
62687
+ app.use("/control-plane", import_express33.default.static(clientDist));
61835
62688
  app.get("/control-plane/*", (_req, res) => {
61836
- res.sendFile((0, import_path19.resolve)(clientDist, "index.html"));
62689
+ res.sendFile((0, import_path20.resolve)(clientDist, "index.html"));
61837
62690
  });
61838
62691
  app.get("/", (_req, res) => res.redirect("/control-plane"));
61839
62692
  app.use(
@@ -61867,6 +62720,7 @@ async function main() {
61867
62720
  startAdapter().catch((err) => {
61868
62721
  console.warn("[adapter] Failed to start:", err.message);
61869
62722
  });
62723
+ startFleetLedgerPoller();
61870
62724
  if (!process.env["IRANTI_CP_NO_OPEN"]) {
61871
62725
  Promise.resolve().then(() => __toESM(require_open(), 1)).then(({ default: open }) => {
61872
62726
  void open(`http://localhost:${PORT}`);
@@ -61877,6 +62731,7 @@ async function main() {
61877
62731
  function shutdown(signal) {
61878
62732
  console.log(`[iranti-cp] Received ${signal} \u2014 shutting down gracefully.`);
61879
62733
  stopAdapter();
62734
+ stopFleetLedgerPoller();
61880
62735
  server.close(() => {
61881
62736
  console.log("[iranti-cp] Server closed.");
61882
62737
  process.exit(0);