@indigoai-us/hq-cloud 6.18.24 → 6.18.26

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 (38) hide show
  1. package/dist/bin/outpost-session-host.d.ts +1 -0
  2. package/dist/bin/outpost-session-host.d.ts.map +1 -1
  3. package/dist/bin/outpost-session-host.js +3 -2
  4. package/dist/bin/outpost-session-host.js.map +1 -1
  5. package/dist/bin/outpost-session-host.test.js +16 -1
  6. package/dist/bin/outpost-session-host.test.js.map +1 -1
  7. package/dist/bin/sync-runner-delete-snapshot-mixed.bench.test.js +129 -5
  8. package/dist/bin/sync-runner-delete-snapshot-mixed.bench.test.js.map +1 -1
  9. package/dist/bin/sync-runner.d.ts +6 -0
  10. package/dist/bin/sync-runner.d.ts.map +1 -1
  11. package/dist/bin/sync-runner.js +42 -3
  12. package/dist/bin/sync-runner.js.map +1 -1
  13. package/dist/bin/sync-runner.test.js +179 -23
  14. package/dist/bin/sync-runner.test.js.map +1 -1
  15. package/dist/hq-root-recovery.d.ts +0 -1
  16. package/dist/hq-root-recovery.d.ts.map +1 -1
  17. package/dist/hq-root-recovery.js +1 -1
  18. package/dist/hq-root-recovery.js.map +1 -1
  19. package/dist/journal-snapshot-memory.test.js +3 -3
  20. package/dist/outposts/client.d.ts.map +1 -1
  21. package/dist/outposts/client.js +4 -1
  22. package/dist/outposts/client.js.map +1 -1
  23. package/dist/outposts/client.test.js +53 -0
  24. package/dist/outposts/client.test.js.map +1 -1
  25. package/dist/outposts/transport.d.ts.map +1 -1
  26. package/dist/outposts/transport.js +18 -1
  27. package/dist/outposts/transport.js.map +1 -1
  28. package/dist/outposts/transport.test.js +89 -0
  29. package/dist/outposts/transport.test.js.map +1 -1
  30. package/dist/sync/push-walk-matchers.bench.test.js +92 -42
  31. package/dist/sync/push-walk-matchers.bench.test.js.map +1 -1
  32. package/dist/telemetry.d.ts +0 -14
  33. package/dist/telemetry.d.ts.map +1 -1
  34. package/dist/telemetry.js +2 -2
  35. package/dist/telemetry.js.map +1 -1
  36. package/dist/watch-parent.test.js +23 -0
  37. package/dist/watch-parent.test.js.map +1 -1
  38. package/package.json +2 -2
@@ -12,7 +12,7 @@ import * as os from "os";
12
12
  import * as path from "path";
13
13
  import * as v8 from "node:v8";
14
14
  import * as vm from "node:vm";
15
- import { monitorEventLoopDelay } from "node:perf_hooks";
15
+ import { monitorEventLoopDelay, PerformanceObserver } from "node:perf_hooks";
16
16
  import { runRunner, parseArgs, loopPassDeps, runRunnerWithLoop as runRunnerWithOwnerLease, resolveDeletePolicy, resolveSkipPersonal, resolveSkipCompanies, selectObjectIOFactory, routeChangeToTarget, buildTargetedPushArgv, resolvePullScope, readPinnedPrefixes, SESSIONS_SCOPE_PREFIX, defaultCollectTelemetry, AUTH_REQUIRED_PASS_EXIT, conventionalWatchSignalExitCode, DEFAULT_WATCH_RUNNER_SHUTDOWN_TIMEOUT_MS, resolveWatchRunnerShutdownTimeoutMs, WATCH_RUNNER_SHUTDOWN_TIMEOUT_ENV, } from "./sync-runner.js";
17
17
  import { SESSION_LOGS_PUSH_ONLY_PREFIX } from "../sync/pull-scope.js";
18
18
  import { WATCH_RUNNER_ALREADY_OWNED_EXIT, WATCH_OWNER_LEASE_LOST_EXIT, } from "../lib/exit-codes.js";
@@ -23,7 +23,7 @@ import { POST_SYNC_FLAG_KEYS, } from "./sync-runner-post-sync-flags.js";
23
23
  import { FakeClock, TreeWatcher, } from "../watcher.js";
24
24
  import { adaptivePollMs, advanceMonotonicDeadline, DEFAULT_EVENT_BATCH_LIMIT, DEFAULT_FAST_LANE_MAX_BYTES, DEFAULT_FAST_LANE_MAX_FILE_BYTES, DEFAULT_FAST_LANE_MAX_FILES, DEFAULT_FULL_RECONCILE_MS, EVENT_BATCH_LIMIT_ENV, FAST_LANE_MAX_BYTES_ENV, FAST_LANE_MAX_FILES_ENV, FULL_RECONCILE_MS_ENV, isFastLocalBatch, isFastReceiverBatch, partitionLocalBatch, journalEntriesForWatcherDelete, resolveEventBatchLimit, resolveFullReconcileMs, resolveWatchPollMs, CHORE_STARVATION_CAP_MS, CooperativePassScheduler, REALTIME_ADMISSION_DEADLINE_MS, REALTIME_DRAIN_BURST_MS, REALTIME_WORKER_OCCUPANCY_CAP, RealtimeAdmissionTimeoutError, setRealtimeAdmissionDeadlineMsForTesting, setRealtimeDrainBurstMsForTesting, normalizeCoordinatorPaths, shouldQuarantineWatcherDeleteIntents, shouldUseAreaStorage, WATCHER_REDISCOVERY_RETRY_MS, WATCHER_REDISCOVERY_RETRY_MAX_MS, EVENT_SYNC_STARTUP_SHUTDOWN_WAIT_MS, } from "./sync-runner-watch-loop.js";
25
25
  import { FullPassCapController, resetFullPassCapLogForTest, } from "../sync/full-pass-caps.js";
26
- import { AreaLedgerMigration, JournalStore, openJournalStateStoreForMigration, PERSONAL_VAULT_JOURNAL_SLUG, readJournal, readJournalScoped, writeJournal, } from "../journal.js";
26
+ import { AreaLedgerMigration, JournalStore, openJournalStateStoreForMigration, PERSONAL_VAULT_JOURNAL_SLUG, readJournal, readJournalForPass, readJournalScoped, readJournalScopedAsync, writeJournal, } from "../journal.js";
27
27
  import { AreaLedger } from "../sync/area-ledger.js";
28
28
  import { AreaResolver } from "../sync/area-resolver.js";
29
29
  import { AREA_LEGACY_MIRROR_LINGER_MS } from "../sync/area-ledger-migration.js";
@@ -285,6 +285,29 @@ describe("argv parsing", () => {
285
285
  exitWithParent: false,
286
286
  });
287
287
  });
288
+ it("validates --watch-parent-pid requirements and values", () => {
289
+ expect(parseArgs(["--companies", "--watch-parent-pid", "42"])).toEqual({
290
+ error: "--watch-parent-pid requires --watch",
291
+ });
292
+ expect(parseArgs(["--companies", "--watch", "--watch-parent-pid", "42"])).toEqual({
293
+ error: "--watch-parent-pid requires --exit-with-parent",
294
+ });
295
+ expect(parseArgs(["--companies", "--watch", "--exit-with-parent", "--watch-parent-pid"])).toEqual({
296
+ error: "--watch-parent-pid requires a positive integer",
297
+ });
298
+ for (const value of ["0", "-1", "1.5", "nope"]) {
299
+ expect(parseArgs(["--companies", "--watch", "--exit-with-parent", "--watch-parent-pid", value])).toEqual({
300
+ error: "--watch-parent-pid requires a positive integer",
301
+ });
302
+ }
303
+ expect(parseArgs(["--companies", "--watch", "--exit-with-parent", "--watch-parent-pid", "42"])).toMatchObject({ watch: true, exitWithParent: true, watchParentPid: 42 });
304
+ expect(parseArgs(["--companies", "--watch", "--exit-with-parent", "--watch-parent-pid", "1"])).toEqual({
305
+ error: "--watch-parent-pid cannot be PID 1 or the runner process",
306
+ });
307
+ expect(parseArgs(["--companies", "--watch", "--exit-with-parent", "--watch-parent-pid", String(process.pid)])).toEqual({
308
+ error: "--watch-parent-pid cannot be PID 1 or the runner process",
309
+ });
310
+ });
288
311
  it("rejects invalid --on-conflict value", async () => {
289
312
  const deps = makeDeps();
290
313
  const code = await runRunner(["--companies", "--on-conflict", "nuke"], deps);
@@ -5052,9 +5075,67 @@ describe("runRunnerWithLoop — event-push wiring", () => {
5052
5075
  });
5053
5076
  expect(await loop).toBe(0);
5054
5077
  expect(startParentExitMonitor).toHaveBeenCalledOnce();
5078
+ expect(startParentExitMonitor.mock.calls[0]?.slice(1)).toEqual([
5079
+ process.ppid,
5080
+ process.ppid,
5081
+ ]);
5055
5082
  expect(release).toHaveBeenCalledOnce();
5056
5083
  expect(stopMonitor).toHaveBeenCalledOnce();
5057
5084
  });
5085
+ it("passes the explicit launcher PID to the exit monitor", async () => {
5086
+ const release = vi.fn(async () => { });
5087
+ const startParentExitMonitor = vi.fn((onParentExit) => {
5088
+ queueMicrotask(onParentExit);
5089
+ return { stop: vi.fn() };
5090
+ });
5091
+ const code = await runRunnerWithLoop(["--companies", "--watch", "--exit-with-parent", "--watch-parent-pid", "9001"], {
5092
+ parentExists: () => true,
5093
+ startParentExitMonitor,
5094
+ runPass: async () => 0,
5095
+ sleep: () => new Promise(() => { }),
5096
+ onShutdownSignal: () => () => { },
5097
+ acquireWatchOwnerLease: () => ({
5098
+ status: "acquired",
5099
+ lease: {
5100
+ statusPath: "test://watch-owner",
5101
+ lost: new Promise(() => { }),
5102
+ release,
5103
+ },
5104
+ }),
5105
+ });
5106
+ expect(code).toBe(0);
5107
+ expect(startParentExitMonitor.mock.calls[0]?.slice(1)).toEqual([9001, process.ppid]);
5108
+ expect(release).toHaveBeenCalledOnce();
5109
+ });
5110
+ it("shuts down before the first pass when the explicit launcher is already gone", async () => {
5111
+ const release = vi.fn(async () => { });
5112
+ const runPass = vi.fn(async () => 0);
5113
+ const stderr = makeWriter();
5114
+ const parentExists = vi.fn(() => false);
5115
+ const startParentExitMonitor = vi.fn(() => ({ stop: vi.fn() }));
5116
+ const code = await runRunnerWithLoop(["--companies", "--watch", "--exit-with-parent", "--watch-parent-pid", "9001"], {
5117
+ stderr,
5118
+ parentExists,
5119
+ startParentExitMonitor,
5120
+ runPass,
5121
+ sleep: () => new Promise(() => { }),
5122
+ onShutdownSignal: () => () => { },
5123
+ acquireWatchOwnerLease: () => ({
5124
+ status: "acquired",
5125
+ lease: {
5126
+ statusPath: "test://watch-owner",
5127
+ lost: new Promise(() => { }),
5128
+ release,
5129
+ },
5130
+ }),
5131
+ });
5132
+ expect(code).toBe(0);
5133
+ expect(parentExists).toHaveBeenCalledWith(9001);
5134
+ expect(stderr.raw()).toContain("--watch-parent-pid 9001 is not running");
5135
+ expect(runPass).not.toHaveBeenCalled();
5136
+ expect(startParentExitMonitor).not.toHaveBeenCalled();
5137
+ expect(release).toHaveBeenCalledOnce();
5138
+ });
5058
5139
  it("bounds parent-exit shutdown when an active pass ignores cancellation", async () => {
5059
5140
  const previousTimeout = process.env[WATCH_RUNNER_SHUTDOWN_TIMEOUT_ENV];
5060
5141
  process.env[WATCH_RUNNER_SHUTDOWN_TIMEOUT_ENV] = "50";
@@ -7656,22 +7737,43 @@ describe("runRunnerWithLoop — event-push wiring", () => {
7656
7737
  const watcher = makeBatchWatcherStub();
7657
7738
  let shutdown = () => { };
7658
7739
  let committedAt;
7740
+ let activeCommitSample;
7741
+ let scopedCommitSequence = 0;
7742
+ const commitPhases = [];
7659
7743
  const runPass = vi.fn(async (argv) => {
7660
7744
  if (!argv.includes("--scope-path")) {
7661
7745
  // Production opens the packed journal during the initial full pass,
7662
7746
  // before the watcher accepts realtime work. That recovered projection
7663
7747
  // is the one a scoped commit must reuse.
7664
- readJournal(PERSONAL_VAULT_JOURNAL_SLUG);
7748
+ readJournalForPass(PERSONAL_VAULT_JOURNAL_SLUG);
7665
7749
  }
7666
7750
  if (argv.includes("--scope-path") && argv.includes("push")) {
7667
- const scoped = readJournalScoped(PERSONAL_VAULT_JOURNAL_SLUG, targetPaths);
7751
+ const passStartedAt = performance.now();
7752
+ const scopedReadStartedAt = performance.now();
7753
+ const scoped = await readJournalScopedAsync(PERSONAL_VAULT_JOURNAL_SLUG, targetPaths);
7754
+ const scopedReadMs = performance.now() - scopedReadStartedAt;
7755
+ const mutationStartedAt = performance.now();
7668
7756
  const target = targetPaths[0];
7669
7757
  scoped.files[target] = {
7670
7758
  ...scoped.files[target],
7671
- syncedAt: "2026-09-16T21:38:18.000Z",
7759
+ syncedAt: new Date(Date.parse("2026-09-16T21:38:18.000Z") + scopedCommitSequence++).toISOString(),
7672
7760
  };
7761
+ const entryMutationMs = performance.now() - mutationStartedAt;
7762
+ const journalWriteStartedAt = performance.now();
7673
7763
  writeJournal(PERSONAL_VAULT_JOURNAL_SLUG, scoped);
7764
+ const journalWriteMs = performance.now() - journalWriteStartedAt;
7674
7765
  committedAt = performance.now();
7766
+ if (activeCommitSample) {
7767
+ commitPhases.push({
7768
+ sample: activeCommitSample.sample,
7769
+ dispatchToPassMs: passStartedAt - activeCommitSample.submittedAt,
7770
+ scopedReadMs,
7771
+ entryMutationMs,
7772
+ journalWriteMs,
7773
+ passRemainderMs: committedAt - passStartedAt - scopedReadMs - entryMutationMs - journalWriteMs,
7774
+ submitToCommitMs: committedAt - activeCommitSample.submittedAt,
7775
+ });
7776
+ }
7675
7777
  }
7676
7778
  return completedPassOutcome();
7677
7779
  });
@@ -7684,21 +7786,19 @@ describe("runRunnerWithLoop — event-push wiring", () => {
7684
7786
  return () => { };
7685
7787
  },
7686
7788
  });
7789
+ const gcActivity = [];
7790
+ const gcObserver = new PerformanceObserver((list) => {
7791
+ for (const entry of list.getEntries()) {
7792
+ const detail = entry;
7793
+ gcActivity.push({ kind: detail.detail?.kind ?? null, duration: entry.duration, startTime: entry.startTime });
7794
+ }
7795
+ });
7796
+ gcObserver.observe({ entryTypes: ["gc"] });
7687
7797
  try {
7688
7798
  await flushLoopMicrotasks(30);
7689
7799
  runPass.mockClear();
7690
7800
  resetStateStoreSnapshotRecoveryCountForTest();
7691
- const eventLoopDelay = monitorEventLoopDelay({ resolution: 10 });
7692
- eventLoopDelay.enable();
7693
7801
  await new Promise((resolve) => setTimeout(resolve, 0));
7694
- const submittedAt = performance.now();
7695
- let timerDelayMs;
7696
- const eventLoopTurn = new Promise((resolve) => {
7697
- setTimeout(() => {
7698
- timerDelayMs = performance.now() - submittedAt;
7699
- resolve();
7700
- }, 0);
7701
- });
7702
7802
  const batch = {
7703
7803
  paths: new Map(targetPaths.map((relativePath) => [path.join(hqRoot, relativePath), relativePath])),
7704
7804
  changes: new Map(targetPaths.map((relativePath) => [
@@ -7706,17 +7806,73 @@ describe("runRunnerWithLoop — event-push wiring", () => {
7706
7806
  { kind: "change" },
7707
7807
  ])),
7708
7808
  };
7709
- watcher.emit(targetPaths[0], batch);
7710
- await vi.waitFor(() => expect(committedAt).toBeDefined(), { timeout: 30_000, interval: 10 });
7711
- await eventLoopTurn;
7712
- eventLoopDelay.disable();
7713
- expect(committedAt - submittedAt).toBeLessThan(1_000);
7714
- expect(Number(eventLoopDelay.max) / 1_000_000).toBeLessThan(100);
7715
- expect(timerDelayMs).toBeLessThan(100);
7809
+ // Measure each commit independently so one long event-loop block cannot
7810
+ // disappear inside an aggregate statistic.
7811
+ const loopDelayMaxMs = [];
7812
+ const commitDurations = [];
7813
+ for (let sample = 0; sample < 5; sample += 1) {
7814
+ const loopDelay = monitorEventLoopDelay({ resolution: 10 });
7815
+ loopDelay.enable();
7816
+ try {
7817
+ // Preserve the old pre-commit window as a separately reported idle
7818
+ // baseline. The guarded histogram starts only after this window.
7819
+ const warmupStart = performance.now();
7820
+ await new Promise((resolve) => setTimeout(resolve, 30));
7821
+ const warmupEnd = performance.now();
7822
+ for (const entry of gcObserver.takeRecords()) {
7823
+ const detail = entry;
7824
+ gcActivity.push({ kind: detail.detail?.kind ?? null, duration: entry.duration, startTime: entry.startTime });
7825
+ }
7826
+ const warmupMaxDelayMs = loopDelay.max / 1e6;
7827
+ const warmupGc = gcActivity
7828
+ .filter((entry) => entry.startTime >= warmupStart && entry.startTime <= warmupEnd)
7829
+ .map((entry) => ({ kind: entry.kind, duration: Number(entry.duration.toFixed(2)), startTime: Number(entry.startTime.toFixed(2)) }));
7830
+ loopDelay.reset();
7831
+ committedAt = undefined;
7832
+ const windowStart = performance.now();
7833
+ activeCommitSample = { sample: sample + 1, submittedAt: windowStart };
7834
+ const watcherCallbackStartedAt = performance.now();
7835
+ watcher.emit(targetPaths[0], batch);
7836
+ const watcherCallbackSyncMs = performance.now() - watcherCallbackStartedAt;
7837
+ await vi.waitFor(() => expect(committedAt).toBeDefined(), { timeout: 30_000, interval: 10 });
7838
+ commitDurations.push(committedAt - windowStart);
7839
+ // Give the histogram's next 10 ms sample time to observe a stall that
7840
+ // ended in the commit callback before reading this window's maximum.
7841
+ await new Promise((resolve) => setTimeout(resolve, 20));
7842
+ const windowEnd = performance.now();
7843
+ for (const entry of gcObserver.takeRecords()) {
7844
+ const detail = entry;
7845
+ gcActivity.push({ kind: detail.detail?.kind ?? null, duration: entry.duration, startTime: entry.startTime });
7846
+ }
7847
+ const maxDelayMs = loopDelay.max / 1e6;
7848
+ loopDelayMaxMs.push(maxDelayMs);
7849
+ const phases = commitPhases.find((phase) => phase.sample === sample + 1);
7850
+ const sampleGc = gcActivity
7851
+ .filter((entry) => entry.startTime >= windowStart && entry.startTime <= windowEnd)
7852
+ .map((entry) => ({ kind: entry.kind, duration: Number(entry.duration.toFixed(2)), startTime: Number(entry.startTime.toFixed(2)) }));
7853
+ process.stdout.write(`realtime-commit-sample ${JSON.stringify({
7854
+ sample: sample + 1,
7855
+ warmupMaxDelayMs: Number(warmupMaxDelayMs.toFixed(1)),
7856
+ warmupGc,
7857
+ maxDelayMs: Number(maxDelayMs.toFixed(1)),
7858
+ windowStart: Number(windowStart.toFixed(2)),
7859
+ windowEnd: Number(windowEnd.toFixed(2)),
7860
+ phases: phases ? { ...phases, watcherCallbackSyncMs } : null,
7861
+ gc: sampleGc,
7862
+ })}\n`);
7863
+ activeCommitSample = undefined;
7864
+ }
7865
+ finally {
7866
+ loopDelay.disable();
7867
+ }
7868
+ }
7869
+ expect(Math.max(...commitDurations)).toBeLessThan(1_000);
7870
+ expect(Math.max(...loopDelayMaxMs)).toBeLessThan(100);
7716
7871
  expect(getStateStoreSnapshotRecoveryCountForTest()).toBe(0);
7717
- expect(runPass.mock.calls.filter(([argv]) => argv.includes("--scope-path"))).toHaveLength(1);
7872
+ expect(runPass.mock.calls.filter(([argv]) => argv.includes("--scope-path"))).toHaveLength(5);
7718
7873
  }
7719
7874
  finally {
7875
+ gcObserver.disconnect();
7720
7876
  shutdown();
7721
7877
  await loop;
7722
7878
  if (previousCoordinator === undefined)