@automagik/omni 2.260508.1 → 2.260508.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.
@@ -15,8 +15,9 @@
15
15
  * 5. orphaned-data-dirs — `.pgserve-data/` directories under cwd
16
16
  * 6. version-match — CLI version vs. /api/v2/health `version` field
17
17
  * 7. pm2-status — omni-api and omni-nats both `online` in pm2
18
- * 8. pm2-max-restarts — omni-api has bounded restarts (not 0 or >= 1000)
18
+ * 8. pm2-max-restarts — omni-api + omni-nats have bounded restarts
19
19
  * 9. pm2-logrotate-installed — pm2-logrotate module configured correctly
20
+ * 10. port-canonical-owner — canonical ports owned by pm2-managed PIDs
20
21
  *
21
22
  * Each check returns OK / WARN / FAIL with a one-line detail. `--fix`
22
23
  * attempts repair for checks with a known repair path. The fix flow
@@ -35,6 +36,12 @@
35
36
  * so the hardened `--max-restarts` flag takes effect.
36
37
  * - pm2-logrotate-installed: Re-run `pm2 install pm2-logrotate` + the four
37
38
  * `pm2 set pm2-logrotate:*` commands.
39
+ * - port-canonical-owner: SIGTERM (then SIGKILL) the non-pm2 squatter
40
+ * holding 4222 / api port, then `pm2 restart`
41
+ * the managed entry. Refuses to act when the
42
+ * squatter PID matches another pm2-managed entry.
43
+ * - pm2-status: Reconcile port ownership (above) then `pm2
44
+ * restart` any non-online managed process.
38
45
  */
39
46
  import { Command } from 'commander';
40
47
  import { type Config, type ServerConfig } from '../config.js';
@@ -42,7 +49,7 @@ import { type EmbeddedDumpResult } from '../lib/canonical-pgserve.js';
42
49
  /** Severity levels reported by each check. */
43
50
  export type CheckLevel = 'OK' | 'WARN' | 'FAIL';
44
51
  /** Identifier used in tests and --json output. */
45
- export type CheckId = 'pm2-env-drift' | 'cli-key-valid' | 'pgserve-reachable' | 'omni-db-exists' | 'orphaned-data-dirs' | 'version-match' | 'pm2-status' | 'pm2-max-restarts' | 'pm2-logrotate-installed' | 'cli-signing-key-for-locked-instances' | 'pgserve-canonical';
52
+ export type CheckId = 'pm2-env-drift' | 'cli-key-valid' | 'pgserve-reachable' | 'omni-db-exists' | 'orphaned-data-dirs' | 'version-match' | 'pm2-status' | 'pm2-max-restarts' | 'pm2-logrotate-installed' | 'cli-signing-key-for-locked-instances' | 'pgserve-canonical' | 'port-canonical-owner';
46
53
  export interface CheckResult {
47
54
  id: CheckId;
48
55
  level: CheckLevel;
@@ -77,6 +84,8 @@ export interface DoctorOptions {
77
84
  interface Pm2Entry {
78
85
  name?: string;
79
86
  pm_id?: number;
87
+ /** Top-level OS PID of the spawned child. Used by port-canonical-owner. */
88
+ pid?: number;
80
89
  pm2_env?: {
81
90
  status?: string;
82
91
  env?: Record<string, string | undefined>;
@@ -174,6 +183,20 @@ export interface DoctorDeps {
174
183
  * to ~/.omni/config.json.
175
184
  */
176
185
  saveServerConfig: (partial: Partial<ServerConfig>) => void;
186
+ /**
187
+ * Resolve the OS PID currently bound to a TCP port (LISTEN). Returns
188
+ * null when nothing is listening, when the lookup fails, or when the
189
+ * platform tool (`ss`) is unavailable. Used by the port-canonical-owner
190
+ * check + fixer to detect non-pm2 squatters on canonical ports.
191
+ */
192
+ findPortOwner: (port: number) => Promise<number | null>;
193
+ /**
194
+ * Send a signal to a PID. Returns true when the signal was delivered,
195
+ * false on EPERM/ESRCH/etc. We never throw — callers treat falsy as
196
+ * "could not signal, fall through to next strategy". Tests stub this
197
+ * so they don't actually kill anything.
198
+ */
199
+ processKill: (pid: number, signal: 'SIGTERM' | 'SIGKILL') => boolean;
177
200
  }
178
201
  /**
179
202
  * Run all checks and optionally apply fixes. Returns a structured
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAMH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,KAAK,MAAM,EACX,KAAK,YAAY,EAKlB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,KAAK,kBAAkB,EAKxB,MAAM,6BAA6B,CAAC;AAarC,8CAA8C;AAC9C,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AAEhD,kDAAkD;AAClD,MAAM,MAAM,OAAO,GACf,eAAe,GACf,eAAe,GACf,mBAAmB,GACnB,gBAAgB,GAChB,oBAAoB,GACpB,eAAe,GACf,YAAY,GACZ,kBAAkB,GAClB,yBAAyB,GACzB,sCAAsC,GACtC,mBAAmB,GACnB,sBAAsB,CAAC;AAE3B,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACpD,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;GAGG;AACH,UAAU,QAAQ;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE;QACR,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;QACzC,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7B;AAoCD,uEAAuE;AACvE,MAAM,WAAW,UAAU;IACzB,mDAAmD;IACnD,eAAe,EAAE,MAAM,OAAO,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,CAAC;IAClD,+DAA+D;IAC/D,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,uEAAuE;IACvE,YAAY,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC,oEAAoE;IACpE,oBAAoB,EAAE,MAAM,MAAM,EAAE,CAAC;IACrC,qDAAqD;IACrD,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAChE,8DAA8D;IAC9D,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD,6CAA6C;IAC7C,SAAS,EAAE,MAAM;QAAE,YAAY,EAAE,YAAY,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACnE;;;OAGG;IACH,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1E,oEAAoE;IACpE,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,2DAA2D;IAC3D,eAAe,EAAE,MAAM,MAAM,CAAC;IAC9B,0DAA0D;IAC1D,cAAc,EAAE,MAAM,MAAM,CAAC;IAC7B,mEAAmE;IACnE,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,iFAAiF;IACjF,cAAc,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C;;;;;OAKG;IACH,mBAAmB,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IACnE;;;;;;OAMG;IACH,gBAAgB,EAAE,MAAM,OAAO,CAAC;IAChC;;;;OAIG;IACH,qBAAqB,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACpD;;;;;OAKG;IACH,cAAc,EAAE,CAAC,kBAAkB,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC5E;;;;OAIG;IACH,0BAA0B,EAAE,CAC1B,IAAI,EAAE,kBAAkB,EACxB,oBAAoB,EAAE,MAAM,KACzB,OAAO,CAAC;QAAE,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxE,6EAA6E;IAC7E,0BAA0B,EAAE,MAAM,MAAM,CAAC;IACzC;;;;OAIG;IACH,gBAAgB,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC;IAC3D;;;;;OAKG;IACH,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxD;;;;;OAKG;IACH,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,SAAS,KAAK,OAAO,CAAC;CACtE;AAi+BD;;;GAGG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,aAAa,EAAE,YAAY,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,CAiBxG;AA2BD,wBAAgB,mBAAmB,IAAI,OAAO,CAiD7C"}
package/dist/index.js CHANGED
@@ -123990,7 +123990,7 @@ import { fileURLToPath } from "url";
123990
123990
  // package.json
123991
123991
  var package_default = {
123992
123992
  name: "@automagik/omni",
123993
- version: "2.260508.1",
123993
+ version: "2.260508.3",
123994
123994
  description: "LLM-optimized CLI for Omni",
123995
123995
  type: "module",
123996
123996
  bin: {
@@ -129027,7 +129027,31 @@ function productionDeps() {
129027
129027
  dumpEmbeddedDb,
129028
129028
  restoreSnapshotToCanonical,
129029
129029
  getCanonicalPgserveDataDir,
129030
- saveServerConfig
129030
+ saveServerConfig,
129031
+ findPortOwner: async (port) => {
129032
+ try {
129033
+ const proc = Bun.spawn(["ss", "-tlnpH", `sport = :${port}`], {
129034
+ stdout: "pipe",
129035
+ stderr: "ignore"
129036
+ });
129037
+ const stdout = await new Response(proc.stdout).text();
129038
+ const exitCode = await proc.exited;
129039
+ if (exitCode !== 0)
129040
+ return null;
129041
+ const match = stdout.match(/pid=(\d+)/);
129042
+ return match ? Number.parseInt(match[1], 10) : null;
129043
+ } catch {
129044
+ return null;
129045
+ }
129046
+ },
129047
+ processKill: (pid, signal) => {
129048
+ try {
129049
+ process.kill(pid, signal);
129050
+ return true;
129051
+ } catch {
129052
+ return false;
129053
+ }
129054
+ }
129031
129055
  };
129032
129056
  }
129033
129057
  function scanForOrphans(dir, acc, depth, maxDepth = 4) {
@@ -129162,36 +129186,41 @@ async function checkPm2MaxRestarts(deps) {
129162
129186
  if (!processes) {
129163
129187
  return { id: "pm2-max-restarts", level: "WARN", detail: "pm2 not reachable" };
129164
129188
  }
129165
- const apiEntry = processes.find((p) => p.name === PM2_PROCESSES.api);
129166
- if (!apiEntry) {
129167
- return { id: "pm2-max-restarts", level: "WARN", detail: `${PM2_PROCESSES.api} not found in pm2` };
129168
- }
129169
- const maxRestarts = apiEntry.pm2_env?.max_restarts;
129170
- if (typeof maxRestarts !== "number") {
129171
- return {
129172
- id: "pm2-max-restarts",
129173
- level: "FAIL",
129174
- detail: `${PM2_PROCESSES.api} has no max_restarts set \u2014 crash loops are unbounded`
129175
- };
129176
- }
129177
- if (maxRestarts === 0 || maxRestarts >= 1000) {
129178
- return {
129179
- id: "pm2-max-restarts",
129180
- level: "FAIL",
129181
- detail: `${PM2_PROCESSES.api} max_restarts=${maxRestarts} \u2014 crash loops are effectively unbounded`
129182
- };
129189
+ const issues = [];
129190
+ let hasFail = false;
129191
+ const targets = [PM2_PROCESSES.api, PM2_PROCESSES.nats];
129192
+ for (const name of targets) {
129193
+ const entry = processes.find((p) => p.name === name);
129194
+ if (!entry) {
129195
+ issues.push(`${name} not found in pm2`);
129196
+ continue;
129197
+ }
129198
+ const v = entry.pm2_env?.max_restarts;
129199
+ if (typeof v !== "number") {
129200
+ hasFail = true;
129201
+ issues.push(`${name} has no max_restarts set \u2014 crash loops are unbounded`);
129202
+ continue;
129203
+ }
129204
+ if (v === 0 || v >= 1000) {
129205
+ hasFail = true;
129206
+ issues.push(`${name} max_restarts=${v} \u2014 crash loops are effectively unbounded`);
129207
+ continue;
129208
+ }
129209
+ if (!(v >= 5 && v <= 50)) {
129210
+ issues.push(`${name} max_restarts=${v} \u2014 expected 5..50`);
129211
+ }
129183
129212
  }
129184
- if (maxRestarts >= 5 && maxRestarts <= 50) {
129213
+ if (issues.length === 0) {
129185
129214
  return {
129186
129215
  id: "pm2-max-restarts",
129187
129216
  level: "OK",
129188
- detail: `${PM2_PROCESSES.api} max_restarts=${maxRestarts}`
129217
+ detail: `${PM2_PROCESSES.api} and ${PM2_PROCESSES.nats} max_restarts in hardened range`
129189
129218
  };
129190
129219
  }
129191
129220
  return {
129192
129221
  id: "pm2-max-restarts",
129193
- level: "WARN",
129194
- detail: `${PM2_PROCESSES.api} max_restarts=${maxRestarts} \u2014 expected 5..50`
129222
+ level: hasFail ? "FAIL" : "WARN",
129223
+ detail: issues.join("; ")
129195
129224
  };
129196
129225
  }
129197
129226
  async function checkPm2LogrotateInstalled(deps) {
@@ -129226,6 +129255,48 @@ async function checkPm2LogrotateInstalled(deps) {
129226
129255
  detail: "pm2-logrotate installed with expected settings"
129227
129256
  };
129228
129257
  }
129258
+ function canonicalPortTargets(serverConfig) {
129259
+ return [
129260
+ { name: PM2_PROCESSES.api, port: serverConfig.port },
129261
+ { name: PM2_PROCESSES.nats, port: 4222 }
129262
+ ];
129263
+ }
129264
+ async function checkPortCanonicalOwner(deps) {
129265
+ const processes = await deps.getPm2Processes();
129266
+ if (!processes) {
129267
+ return { id: "port-canonical-owner", level: "WARN", detail: "pm2 not reachable" };
129268
+ }
129269
+ const { serverConfig } = deps.loadState();
129270
+ const targets = canonicalPortTargets(serverConfig);
129271
+ const issues = [];
129272
+ for (const { name, port } of targets) {
129273
+ const pm2Entry = processes.find((p) => p.name === name);
129274
+ const pm2Pid = pm2Entry?.pid;
129275
+ const ownerPid = await deps.findPortOwner(port);
129276
+ if (ownerPid === null) {
129277
+ continue;
129278
+ }
129279
+ if (typeof pm2Pid !== "number" || pm2Pid <= 0) {
129280
+ issues.push(`${name}:${port} owned by pid=${ownerPid} but pm2 has no PID for ${name}`);
129281
+ continue;
129282
+ }
129283
+ if (ownerPid !== pm2Pid) {
129284
+ issues.push(`${name}:${port} owned by non-pm2 pid=${ownerPid} (pm2 child pid=${pm2Pid})`);
129285
+ }
129286
+ }
129287
+ if (issues.length === 0) {
129288
+ return {
129289
+ id: "port-canonical-owner",
129290
+ level: "OK",
129291
+ detail: "all canonical ports owned by pm2-managed processes"
129292
+ };
129293
+ }
129294
+ return {
129295
+ id: "port-canonical-owner",
129296
+ level: "FAIL",
129297
+ detail: issues.join("; ")
129298
+ };
129299
+ }
129229
129300
  async function fixPm2EnvDrift(deps) {
129230
129301
  const { serverConfig, cliConfig } = deps.loadState();
129231
129302
  const env2 = buildRuntimeEnv(serverConfig, cliConfig);
@@ -129335,6 +129406,83 @@ async function fixPgserveCanonical(deps) {
129335
129406
  const dataNote = dumpResult.status === "dumped" && restoreOutcome.status === "restored" ? `restored ${dumpResult.snapshotPath} into ${canonicalDir} (omni-api \u2192 ${url})` : dumpResult.status === "no-embedded-data" ? `no embedded data to migrate; canonical started empty at ${canonicalDir} (omni-api \u2192 ${url})` : `embedded data dir invalid; canonical started empty at ${canonicalDir} (omni-api \u2192 ${url})`;
129336
129407
  return `migrated to canonical pgserve@^2.1.0; ${dataNote}`;
129337
129408
  }
129409
+ async function waitForPortRelease(deps, pid, port) {
129410
+ for (let i = 0;i < 10; i++) {
129411
+ await deps.sleepMs(500);
129412
+ const stillOwner = await deps.findPortOwner(port);
129413
+ if (stillOwner !== pid)
129414
+ return true;
129415
+ }
129416
+ return false;
129417
+ }
129418
+ async function reclaimPortFromSquatter(deps, target, pm2Pid, managedPids) {
129419
+ const { name, port } = target;
129420
+ const ownerPid = await deps.findPortOwner(port);
129421
+ if (ownerPid === null)
129422
+ return null;
129423
+ if (typeof pm2Pid === "number" && ownerPid === pm2Pid)
129424
+ return null;
129425
+ if (managedPids.has(ownerPid)) {
129426
+ return `SKIPPED ${name}:${port} squatter pid=${ownerPid} is itself pm2-managed`;
129427
+ }
129428
+ if (!deps.processKill(ownerPid, "SIGTERM")) {
129429
+ return `FAILED ${name}:${port} could not signal pid=${ownerPid} (EPERM/ESRCH)`;
129430
+ }
129431
+ const released = await waitForPortRelease(deps, ownerPid, port);
129432
+ if (!released) {
129433
+ deps.processKill(ownerPid, "SIGKILL");
129434
+ await deps.sleepMs(500);
129435
+ }
129436
+ const code = await deps.runPm2(["restart", name]);
129437
+ if (code !== 0) {
129438
+ return `FAILED ${name}:${port} pm2 restart exited ${code} after killing pid=${ownerPid}`;
129439
+ }
129440
+ return `reclaimed ${name}:${port} from non-pm2 pid=${ownerPid}`;
129441
+ }
129442
+ async function fixPortCanonicalOwner(deps) {
129443
+ const processes = await deps.getPm2Processes();
129444
+ if (!processes) {
129445
+ throw new Error("pm2 not reachable \u2014 cannot reconcile port ownership");
129446
+ }
129447
+ const { serverConfig } = deps.loadState();
129448
+ const targets = canonicalPortTargets(serverConfig);
129449
+ const managedPids = new Set(processes.map((p) => p.pid).filter((p) => typeof p === "number" && p > 0));
129450
+ const repairs = [];
129451
+ for (const target of targets) {
129452
+ const pm2Entry = processes.find((p) => p.name === target.name);
129453
+ const outcome = await reclaimPortFromSquatter(deps, target, pm2Entry?.pid, managedPids);
129454
+ if (outcome !== null)
129455
+ repairs.push(outcome);
129456
+ }
129457
+ if (repairs.length === 0)
129458
+ return "no port-ownership conflicts to reconcile";
129459
+ return repairs.join("; ");
129460
+ }
129461
+ async function restartNonOnlineProcesses(deps) {
129462
+ const processes = await deps.getPm2Processes();
129463
+ if (!processes)
129464
+ return [];
129465
+ const restarted = [];
129466
+ for (const name of [PM2_PROCESSES.api, PM2_PROCESSES.nats]) {
129467
+ const entry = processes.find((p) => p.name === name);
129468
+ if (entry?.pm2_env?.status !== "online") {
129469
+ const code = await deps.runPm2(["restart", name]);
129470
+ if (code === 0)
129471
+ restarted.push(name);
129472
+ }
129473
+ }
129474
+ return restarted;
129475
+ }
129476
+ async function fixPm2Status(deps) {
129477
+ const portRepairs = await fixPortCanonicalOwner(deps);
129478
+ const restarted = await restartNonOnlineProcesses(deps);
129479
+ const parts = [];
129480
+ if (portRepairs !== "no port-ownership conflicts to reconcile")
129481
+ parts.push(portRepairs);
129482
+ if (restarted.length > 0)
129483
+ parts.push(`restarted ${restarted.join(", ")}`);
129484
+ return parts.length > 0 ? parts.join("; ") : "no pm2 status remediation needed";
129485
+ }
129338
129486
  function fixOrphanedDataDirs(deps) {
129339
129487
  const found = deps.findOrphanedDataDirs();
129340
129488
  if (found.length === 0) {
@@ -129400,7 +129548,8 @@ async function runAllChecks(deps) {
129400
129548
  await checkPm2MaxRestarts(deps),
129401
129549
  await checkPm2LogrotateInstalled(deps),
129402
129550
  await checkSigningKeyForLockedInstances(deps),
129403
- checkPgserveCanonical(deps)
129551
+ checkPgserveCanonical(deps),
129552
+ await checkPortCanonicalOwner(deps)
129404
129553
  ];
129405
129554
  }
129406
129555
  async function applyFix(deps, check) {
@@ -129417,6 +129566,10 @@ async function applyFix(deps, check) {
129417
129566
  return await fixPm2LogrotateInstalled(deps);
129418
129567
  if (check.id === "pgserve-canonical")
129419
129568
  return await fixPgserveCanonical(deps);
129569
+ if (check.id === "port-canonical-owner")
129570
+ return await fixPortCanonicalOwner(deps);
129571
+ if (check.id === "pm2-status")
129572
+ return await fixPm2Status(deps);
129420
129573
  return null;
129421
129574
  } catch (err) {
129422
129575
  const msg = err instanceof Error ? err.message : String(err);
@@ -129508,9 +129661,10 @@ Checks:
129508
129661
  orphaned-data-dirs .pgserve-data directories outside ~/.omni
129509
129662
  version-match CLI version vs. /api/v2/health version field
129510
129663
  pm2-status omni-api and omni-nats both online in pm2
129511
- pm2-max-restarts omni-api max_restarts is in the hardened range
129664
+ pm2-max-restarts omni-api + omni-nats max_restarts in hardened range
129512
129665
  pm2-logrotate-installed pm2-logrotate module installed with expected settings
129513
129666
  pgserve-canonical using canonical pgserve@^2.1.0 (shared backbone) vs. embedded
129667
+ port-canonical-owner canonical ports (NATS, API) owned by pm2-managed PIDs
129514
129668
 
129515
129669
  Safety:
129516
129670
  --fix NEVER touches ~/.omni/data/pgserve \u2014 it only operates on the pm2
@@ -230060,7 +230060,7 @@ var init_sentry_scrub = __esm(() => {
230060
230060
  var require_package8 = __commonJS((exports, module) => {
230061
230061
  module.exports = {
230062
230062
  name: "@omni/api",
230063
- version: "2.260508.1",
230063
+ version: "2.260508.3",
230064
230064
  type: "module",
230065
230065
  exports: {
230066
230066
  ".": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/omni",
3
- "version": "2.260508.1",
3
+ "version": "2.260508.3",
4
4
  "description": "LLM-optimized CLI for Omni",
5
5
  "type": "module",
6
6
  "bin": {