@indigoai-us/hq-cloud 6.15.53 → 6.15.55

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.
@@ -5078,6 +5078,27 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
5078
5078
  function startDrainLoop(runPass) {
5079
5079
  return startDrainLoopWithArgv(runPass, watchArgv);
5080
5080
  }
5081
+ function startBatchDrainLoop(runPass) {
5082
+ const watcher = makeBatchWatcherStub();
5083
+ const sleep = makeSteppableSleep();
5084
+ let triggerShutdown = () => { };
5085
+ const loop = runRunnerWithLoop(watchArgv, {
5086
+ runPass,
5087
+ clock: new FakeClock(),
5088
+ createWatcher: () => watcher,
5089
+ sleep: sleep.sleep,
5090
+ onShutdownSignal: (handler) => {
5091
+ triggerShutdown = handler;
5092
+ return () => { };
5093
+ },
5094
+ });
5095
+ return {
5096
+ loop,
5097
+ watcher,
5098
+ tick: sleep.tick,
5099
+ shutdown: () => triggerShutdown(),
5100
+ };
5101
+ }
5081
5102
  function startDrainLoopWithArgv(runPass, argv) {
5082
5103
  const watcher = makeWatcherStub();
5083
5104
  const sleep = makeSteppableSleep();
@@ -5099,6 +5120,293 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
5099
5120
  shutdown: () => triggerShutdown(),
5100
5121
  };
5101
5122
  }
5123
+ function captureFullReconcileDecisions() {
5124
+ const records = [];
5125
+ const spy = vi
5126
+ .spyOn(process.stderr, "write")
5127
+ .mockImplementation((chunk) => {
5128
+ if (typeof chunk === "string" && chunk.startsWith("{")) {
5129
+ const parsed = JSON.parse(String(chunk));
5130
+ if (typeof parsed === "object" &&
5131
+ parsed !== null &&
5132
+ parsed.type === "full-reconcile-decision") {
5133
+ records.push(parsed);
5134
+ }
5135
+ }
5136
+ return true;
5137
+ });
5138
+ return {
5139
+ records,
5140
+ clear: () => records.splice(0),
5141
+ restore: () => spy.mockRestore(),
5142
+ };
5143
+ }
5144
+ const allTriggerFlags = (matched = []) => ({
5145
+ "event-push-surfaces-inactive": matched.includes("event-push-surfaces-inactive"),
5146
+ "first-tick": matched.includes("first-tick"),
5147
+ "bare-wake": matched.includes("bare-wake"),
5148
+ "batch-over-limit": matched.includes("batch-over-limit"),
5149
+ "overflow-routes-unknown": matched.includes("overflow-routes-unknown"),
5150
+ "scheduled-interval": matched.includes("scheduled-interval"),
5151
+ });
5152
+ function expectFullReconcileDecision(record, mode, reasons) {
5153
+ expect(record).toMatchObject({
5154
+ type: "full-reconcile-decision",
5155
+ mode,
5156
+ reasons,
5157
+ triggers: allTriggerFlags(reasons),
5158
+ });
5159
+ }
5160
+ it("logs first-tick as the sole full-reconcile trigger when event push is off", async () => {
5161
+ const capture = captureFullReconcileDecisions();
5162
+ const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
5163
+ const sleep = makeSteppableSleep();
5164
+ let shutdown = () => { };
5165
+ const loop = runRunnerWithLoop([
5166
+ "--companies", "--watch", "--direction", "both", "--hq-root", "/tmp/hq",
5167
+ "--poll-remote-ms", "60000",
5168
+ ], {
5169
+ runPass,
5170
+ sleep: sleep.sleep,
5171
+ onShutdownSignal: (handler) => {
5172
+ shutdown = handler;
5173
+ return () => { };
5174
+ },
5175
+ });
5176
+ await flushLoopMicrotasks();
5177
+ expectFullReconcileDecision(capture.records[0], "full", ["first-tick"]);
5178
+ expect(runPass.mock.calls.map((call) => call[0])).toEqual([fullArgv]);
5179
+ shutdown();
5180
+ sleep.tick();
5181
+ await loop;
5182
+ capture.restore();
5183
+ });
5184
+ it("logs event-push-surfaces-inactive without silently attributing the first tick", async () => {
5185
+ const saved = process.env.HQ_SYNC_FULL_RECONCILE_MS;
5186
+ process.env.HQ_SYNC_FULL_RECONCILE_MS = "10800000";
5187
+ const capture = captureFullReconcileDecisions();
5188
+ try {
5189
+ const sleep = makeSteppableSleep();
5190
+ let shutdown = () => { };
5191
+ const runPass = vi.fn().mockResolvedValue(PARTIAL_SYNC_EXIT);
5192
+ const loop = runRunnerWithLoop(watchArgv, {
5193
+ runPass,
5194
+ monotonicNow: () => 0,
5195
+ sleep: sleep.sleep,
5196
+ onShutdownSignal: (handler) => {
5197
+ shutdown = handler;
5198
+ return () => { };
5199
+ },
5200
+ });
5201
+ await flushLoopMicrotasks();
5202
+ capture.clear();
5203
+ runPass.mockClear();
5204
+ sleep.tick();
5205
+ await flushLoopMicrotasks();
5206
+ expectFullReconcileDecision(capture.records[0], "full", [
5207
+ "event-push-surfaces-inactive",
5208
+ ]);
5209
+ expect(runPass.mock.calls.map((call) => call[0])).toEqual([fullArgv]);
5210
+ shutdown();
5211
+ await loop;
5212
+ }
5213
+ finally {
5214
+ capture.restore();
5215
+ if (saved === undefined)
5216
+ delete process.env.HQ_SYNC_FULL_RECONCILE_MS;
5217
+ else
5218
+ process.env.HQ_SYNC_FULL_RECONCILE_MS = saved;
5219
+ }
5220
+ });
5221
+ it("reports every matching trigger instead of short-circuiting at the first", async () => {
5222
+ const capture = captureFullReconcileDecisions();
5223
+ const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
5224
+ const { loop, shutdown } = startDrainLoop(runPass);
5225
+ await flushLoopMicrotasks();
5226
+ expectFullReconcileDecision(capture.records[0], "full", [
5227
+ "event-push-surfaces-inactive",
5228
+ "first-tick",
5229
+ ]);
5230
+ expect(runPass.mock.calls.map((call) => call[0])).toEqual([fullArgv]);
5231
+ shutdown();
5232
+ await loop;
5233
+ capture.restore();
5234
+ });
5235
+ it("logs a bare watcher wake as the sole full-reconcile trigger", async () => {
5236
+ const capture = captureFullReconcileDecisions();
5237
+ const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
5238
+ const { loop, watcher, shutdown } = startDrainLoop(runPass);
5239
+ await flushLoopMicrotasks();
5240
+ capture.clear();
5241
+ runPass.mockClear();
5242
+ watcher.emit();
5243
+ await flushLoopMicrotasks();
5244
+ expectFullReconcileDecision(capture.records[0], "full", ["bare-wake"]);
5245
+ expect(runPass.mock.calls.map((call) => call[0])).toEqual([fullArgv]);
5246
+ shutdown();
5247
+ await loop;
5248
+ capture.restore();
5249
+ });
5250
+ it("logs batch-over-limit as the sole full-reconcile trigger", async () => {
5251
+ const saved = process.env.HQ_SYNC_EVENT_BATCH_LIMIT;
5252
+ process.env.HQ_SYNC_EVENT_BATCH_LIMIT = "2";
5253
+ const capture = captureFullReconcileDecisions();
5254
+ try {
5255
+ const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
5256
+ const { loop, watcher, tick, shutdown } = startBatchDrainLoop(runPass);
5257
+ await flushLoopMicrotasks();
5258
+ capture.clear();
5259
+ runPass.mockClear();
5260
+ watcher.emit("companies/indigo/a.md", {
5261
+ paths: new Map([
5262
+ ["/tmp/hq/companies/indigo/a.md", "companies/indigo/a.md"],
5263
+ ["/tmp/hq/companies/indigo/b.md", "companies/indigo/b.md"],
5264
+ ["/tmp/hq/companies/indigo/c.md", "companies/indigo/c.md"],
5265
+ ]),
5266
+ });
5267
+ tick();
5268
+ await flushLoopMicrotasks();
5269
+ expectFullReconcileDecision(capture.records[0], "full", ["batch-over-limit"]);
5270
+ expect(capture.records[0]?.batchPathCount).toBe(3);
5271
+ expect(runPass.mock.calls.map((call) => call[0])).toEqual([fullArgv]);
5272
+ shutdown();
5273
+ await loop;
5274
+ }
5275
+ finally {
5276
+ capture.restore();
5277
+ if (saved === undefined)
5278
+ delete process.env.HQ_SYNC_EVENT_BATCH_LIMIT;
5279
+ else
5280
+ process.env.HQ_SYNC_EVENT_BATCH_LIMIT = saved;
5281
+ }
5282
+ });
5283
+ it("logs overflow-routes-unknown as the sole full-reconcile trigger", async () => {
5284
+ const capture = captureFullReconcileDecisions();
5285
+ const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
5286
+ const { loop, watcher, tick, shutdown } = startBatchDrainLoop(runPass);
5287
+ await flushLoopMicrotasks();
5288
+ capture.clear();
5289
+ runPass.mockClear();
5290
+ watcher.emit("companies/indigo/a.md", {
5291
+ paths: new Map([["/tmp/hq/companies/indigo/a.md", "companies/indigo/a.md"]]),
5292
+ overflowed: true,
5293
+ });
5294
+ tick();
5295
+ await flushLoopMicrotasks();
5296
+ expectFullReconcileDecision(capture.records[0], "full", [
5297
+ "overflow-routes-unknown",
5298
+ ]);
5299
+ expect(runPass.mock.calls.map((call) => call[0])).toEqual([fullArgv]);
5300
+ shutdown();
5301
+ await loop;
5302
+ capture.restore();
5303
+ });
5304
+ it("logs scheduled-interval as the sole full-reconcile trigger on the legacy cadence", async () => {
5305
+ const saved = process.env.HQ_SYNC_FULL_RECONCILE_MS;
5306
+ delete process.env.HQ_SYNC_FULL_RECONCILE_MS;
5307
+ const capture = captureFullReconcileDecisions();
5308
+ try {
5309
+ const sleep = makeSteppableSleep();
5310
+ let shutdown = () => { };
5311
+ const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
5312
+ const loop = runRunnerWithLoop([
5313
+ "--companies", "--watch", "--direction", "both", "--hq-root", "/tmp/hq",
5314
+ "--poll-remote-ms", "60000",
5315
+ ], {
5316
+ runPass,
5317
+ sleep: sleep.sleep,
5318
+ onShutdownSignal: (handler) => {
5319
+ shutdown = handler;
5320
+ return () => { };
5321
+ },
5322
+ });
5323
+ await flushLoopMicrotasks();
5324
+ capture.clear();
5325
+ runPass.mockClear();
5326
+ for (let tick = 0; tick < 8; tick++) {
5327
+ sleep.tick();
5328
+ await flushLoopMicrotasks();
5329
+ }
5330
+ capture.clear();
5331
+ runPass.mockClear();
5332
+ sleep.tick();
5333
+ await flushLoopMicrotasks();
5334
+ expectFullReconcileDecision(capture.records[0], "full", [
5335
+ "scheduled-interval",
5336
+ ]);
5337
+ expect(runPass.mock.calls.map((call) => call[0])).toEqual([fullArgv]);
5338
+ shutdown();
5339
+ await loop;
5340
+ }
5341
+ finally {
5342
+ capture.restore();
5343
+ if (saved === undefined)
5344
+ delete process.env.HQ_SYNC_FULL_RECONCILE_MS;
5345
+ else
5346
+ process.env.HQ_SYNC_FULL_RECONCILE_MS = saved;
5347
+ }
5348
+ });
5349
+ it("logs scoped passes with their batch path count and preserves the scoped route", async () => {
5350
+ const capture = captureFullReconcileDecisions();
5351
+ const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
5352
+ const { loop, watcher, shutdown } = startDrainLoop(runPass);
5353
+ await flushLoopMicrotasks();
5354
+ capture.clear();
5355
+ runPass.mockClear();
5356
+ watcher.emit("companies/indigo/knowledge/a.md");
5357
+ await flushLoopMicrotasks();
5358
+ expectFullReconcileDecision(capture.records[0], "scoped", []);
5359
+ expect(capture.records[0]?.batchPathCount).toBe(1);
5360
+ expect(runPass.mock.calls.map((call) => call[0])).not.toContainEqual(fullArgv);
5361
+ expect(runPass).toHaveBeenCalledTimes(2);
5362
+ shutdown();
5363
+ await loop;
5364
+ capture.restore();
5365
+ });
5366
+ it("reports interval diagnostics while the configured interval has not elapsed", async () => {
5367
+ const saved = process.env.HQ_SYNC_FULL_RECONCILE_MS;
5368
+ process.env.HQ_SYNC_FULL_RECONCILE_MS = "1000";
5369
+ const capture = captureFullReconcileDecisions();
5370
+ try {
5371
+ const clockState = { now: 0 };
5372
+ const sleep = makeSteppableSleep();
5373
+ let shutdown = () => { };
5374
+ const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
5375
+ const loop = runRunnerWithLoop(watchArgv, {
5376
+ runPass,
5377
+ monotonicNow: () => clockState.now,
5378
+ createWatcher: () => makeBatchWatcherStub(),
5379
+ sleep: sleep.sleep,
5380
+ onShutdownSignal: (handler) => {
5381
+ shutdown = handler;
5382
+ return () => { };
5383
+ },
5384
+ });
5385
+ await flushLoopMicrotasks();
5386
+ capture.clear();
5387
+ runPass.mockClear();
5388
+ clockState.now = 999;
5389
+ sleep.tick();
5390
+ await flushLoopMicrotasks();
5391
+ expectFullReconcileDecision(capture.records[0], "scoped", []);
5392
+ expect(capture.records[0]).toMatchObject({
5393
+ pollTick: 2,
5394
+ configuredIntervalMs: 1000,
5395
+ elapsedSinceLastCompletedFullReconcileMs: 999,
5396
+ triggers: allTriggerFlags(),
5397
+ });
5398
+ expect(runPass.mock.calls.map((call) => call[0])).toEqual([fullPullArgv]);
5399
+ shutdown();
5400
+ await loop;
5401
+ }
5402
+ finally {
5403
+ capture.restore();
5404
+ if (saved === undefined)
5405
+ delete process.env.HQ_SYNC_FULL_RECONCILE_MS;
5406
+ else
5407
+ process.env.HQ_SYNC_FULL_RECONCILE_MS = saved;
5408
+ }
5409
+ });
5102
5410
  it("startup runs one full reconcile, then watcher edits interrupt the pending wait", async () => {
5103
5411
  const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
5104
5412
  const { loop, watcher, tick, shutdown } = startDrainLoop(runPass);
@@ -5747,6 +6055,7 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
5747
6055
  }
5748
6056
  });
5749
6057
  it("a throwing targeted pass falls back to one full reconcile instead of dropping the change", async () => {
6058
+ const capture = captureFullReconcileDecisions();
5750
6059
  const runPass = vi.fn(async (argv) => {
5751
6060
  if (argv.includes("pull") && argv.includes("--scope-path")) {
5752
6061
  throw new Error("scoped pull exploded");
@@ -5755,6 +6064,7 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
5755
6064
  });
5756
6065
  const { loop, watcher, tick, shutdown } = startDrainLoop(runPass);
5757
6066
  await flushLoopMicrotasks();
6067
+ capture.clear();
5758
6068
  runPass.mockClear();
5759
6069
  watcher.emit("companies/indigo/knowledge/a.md");
5760
6070
  tick();
@@ -5784,9 +6094,14 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
5784
6094
  // …and the loop degrades to the full reconcile, never dropping the tick.
5785
6095
  fullArgv,
5786
6096
  ]);
6097
+ expect(capture.records).toHaveLength(1);
6098
+ expectFullReconcileDecision(capture.records[0], "full", [
6099
+ "targeted-pass-failed",
6100
+ ]);
5787
6101
  shutdown();
5788
6102
  tick();
5789
6103
  await loop;
6104
+ capture.restore();
5790
6105
  });
5791
6106
  it("wires the event debounce config (default 15s quiet / 120s max-wait) into the watcher factory", async () => {
5792
6107
  const captured = [];