@indigoai-us/hq-cli 5.342.11 → 5.342.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/command-catalog.generated.d.ts +1 -1
  3. package/dist/command-catalog.generated.js +1 -1
  4. package/dist/commands/agent-kit.d.ts +2 -0
  5. package/dist/commands/agent-kit.js +23 -17
  6. package/dist/commands/agent-mcp.d.ts +2 -0
  7. package/dist/commands/agent-mcp.js +21 -15
  8. package/dist/commands/lanes-keep-alive-audit.js +1 -1
  9. package/dist/commands/lanes-message.d.ts +20 -2
  10. package/dist/commands/lanes-message.js +99 -38
  11. package/dist/commands/lanes-shared.d.ts +1 -0
  12. package/dist/commands/lanes.d.ts +4 -0
  13. package/dist/commands/lanes.js +24 -30
  14. package/dist/commands/mesh.js +2 -1
  15. package/dist/commands/monitor.d.ts +14 -1
  16. package/dist/commands/monitor.js +43 -14
  17. package/dist/lib/doctor/checks/anywhere.js +11 -6
  18. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  19. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  20. package/dist/lib/lanes/keep-alive-audit.d.ts +5 -1
  21. package/dist/lib/lanes/keep-alive-audit.js +132 -19
  22. package/dist/lib/lanes/reconcile.js +46 -3
  23. package/dist/lib/lanes/spawn.js +79 -0
  24. package/dist/lib/lanes/types.d.ts +2 -1
  25. package/dist/utils/package-use-lease.d.ts +15 -0
  26. package/dist/utils/package-use-lease.js +94 -1
  27. package/dist/utils/resident-package-preload.d.ts +8 -1
  28. package/dist/utils/resident-package-preload.js +68 -0
  29. package/dist/utils/self-update.d.ts +5 -0
  30. package/dist/utils/self-update.js +100 -12
  31. package/docs/anywhere-runtime.md +3 -1
  32. package/package.json +4 -4
  33. package/dist/lib/db/remote-engine.d.ts +0 -58
  34. package/dist/lib/db/remote-engine.js +0 -87
  35. package/dist/lib/db/remote-sql.d.ts +0 -22
  36. package/dist/lib/db/remote-sql.js +0 -36
@@ -1,4 +1,4 @@
1
- import { createHash } from "node:crypto";
1
+ import { createHash, randomBytes } from "node:crypto";
2
2
  import { mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync, } from "node:fs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
@@ -6,6 +6,8 @@ import { fileURLToPath } from "node:url";
6
6
  import { performance } from "node:perf_hooks";
7
7
  const CLI_NAME = "@indigoai-us/hq-cli";
8
8
  const UPDATE_REQUEST = "update.pending.json";
9
+ export const PACKAGE_USE_LEASE_HANDOFF_ENV = "HQ_CLI_PACKAGE_USE_LEASE_HANDOFF";
10
+ const HANDOFF_TIMEOUT_MS = 2_000;
9
11
  export class PackageUseUpdatePendingError extends Error {
10
12
  constructor(message) {
11
13
  super(message);
@@ -172,6 +174,93 @@ export function acquirePackageUseLease(prefix, hqVersion, stateDirectory) {
172
174
  }
173
175
  throw new PackageUseUpdatePendingError("An HQ CLI update is waiting for the package lease; retry when it finishes");
174
176
  }
177
+ /**
178
+ * Create a bounded, per-reexec rendezvous in the current lease directory.
179
+ * The child confirms readiness only after publishing its own atomic lease.
180
+ */
181
+ export function createPackageUseLeaseHandoff(lease, timeoutMs = HANDOFF_TIMEOUT_MS) {
182
+ const directory = path.dirname(lease.leasePath);
183
+ const markerPath = path.join(directory, `.reexec-${process.pid}-${randomBytes(12).toString("hex")}.handoff`);
184
+ let cleaned = false;
185
+ const cleanup = () => {
186
+ if (cleaned)
187
+ return;
188
+ cleaned = true;
189
+ process.removeListener("exit", cleanup);
190
+ try {
191
+ rmSync(markerPath, { force: true });
192
+ rmSync(`${markerPath}.tmp`, { force: true });
193
+ }
194
+ catch (error) {
195
+ process.stderr.write(`hq-cli package-use lease handoff cleanup failed (${errorLabel(error)})\n`);
196
+ }
197
+ };
198
+ process.once("exit", cleanup);
199
+ return {
200
+ envValue: markerPath,
201
+ async waitForChild(pid, childAlive) {
202
+ const deadline = Date.now() + timeoutMs;
203
+ while (childAlive() && Date.now() <= deadline) {
204
+ try {
205
+ const marker = JSON.parse(readFileSync(markerPath, "utf8"));
206
+ if (marker.pid === pid && Number.isSafeInteger(marker.start_time_ms) && typeof marker.lease_name === "string") {
207
+ const leasePath = path.join(directory, marker.lease_name);
208
+ if (path.basename(leasePath) === marker.lease_name) {
209
+ const record = JSON.parse(readFileSync(leasePath, "utf8"));
210
+ if (record.pid === pid && record.start_time_ms === marker.start_time_ms)
211
+ return true;
212
+ }
213
+ }
214
+ }
215
+ catch (error) {
216
+ if (error.code !== "ENOENT" && !(error instanceof SyntaxError)) {
217
+ process.stderr.write(`hq-cli package-use lease handoff check failed (${errorLabel(error)})\n`);
218
+ }
219
+ }
220
+ await new Promise((resolve) => setTimeout(resolve, 25));
221
+ }
222
+ return false;
223
+ },
224
+ cleanup,
225
+ };
226
+ }
227
+ export function createCurrentPackageUseLeaseHandoff(timeoutMs = HANDOFF_TIMEOUT_MS) {
228
+ return currentPackageUseLease ? createPackageUseLeaseHandoff(currentPackageUseLease, timeoutMs) : null;
229
+ }
230
+ /** Publish this process's lease identity to a parent waiting in the same prefix. */
231
+ export function publishPackageUseLeaseHandoff(lease, env = process.env) {
232
+ const markerPath = env[PACKAGE_USE_LEASE_HANDOFF_ENV];
233
+ if (!markerPath)
234
+ return false;
235
+ const directory = path.dirname(lease.leasePath);
236
+ if (path.dirname(path.resolve(markerPath)) !== path.resolve(directory)) {
237
+ delete env[PACKAGE_USE_LEASE_HANDOFF_ENV];
238
+ return false;
239
+ }
240
+ const match = /^(\d+)-(\d+)\.json$/.exec(path.basename(lease.leasePath));
241
+ if (!match || Number(match[1]) !== process.pid) {
242
+ delete env[PACKAGE_USE_LEASE_HANDOFF_ENV];
243
+ return false;
244
+ }
245
+ const record = {
246
+ pid: process.pid,
247
+ start_time_ms: Number(match[2]),
248
+ lease_name: path.basename(lease.leasePath),
249
+ };
250
+ const tempPath = `${markerPath}.tmp`;
251
+ try {
252
+ writeFileSync(tempPath, `${JSON.stringify(record)}\n`, { flag: "wx", mode: 0o600 });
253
+ renameSync(tempPath, markerPath);
254
+ delete env[PACKAGE_USE_LEASE_HANDOFF_ENV];
255
+ return true;
256
+ }
257
+ catch (error) {
258
+ rmSync(tempPath, { force: true });
259
+ delete env[PACKAGE_USE_LEASE_HANDOFF_ENV];
260
+ process.stderr.write(`hq-cli package-use lease handoff publish failed (${errorLabel(error)})\n`);
261
+ return false;
262
+ }
263
+ }
175
264
  function exists(file) {
176
265
  try {
177
266
  readFileSync(file);
@@ -198,6 +287,7 @@ export function acquireCurrentPackageUseLease() {
198
287
  }
199
288
  const lease = acquirePackageUseLease(prefix, pkg.version);
200
289
  currentPackageUseLease = lease;
290
+ publishPackageUseLeaseHandoff(lease);
201
291
  return lease;
202
292
  }
203
293
  /** Release this process's package-use lease after a long-running command has loaded its package modules. */
@@ -260,5 +350,8 @@ export function currentPackageUseUpdateRequested() {
260
350
  }
261
351
  export const __test__ = {
262
352
  waitForUpdateRequest,
353
+ setCurrentPackageUseLease(lease) {
354
+ currentPackageUseLease = lease;
355
+ },
263
356
  };
264
357
  //# sourceMappingURL=package-use-lease.js.map
@@ -1,4 +1,4 @@
1
- export type ResidentPackagePath = "auth-login" | "bot" | "childWrapper" | "coreJobRun" | "login" | "mcp" | "daemon";
1
+ export type ResidentPackagePath = "agent-kit" | "agent-mcp" | "auth-login" | "bot" | "childWrapper" | "coreJobRun" | "login" | "mcp" | "daemon" | "mesh-daemon" | "lanes-watch" | "monitor-wait" | "monitor-run";
2
2
  /** Package imports reachable lazily from each resident command's runtime graph. */
3
3
  export declare const RESIDENT_PACKAGE_MODULES: Record<ResidentPackagePath, readonly string[]>;
4
4
  export declare class ResidentPackagePreloadError extends Error {
@@ -10,4 +10,11 @@ export declare class ResidentPackagePreloadError extends Error {
10
10
  export declare function reportResidentPackagePreloadFailure(error: unknown): void;
11
11
  /** Resolve every lazy package module before a resident command releases its lease. */
12
12
  export declare function preloadResidentPackageModules(residentPath: ResidentPackagePath, load?: () => Promise<unknown>): Promise<void>;
13
+ export interface ResidentCommandLeaseOptions {
14
+ preload?: () => Promise<void>;
15
+ reportFailure?: (error: unknown) => void;
16
+ release?: () => void;
17
+ }
18
+ /** Start a resident loop only after its declared lazy package imports are loaded. */
19
+ export declare function runResidentCommandWithPackageLease<T>(residentPath: ResidentPackagePath, run: () => Promise<T>, options?: ResidentCommandLeaseOptions): Promise<T>;
13
20
  //# sourceMappingURL=resident-package-preload.d.ts.map
@@ -1,5 +1,8 @@
1
+ import { releaseCurrentPackageUseLease, releasePackageUseLeaseAfterResidentPreload, } from "./package-use-lease.js";
1
2
  /** Package imports reachable lazily from each resident command's runtime graph. */
2
3
  export const RESIDENT_PACKAGE_MODULES = {
4
+ "agent-kit": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open"],
5
+ "agent-mcp": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../commands/access.js", "open"],
3
6
  login: [
4
7
  "../lib/mesh/api.js",
5
8
  "../lib/work-context/config.js",
@@ -40,8 +43,31 @@ export const RESIDENT_PACKAGE_MODULES = {
40
43
  "../utils/staged-npm-install.js",
41
44
  "open",
42
45
  ],
46
+ "mesh-daemon": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open"],
47
+ "lanes-watch": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open"],
48
+ "monitor-wait": ["../sentry.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open"],
49
+ "monitor-run": ["../commands/lanes.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../sentry.js", "open"],
43
50
  };
44
51
  const LOADERS = {
52
+ "agent-kit": [
53
+ async () => {
54
+ const { preloadSentrySdk } = await import("../sentry.js");
55
+ preloadSentrySdk();
56
+ },
57
+ () => import("../lib/plan-limit-nag.js"),
58
+ () => import("../utils/plan-gate-error.js"),
59
+ () => import("open"),
60
+ ],
61
+ "agent-mcp": [
62
+ async () => {
63
+ const { preloadSentrySdk } = await import("../sentry.js");
64
+ preloadSentrySdk();
65
+ },
66
+ () => import("../lib/plan-limit-nag.js"),
67
+ () => import("../utils/plan-gate-error.js"),
68
+ () => import("../commands/access.js"),
69
+ () => import("open"),
70
+ ],
45
71
  login: [
46
72
  () => import("../lib/mesh/api.js"),
47
73
  () => import("../lib/work-context/config.js"),
@@ -101,6 +127,43 @@ const LOADERS = {
101
127
  () => import("../utils/staged-npm-install.js"),
102
128
  () => import("open"),
103
129
  ],
130
+ "mesh-daemon": [
131
+ async () => {
132
+ const { preloadSentrySdk } = await import("../sentry.js");
133
+ preloadSentrySdk();
134
+ },
135
+ () => import("../lib/plan-limit-nag.js"),
136
+ () => import("../utils/plan-gate-error.js"),
137
+ () => import("open"),
138
+ ],
139
+ "lanes-watch": [
140
+ async () => {
141
+ const { preloadSentrySdk } = await import("../sentry.js");
142
+ preloadSentrySdk();
143
+ },
144
+ () => import("../lib/plan-limit-nag.js"),
145
+ () => import("../utils/plan-gate-error.js"),
146
+ () => import("open"),
147
+ ],
148
+ "monitor-wait": [
149
+ async () => {
150
+ const { preloadSentrySdk } = await import("../sentry.js");
151
+ preloadSentrySdk();
152
+ },
153
+ () => import("../lib/plan-limit-nag.js"),
154
+ () => import("../utils/plan-gate-error.js"),
155
+ () => import("open"),
156
+ ],
157
+ "monitor-run": [
158
+ () => import("../commands/lanes.js"),
159
+ () => import("../lib/plan-limit-nag.js"),
160
+ () => import("../utils/plan-gate-error.js"),
161
+ async () => {
162
+ const { preloadSentrySdk } = await import("../sentry.js");
163
+ preloadSentrySdk();
164
+ },
165
+ () => import("open"),
166
+ ],
104
167
  };
105
168
  export class ResidentPackagePreloadError extends Error {
106
169
  moduleSpecifier;
@@ -141,4 +204,9 @@ export async function preloadResidentPackageModules(residentPath, load) {
141
204
  }
142
205
  }));
143
206
  }
207
+ /** Start a resident loop only after its declared lazy package imports are loaded. */
208
+ export async function runResidentCommandWithPackageLease(residentPath, run, options = {}) {
209
+ await releasePackageUseLeaseAfterResidentPreload(options.preload ?? (() => preloadResidentPackageModules(residentPath)), options.reportFailure ?? reportResidentPackagePreloadFailure, options.release ?? releaseCurrentPackageUseLease);
210
+ return run();
211
+ }
144
212
  //# sourceMappingURL=resident-package-preload.js.map
@@ -56,6 +56,7 @@
56
56
  * Opt-outs: `HQ_NO_UPDATE_CHECK=1` (the shared knob that also silences
57
57
  * version-check), `hq rescue --no-self-update`, and the re-exec guard env.
58
58
  */
59
+ import { spawn } from "node:child_process";
59
60
  import * as fs from "node:fs";
60
61
  import { type RunningInstall, type UpdateResult } from "./version-gate.js";
61
62
  import { type RestartDaemonResult } from "../lib/mesh/live/daemon/install.js";
@@ -145,6 +146,10 @@ export interface SelfUpdateDeps {
145
146
  renamePath?: typeof fs.renameSync;
146
147
  runner?: (cmd: string, args: string[], env?: NodeJS.ProcessEnv) => UpdateResult;
147
148
  reexec?: (argv: string[], env: NodeJS.ProcessEnv) => number | null;
149
+ /** Async process-spawn seam for lease handoff tests. */
150
+ spawnReexec?: typeof spawn;
151
+ /** stdin TTY probe for re-exec signal forwarding tests. */
152
+ stdinIsTTY?: () => boolean;
148
153
  acquireLock?: () => (() => void) | null;
149
154
  /**
150
155
  * Read-your-writes convergence check run after a "successful" install:
@@ -56,7 +56,7 @@
56
56
  * Opt-outs: `HQ_NO_UPDATE_CHECK=1` (the shared knob that also silences
57
57
  * version-check), `hq rescue --no-self-update`, and the re-exec guard env.
58
58
  */
59
- import { spawnSync } from "node:child_process";
59
+ import { spawn } from "node:child_process";
60
60
  import { randomBytes } from "node:crypto";
61
61
  import * as fs from "node:fs";
62
62
  import * as path from "node:path";
@@ -70,6 +70,7 @@ import { canStageInstall, stagedNpmInstall } from "./staged-install.js";
70
70
  import { acquireUpdateLock as acquireSharedUpdateLock } from "./update-lock.js";
71
71
  import { markLatestIneffective } from "./version-check.js";
72
72
  import { restartDaemonServiceIfInstalled } from "../lib/mesh/live/daemon/install.js";
73
+ import { createCurrentPackageUseLeaseHandoff, PACKAGE_USE_LEASE_HANDOFF_ENV, releaseCurrentPackageUseLease, } from "./package-use-lease.js";
73
74
  /**
74
75
  * Set on the re-exec'd child so it can never self-update (and re-exec) again.
75
76
  * One update + one re-exec per user invocation, ever.
@@ -596,18 +597,101 @@ export function acquireUpdateLock(now = Date.now()) {
596
597
  * command. Returns the child's exit status, or null when the child could not be
597
598
  * started at all (e.g. `hq` not on PATH in a minimal-PATH parent).
598
599
  */
599
- function reexecHq(argv, env) {
600
+ async function reexecHq(argv, env, spawnProcess = spawn, stdinIsTTY = () => process.stdin.isTTY === true) {
600
601
  const plan = buildSpawnPlan("hq", argv);
601
- const result = spawnSync(plan.cmd, plan.args, {
602
- stdio: "inherit",
603
- shell: plan.shell,
604
- env,
605
- });
606
- if (result.error)
602
+ const handoff = createCurrentPackageUseLeaseHandoff();
603
+ const childEnv = handoff
604
+ ? { ...env, [PACKAGE_USE_LEASE_HANDOFF_ENV]: handoff.envValue }
605
+ : env;
606
+ let childForSignals;
607
+ let childSpawned = false;
608
+ let childClosed = false;
609
+ let spawnedChild;
610
+ const pendingSignals = [];
611
+ const reexecSignals = process.platform === "win32"
612
+ ? ["SIGINT", "SIGTERM"]
613
+ : ["SIGINT", "SIGTERM", "SIGHUP"];
614
+ const forwardSignal = (signal) => {
615
+ if (signal === "SIGINT" && stdinIsTTY())
616
+ return;
617
+ if (childClosed)
618
+ return;
619
+ if (!childForSignals || !childSpawned) {
620
+ pendingSignals.push(signal);
621
+ return;
622
+ }
623
+ if (childForSignals.exitCode === null && childForSignals.signalCode === null) {
624
+ childForSignals.kill(signal);
625
+ }
626
+ };
627
+ const signalHandlers = new Map(reexecSignals.map((signal) => [signal, () => forwardSignal(signal)]));
628
+ for (const [signal, handler] of signalHandlers)
629
+ process.on(signal, handler);
630
+ const removeSignalHandlers = () => {
631
+ for (const [signal, handler] of signalHandlers)
632
+ process.removeListener(signal, handler);
633
+ };
634
+ // spawnSync left default parent signal handling in place, so a parent-only
635
+ // SIGTERM or SIGHUP could terminate it. Forward stop signals to the async
636
+ // child while keeping this process waiting for the child's exit status.
637
+ try {
638
+ spawnedChild = spawnProcess(plan.cmd, plan.args, {
639
+ stdio: "inherit",
640
+ shell: plan.shell,
641
+ env: childEnv,
642
+ });
643
+ childForSignals = spawnedChild;
644
+ }
645
+ catch (error) {
646
+ removeSignalHandlers();
647
+ handoff?.cleanup();
648
+ process.stderr.write(`hq-cli re-exec spawn failed (${reexecErrorClass(error)})\n`);
607
649
  return null;
608
- // A signal-killed child has status null; surface it as a failure exit rather
609
- // than pretending the command completed.
610
- return result.status ?? 1;
650
+ }
651
+ return new Promise((resolve) => {
652
+ let alive = true;
653
+ let spawnFailed = false;
654
+ spawnedChild.once("error", (error) => {
655
+ spawnFailed = true;
656
+ process.stderr.write(`hq-cli re-exec spawn failed (${reexecErrorClass(error)})\n`);
657
+ });
658
+ spawnedChild.once("spawn", () => {
659
+ childSpawned = true;
660
+ for (const signal of pendingSignals.splice(0)) {
661
+ if (spawnedChild.exitCode === null && spawnedChild.signalCode === null)
662
+ spawnedChild.kill(signal);
663
+ }
664
+ if (!handoff || spawnedChild.pid === undefined)
665
+ return;
666
+ void handoff.waitForChild(spawnedChild.pid, () => alive).then((matched) => {
667
+ if (matched && alive) {
668
+ releaseCurrentPackageUseLease();
669
+ handoff.cleanup();
670
+ }
671
+ }).catch((error) => {
672
+ process.stderr.write(`hq-cli package-use lease handoff wait failed (${error instanceof Error ? error.name : "unknown"})\n`);
673
+ });
674
+ });
675
+ spawnedChild.once("close", (code, _signal) => {
676
+ alive = false;
677
+ childClosed = true;
678
+ pendingSignals.length = 0;
679
+ removeSignalHandlers();
680
+ handoff?.cleanup();
681
+ // The former spawnSync path returned null on spawn failure and 1 for a signal.
682
+ resolve(spawnFailed ? null : code ?? 1);
683
+ });
684
+ });
685
+ }
686
+ function reexecErrorClass(error) {
687
+ if (error && typeof error === "object") {
688
+ const code = error.code;
689
+ if (typeof code === "string" && /^[A-Z0-9_]{1,32}$/.test(code))
690
+ return code;
691
+ if (error instanceof Error)
692
+ return error.name;
693
+ }
694
+ return "unknown";
611
695
  }
612
696
  async function updateAndReexec(argv, flavor, known, deps) {
613
697
  const env = deps.env ?? process.env;
@@ -829,7 +913,11 @@ async function updateAndReexec(argv, flavor, known, deps) {
829
913
  return { action: "updated", latest };
830
914
  }
831
915
  const childEnv = { ...env, [REEXEC_GUARD_ENV]: "1" };
832
- const status = (deps.reexec ?? reexecHq)([...argv.slice(2)], childEnv);
916
+ const status = deps.spawnReexec
917
+ ? await reexecHq([...argv.slice(2)], childEnv, deps.spawnReexec, deps.stdinIsTTY)
918
+ : deps.reexec
919
+ ? deps.reexec([...argv.slice(2)], childEnv)
920
+ : await reexecHq([...argv.slice(2)], childEnv, spawn, deps.stdinIsTTY);
833
921
  if (status === null) {
834
922
  console.error(chalk.yellow(`⚠ Updated hq-cli to ${latest}, but couldn't re-launch \`hq\` from PATH; ` +
835
923
  `continuing the ${flavor.noun} on ${current}.`));
@@ -73,7 +73,9 @@ the named-pipe transport without Unix mode or owner fields. Use `hq doctor
73
73
  --json` for the same fields as structured details. The hqd health operation is
74
74
  read-only and available while the runtime flag is off; it reports only the
75
75
  current directory's binding and never returns authentication tokens. A Unix
76
- socket that cannot be verified as user-only produces a warning. If an older
76
+ socket that cannot be verified as user-only produces a warning. When a Unix
77
+ socket is unreachable, doctor recommends installing the daemon service if it is
78
+ absent or restarting it if it is installed. If an older
77
79
  resident daemon does not support the health operation, doctor falls back to
78
80
  `ping` for reachability, version and uptime, then warns that a restart is needed
79
81
  for full diagnostics.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.342.11",
3
+ "version": "5.342.13",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -59,7 +59,7 @@
59
59
  "js-yaml": "^4.1.0",
60
60
  "mqtt": "5.15.2",
61
61
  "open": "^10.1.0",
62
- "semver": "^7.6.3",
62
+ "semver": "^7.8.5",
63
63
  "simple-git": "^3.27.0",
64
64
  "smol-toml": "1.6.1",
65
65
  "varlock": "1.21.1"
@@ -69,11 +69,11 @@
69
69
  "@types/better-sqlite3": "^7.6.13",
70
70
  "@types/js-yaml": "^4.0.9",
71
71
  "@types/node": "^22.0.0",
72
- "@types/semver": "^7.5.8",
72
+ "@types/semver": "^7.8.0",
73
73
  "@vitest/coverage-v8": "4.1.6",
74
74
  "aedes": "1.1.2",
75
75
  "aws-sdk-client-mock": "^4.1.0",
76
- "esbuild": "0.27.0",
76
+ "esbuild": "0.28.2",
77
77
  "eslint": "^10.5.0",
78
78
  "husky": "^9.1.7",
79
79
  "lint-staged": "^15.5.2",
@@ -1,58 +0,0 @@
1
- /**
2
- * Pluggable remote vault DB engine interface (vault-databases US-007).
3
- *
4
- * Default shipped adapter id: aurora-dsql.
5
- * This module is interface + registry only — no production AWS provision here.
6
- */
7
- /** Connection material written only to secrets stores — never CLI stdout. */
8
- export interface RemoteConnectionSecretPayload {
9
- /** Opaque secret body (e.g. JSON with host/user/password or IAM token recipe). */
10
- payload: Record<string, unknown>;
11
- /** Suggested vault/Secrets Manager key suffix (namespaced by company elsewhere). */
12
- secretNameHint: string;
13
- }
14
- export interface RemoteProvisionRequest {
15
- companyUid: string;
16
- companySlug: string;
17
- region?: string;
18
- /** Engine-specific options (cluster params, etc.). */
19
- options?: Record<string, unknown>;
20
- }
21
- export interface RemoteProvisionResult {
22
- engineId: string;
23
- resourceArn: string;
24
- region: string;
25
- status: "ready" | "provisioning" | "failed" | "existing";
26
- /** Secret payload to store — callers must not print. */
27
- connectionSecret: RemoteConnectionSecretPayload;
28
- }
29
- export interface RemoteHealth {
30
- engineId: string;
31
- resourceArn: string;
32
- healthy: boolean;
33
- status: string;
34
- checkedAt: string;
35
- }
36
- /**
37
- * Remote Postgres-class engine adapter.
38
- * Implementations live in control plane (hq-pro) for real AWS; CLI may mock.
39
- */
40
- export interface RemoteDbEngine {
41
- readonly id: string;
42
- provision(req: RemoteProvisionRequest): Promise<RemoteProvisionResult>;
43
- deprovision(companyUid: string, resourceArn: string): Promise<void>;
44
- connectionSecretPayload(companyUid: string, resourceArn: string): Promise<RemoteConnectionSecretPayload>;
45
- healthCheck(resourceArn: string): Promise<RemoteHealth>;
46
- }
47
- /** Default engine selection (PRD decision). */
48
- export declare const DEFAULT_REMOTE_ENGINE_ID = "aurora-dsql";
49
- export declare function registerRemoteEngine(engine: RemoteDbEngine): void;
50
- export declare function getRemoteEngine(id?: string): RemoteDbEngine;
51
- export declare function listRemoteEngineIds(): string[];
52
- /** Clear registry (tests only). */
53
- export declare function _resetRemoteEngineRegistryForTests(): void;
54
- /**
55
- * In-memory mock engine for unit tests — no AWS.
56
- */
57
- export declare function createMockRemoteEngine(id?: string): RemoteDbEngine;
58
- //# sourceMappingURL=remote-engine.d.ts.map
@@ -1,87 +0,0 @@
1
- /**
2
- * Pluggable remote vault DB engine interface (vault-databases US-007).
3
- *
4
- * Default shipped adapter id: aurora-dsql.
5
- * This module is interface + registry only — no production AWS provision here.
6
- */
7
- /** Default engine selection (PRD decision). */
8
- export const DEFAULT_REMOTE_ENGINE_ID = "aurora-dsql";
9
- const registry = new Map();
10
- export function registerRemoteEngine(engine) {
11
- registry.set(engine.id, engine);
12
- }
13
- export function getRemoteEngine(id = DEFAULT_REMOTE_ENGINE_ID) {
14
- const engine = registry.get(id);
15
- if (!engine) {
16
- throw new Error(`remote DB engine not registered: ${id} (available: ${[...registry.keys()].join(", ") || "none"})`);
17
- }
18
- return engine;
19
- }
20
- export function listRemoteEngineIds() {
21
- return [...registry.keys()];
22
- }
23
- /** Clear registry (tests only). */
24
- export function _resetRemoteEngineRegistryForTests() {
25
- registry.clear();
26
- }
27
- /**
28
- * In-memory mock engine for unit tests — no AWS.
29
- */
30
- export function createMockRemoteEngine(id = "mock") {
31
- const resources = new Map();
32
- return {
33
- id,
34
- async provision(req) {
35
- const existing = [...resources.entries()].find(([, v]) => v.companyUid === req.companyUid);
36
- if (existing) {
37
- const [arn, meta] = existing;
38
- return {
39
- engineId: id,
40
- resourceArn: arn,
41
- region: meta.region,
42
- status: "existing",
43
- connectionSecret: {
44
- payload: { mock: true, companyUid: req.companyUid },
45
- secretNameHint: `db/remote/${req.companySlug}`,
46
- },
47
- };
48
- }
49
- const arn = `arn:mock:db:${req.companyUid}`;
50
- const region = req.region ?? "us-east-1";
51
- resources.set(arn, { companyUid: req.companyUid, region });
52
- return {
53
- engineId: id,
54
- resourceArn: arn,
55
- region,
56
- status: "ready",
57
- connectionSecret: {
58
- payload: { mock: true, companyUid: req.companyUid },
59
- secretNameHint: `db/remote/${req.companySlug}`,
60
- },
61
- };
62
- },
63
- async deprovision(companyUid, resourceArn) {
64
- const meta = resources.get(resourceArn);
65
- if (meta && meta.companyUid === companyUid) {
66
- resources.delete(resourceArn);
67
- }
68
- },
69
- async connectionSecretPayload(companyUid, resourceArn) {
70
- return {
71
- payload: { mock: true, companyUid, resourceArn },
72
- secretNameHint: `db/remote/${companyUid}`,
73
- };
74
- },
75
- async healthCheck(resourceArn) {
76
- const ok = resources.has(resourceArn);
77
- return {
78
- engineId: id,
79
- resourceArn,
80
- healthy: ok,
81
- status: ok ? "ready" : "missing",
82
- checkedAt: new Date().toISOString(),
83
- };
84
- },
85
- };
86
- }
87
- //# sourceMappingURL=remote-engine.js.map
@@ -1,22 +0,0 @@
1
- /**
2
- * Remote SQL helper with injected credentials and executor (US-010).
3
- *
4
- * Connection string never appears in argv, logs, or stdout.
5
- * The CLI command does not currently wire this helper to a remote executor.
6
- */
7
- export interface RemoteSqlOptions {
8
- company: string;
9
- sql: string;
10
- /** Injected secret material — never log. */
11
- getConnectionConfig: () => Promise<Record<string, unknown>>;
12
- /** Execute against remote; tests inject mock. */
13
- execute?: (config: Record<string, unknown>, sql: string) => Promise<Record<string, unknown>[]>;
14
- }
15
- export interface RemoteSqlResult {
16
- rows: Record<string, unknown>[];
17
- }
18
- /**
19
- * Run SQL on remote tier using vault-bound credentials.
20
- */
21
- export declare function runRemoteSql(opts: RemoteSqlOptions): Promise<RemoteSqlResult>;
22
- //# sourceMappingURL=remote-sql.d.ts.map
@@ -1,36 +0,0 @@
1
- /**
2
- * Remote SQL helper with injected credentials and executor (US-010).
3
- *
4
- * Connection string never appears in argv, logs, or stdout.
5
- * The CLI command does not currently wire this helper to a remote executor.
6
- */
7
- function assertNoLeak(label, value) {
8
- const s = typeof value === "string" ? value : JSON.stringify(value);
9
- if (/postgres:\/\//i.test(s) || /postgresql:\/\//i.test(s)) {
10
- throw new Error(`${label}: connection string leak blocked`);
11
- }
12
- }
13
- /**
14
- * Run SQL on remote tier using vault-bound credentials.
15
- */
16
- export async function runRemoteSql(opts) {
17
- if (!opts.sql?.trim()) {
18
- throw new Error("SQL statement is required");
19
- }
20
- let config;
21
- try {
22
- config = await opts.getConnectionConfig();
23
- }
24
- catch (e) {
25
- const msg = e instanceof Error ? e.message : "no remote binding";
26
- throw new Error(`remote SQL unavailable: ${msg}`);
27
- }
28
- assertNoLeak("connection config", config);
29
- if (!opts.execute) {
30
- throw new Error("remote SQL executor not configured in this build (adapter pending live DSQL wire-up)");
31
- }
32
- const rows = await opts.execute(config, opts.sql);
33
- assertNoLeak("remote sql rows", rows);
34
- return { rows };
35
- }
36
- //# sourceMappingURL=remote-sql.js.map