@indigoai-us/hq-cloud 6.15.65 → 6.15.67
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.
- package/dist/bin/sync-runner-watch-loop.d.ts +30 -2
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-loop.js +206 -41
- package/dist/bin/sync-runner-watch-loop.js.map +1 -1
- package/dist/bin/sync-runner.d.ts +2 -0
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +260 -2
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/sync/event-sync.d.ts +6 -0
- package/dist/sync/event-sync.d.ts.map +1 -1
- package/dist/sync/event-sync.js +3 -0
- package/dist/sync/event-sync.js.map +1 -1
- package/dist/sync/push-receiver.d.ts +16 -1
- package/dist/sync/push-receiver.d.ts.map +1 -1
- package/dist/sync/push-receiver.js +23 -8
- package/dist/sync/push-receiver.js.map +1 -1
- package/dist/sync/push-receiver.test.js +50 -0
- package/dist/sync/push-receiver.test.js.map +1 -1
- package/dist/watcher.d.ts +18 -2
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +139 -2
- package/dist/watcher.js.map +1 -1
- package/dist/watcher.test.js +63 -7
- package/dist/watcher.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -14,7 +14,7 @@ import { runRunner, runRunnerWithLoop, resolveDeletePolicy, resolveSkipPersonal,
|
|
|
14
14
|
import { PresignObjectIO, S3SdkObjectIO } from "../object-io.js";
|
|
15
15
|
import { setBandwidthGovernorForTesting } from "../bandwidth.js";
|
|
16
16
|
import { FakeClock, TreeWatcher, } from "../watcher.js";
|
|
17
|
-
import { adaptivePollMs, advanceMonotonicDeadline, DEFAULT_EVENT_BATCH_LIMIT, DEFAULT_FAST_LANE_MAX_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, journalEntriesForWatcherDelete, resolveEventBatchLimit, resolveFullReconcileMs, shouldQuarantineWatcherDeleteIntents, WATCHER_REDISCOVERY_RETRY_MS, WATCHER_REDISCOVERY_RETRY_MAX_MS, } from "./sync-runner-watch-loop.js";
|
|
17
|
+
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, shouldQuarantineWatcherDeleteIntents, WATCHER_REDISCOVERY_RETRY_MS, WATCHER_REDISCOVERY_RETRY_MAX_MS, } from "./sync-runner-watch-loop.js";
|
|
18
18
|
import { PERSONAL_VAULT_JOURNAL_SLUG, readJournal, writeJournal, } from "../journal.js";
|
|
19
19
|
import { HQ_CLOUD_VERSION } from "../version.js";
|
|
20
20
|
import { lockPathFor, OPERATION_LOCKED_EXIT } from "../operation-lock.js";
|
|
@@ -3695,6 +3695,17 @@ describe("adaptivePollMs (whole-box CPU backoff)", () => {
|
|
|
3695
3695
|
expect(adaptivePollMs(4, 4, 30_000, 300_000)).toBe(300_000);
|
|
3696
3696
|
});
|
|
3697
3697
|
});
|
|
3698
|
+
describe("resolveWatchPollMs", () => {
|
|
3699
|
+
it("holds the floor after a receiver observed messages, then resumes normal backoff only after empty", () => {
|
|
3700
|
+
// Regression (v6.15.66): a bulk receive was waiting behind a never-ending
|
|
3701
|
+
// stream of fast passes while the CPU sampler stretched ticks to 10 min.
|
|
3702
|
+
expect(resolveWatchPollMs(undefined, true, 1_000, 1)).toBe(60_000);
|
|
3703
|
+
expect(resolveWatchPollMs(undefined, false, 1_000, 1)).toBe(600_000);
|
|
3704
|
+
});
|
|
3705
|
+
it("preserves an explicit poll cadence regardless of receiver activity", () => {
|
|
3706
|
+
expect(resolveWatchPollMs(45_000, true, 1_000, 1)).toBe(45_000);
|
|
3707
|
+
});
|
|
3708
|
+
});
|
|
3698
3709
|
describe("advanceMonotonicDeadline", () => {
|
|
3699
3710
|
it("gives an overrun pass a full cooldown instead of running an immediate catch-up", () => {
|
|
3700
3711
|
// A pass due at t=0 took through t=1,800 with a 600 ms cadence. The next
|
|
@@ -3741,6 +3752,90 @@ describe("runRunnerWithLoop — adaptive poll interval (no --poll-remote-ms)", (
|
|
|
3741
3752
|
},
|
|
3742
3753
|
};
|
|
3743
3754
|
}
|
|
3755
|
+
function makeInterruptibleCapturingSleep() {
|
|
3756
|
+
const delays = [];
|
|
3757
|
+
const pending = [];
|
|
3758
|
+
return {
|
|
3759
|
+
delays,
|
|
3760
|
+
sleep: (delay) => new Promise((resolve) => {
|
|
3761
|
+
delays.push(delay);
|
|
3762
|
+
pending.push(resolve);
|
|
3763
|
+
}),
|
|
3764
|
+
resolveLatest() {
|
|
3765
|
+
const resolve = pending.at(-1);
|
|
3766
|
+
if (!resolve)
|
|
3767
|
+
throw new Error("poll sleep was not pending");
|
|
3768
|
+
resolve();
|
|
3769
|
+
},
|
|
3770
|
+
};
|
|
3771
|
+
}
|
|
3772
|
+
it("resets a long adaptive deadline when a received batch tightens the cadence", async () => {
|
|
3773
|
+
const sleep = makeInterruptibleCapturingSleep();
|
|
3774
|
+
const watcher = makeWatcherStub();
|
|
3775
|
+
const clockState = { now: 0 };
|
|
3776
|
+
let syncBatch;
|
|
3777
|
+
let shutdown = () => { };
|
|
3778
|
+
const loop = runRunnerWithLoop(["--companies", "--watch", "--event-push", "--hq-root", "/tmp/hq"], {
|
|
3779
|
+
runPass: vi.fn().mockResolvedValue(0),
|
|
3780
|
+
createWatcher: () => watcher,
|
|
3781
|
+
createReceiver: ({ syncBatchFn }) => {
|
|
3782
|
+
syncBatch = syncBatchFn;
|
|
3783
|
+
return { connected: true, start: async () => { }, dispose: async () => { } };
|
|
3784
|
+
},
|
|
3785
|
+
sleep: sleep.sleep,
|
|
3786
|
+
sampleLoadAvg: () => 1_000,
|
|
3787
|
+
monotonicNow: () => clockState.now,
|
|
3788
|
+
onShutdownSignal: (handler) => {
|
|
3789
|
+
shutdown = handler;
|
|
3790
|
+
return () => { };
|
|
3791
|
+
},
|
|
3792
|
+
});
|
|
3793
|
+
await flushLoopMicrotasks();
|
|
3794
|
+
expect(sleep.delays).toEqual([600_000]);
|
|
3795
|
+
clockState.now = 1_000;
|
|
3796
|
+
await syncBatch({ events: [], signal: new AbortController().signal });
|
|
3797
|
+
await flushLoopMicrotasks();
|
|
3798
|
+
// The receive interrupts a 10-minute wait, so its next poll is one minute
|
|
3799
|
+
// from the current monotonic time, not one minute after the old deadline.
|
|
3800
|
+
expect(sleep.delays).toEqual([600_000, 60_000]);
|
|
3801
|
+
shutdown();
|
|
3802
|
+
await loop;
|
|
3803
|
+
});
|
|
3804
|
+
it("lets an injected receiver clear queued-work cadence state after an empty receive", async () => {
|
|
3805
|
+
const sleep = makeInterruptibleCapturingSleep();
|
|
3806
|
+
const watcher = makeWatcherStub();
|
|
3807
|
+
const clockState = { now: 0 };
|
|
3808
|
+
let reportReceiveActivity;
|
|
3809
|
+
let shutdown = () => { };
|
|
3810
|
+
const loop = runRunnerWithLoop(["--companies", "--watch", "--event-push", "--hq-root", "/tmp/hq"], {
|
|
3811
|
+
runPass: vi.fn().mockResolvedValue(0),
|
|
3812
|
+
createWatcher: () => watcher,
|
|
3813
|
+
createReceiver: ({ onReceiveActivity }) => {
|
|
3814
|
+
reportReceiveActivity = onReceiveActivity;
|
|
3815
|
+
return { connected: true, start: async () => { }, dispose: async () => { } };
|
|
3816
|
+
},
|
|
3817
|
+
sleep: sleep.sleep,
|
|
3818
|
+
sampleLoadAvg: () => 1_000,
|
|
3819
|
+
monotonicNow: () => clockState.now,
|
|
3820
|
+
onShutdownSignal: (handler) => {
|
|
3821
|
+
shutdown = handler;
|
|
3822
|
+
return () => { };
|
|
3823
|
+
},
|
|
3824
|
+
});
|
|
3825
|
+
await flushLoopMicrotasks();
|
|
3826
|
+
expect(reportReceiveActivity).toBeTypeOf("function");
|
|
3827
|
+
clockState.now = 1_000;
|
|
3828
|
+
reportReceiveActivity(true);
|
|
3829
|
+
await flushLoopMicrotasks();
|
|
3830
|
+
expect(sleep.delays).toEqual([600_000, 60_000]);
|
|
3831
|
+
reportReceiveActivity(false);
|
|
3832
|
+
clockState.now = 61_000;
|
|
3833
|
+
sleep.resolveLatest();
|
|
3834
|
+
await flushLoopMicrotasks();
|
|
3835
|
+
expect(sleep.delays).toEqual([600_000, 60_000, 600_000]);
|
|
3836
|
+
shutdown();
|
|
3837
|
+
await loop;
|
|
3838
|
+
});
|
|
3744
3839
|
it("waits only the bounded remainder of the idle cadence slot", async () => {
|
|
3745
3840
|
const { calls, sleep } = makeCapturingSleep();
|
|
3746
3841
|
const monotonicNow = vi
|
|
@@ -4697,6 +4792,62 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
4697
4792
|
await loop;
|
|
4698
4793
|
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
4699
4794
|
});
|
|
4795
|
+
it("fast lane: peels a tiny watcher path from mixed multi-megabyte residue", async () => {
|
|
4796
|
+
const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-fast-lane-mixed-watch-"));
|
|
4797
|
+
const tinyRel = "companies/indigo/realtime.md";
|
|
4798
|
+
const tiny = path.join(hqRoot, tinyRel);
|
|
4799
|
+
const bulk = [0, 1, 2].map((index) => path.join(hqRoot, `companies/indigo/session-${index}.log`));
|
|
4800
|
+
fs.mkdirSync(path.dirname(tiny), { recursive: true });
|
|
4801
|
+
fs.writeFileSync(tiny, "tiny realtime edit");
|
|
4802
|
+
for (const file of bulk)
|
|
4803
|
+
fs.writeFileSync(file, Buffer.alloc(8 * 1024 * 1024));
|
|
4804
|
+
const watcher = makeBatchWatcherStub();
|
|
4805
|
+
let triggerShutdown = () => { };
|
|
4806
|
+
let releaseBulk = () => { };
|
|
4807
|
+
const bulkGate = new Promise((resolve) => { releaseBulk = resolve; });
|
|
4808
|
+
const calls = [];
|
|
4809
|
+
let fullPasses = 0;
|
|
4810
|
+
const runPass = vi.fn().mockImplementation(async (passArgv) => {
|
|
4811
|
+
calls.push(passArgv);
|
|
4812
|
+
if (!passArgv.includes("--scope-path")) {
|
|
4813
|
+
fullPasses += 1;
|
|
4814
|
+
if (fullPasses === 2)
|
|
4815
|
+
await bulkGate;
|
|
4816
|
+
}
|
|
4817
|
+
return completedPassOutcome();
|
|
4818
|
+
});
|
|
4819
|
+
const loop = runRunnerWithLoop(["--companies", "--watch", "--event-push", "--direction", "push", "--hq-root", hqRoot], {
|
|
4820
|
+
runPass,
|
|
4821
|
+
clock: new FakeClock(),
|
|
4822
|
+
createWatcher: () => watcher,
|
|
4823
|
+
sleep: () => new Promise(() => { }),
|
|
4824
|
+
onShutdownSignal: (handler) => {
|
|
4825
|
+
triggerShutdown = handler;
|
|
4826
|
+
return () => { };
|
|
4827
|
+
},
|
|
4828
|
+
});
|
|
4829
|
+
await flushLoopMicrotasks();
|
|
4830
|
+
watcher.emit();
|
|
4831
|
+
await flushLoopMicrotasks();
|
|
4832
|
+
watcher.emit(tinyRel, {
|
|
4833
|
+
paths: new Map([
|
|
4834
|
+
[tiny, tinyRel],
|
|
4835
|
+
...bulk.map((file, index) => [file, `companies/indigo/session-${index}.log`]),
|
|
4836
|
+
]),
|
|
4837
|
+
});
|
|
4838
|
+
await flushLoopMicrotasks(30);
|
|
4839
|
+
// origin/main failure: expected a scoped tiny path while the bulk pass is
|
|
4840
|
+
// held, but #408 kept all four paths in the slow lane.
|
|
4841
|
+
expect(calls.filter((argv) => argv.includes("--scope-path"))).toHaveLength(1);
|
|
4842
|
+
expect(calls.flat().join(" ")).toContain("realtime.md");
|
|
4843
|
+
releaseBulk();
|
|
4844
|
+
await flushLoopMicrotasks(30);
|
|
4845
|
+
expect(calls.filter((argv) => argv.includes("--scope-path"))).toHaveLength(2);
|
|
4846
|
+
expect(calls.flat().join(" ")).toContain("session-0.log");
|
|
4847
|
+
triggerShutdown();
|
|
4848
|
+
await loop;
|
|
4849
|
+
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
4850
|
+
});
|
|
4700
4851
|
it("fast lane: a small receiver pull dispatches during a slow bulk pass", async () => {
|
|
4701
4852
|
const watcher = makeWatcherStub();
|
|
4702
4853
|
let triggerShutdown = () => { };
|
|
@@ -4705,6 +4856,14 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
4705
4856
|
const bulkGate = new Promise((resolve) => { releaseBulk = resolve; });
|
|
4706
4857
|
const order = [];
|
|
4707
4858
|
let fullPasses = 0;
|
|
4859
|
+
const journal = readJournal("acme");
|
|
4860
|
+
journal.files["realtime.md"] = {
|
|
4861
|
+
hash: "sha256:x",
|
|
4862
|
+
size: 16,
|
|
4863
|
+
syncedAt: "2026-08-26T00:00:00.000Z",
|
|
4864
|
+
direction: "down",
|
|
4865
|
+
};
|
|
4866
|
+
writeJournal("acme", journal);
|
|
4708
4867
|
const runPass = vi.fn().mockImplementation(async (passArgv) => {
|
|
4709
4868
|
if (passArgv.includes("--scope-path")) {
|
|
4710
4869
|
order.push("fast-pull");
|
|
@@ -4954,12 +5113,28 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
4954
5113
|
eventTimestamp: "2026-08-26T00:00:00.000Z",
|
|
4955
5114
|
})),
|
|
4956
5115
|
];
|
|
5116
|
+
for (const [slug, prefix, count] of [["acme", "a", 17], ["beta", "b", 16]]) {
|
|
5117
|
+
const journal = readJournal(slug);
|
|
5118
|
+
for (let index = 0; index < count; index++) {
|
|
5119
|
+
journal.files[`${prefix}-${index}.md`] = {
|
|
5120
|
+
hash: "sha256:x",
|
|
5121
|
+
size: 16,
|
|
5122
|
+
syncedAt: "2026-08-26T00:00:00.000Z",
|
|
5123
|
+
direction: "down",
|
|
5124
|
+
};
|
|
5125
|
+
}
|
|
5126
|
+
writeJournal(slug, journal);
|
|
5127
|
+
}
|
|
4957
5128
|
const receiverDrain = receiverSync({
|
|
4958
5129
|
events,
|
|
4959
5130
|
signal: new AbortController().signal,
|
|
4960
5131
|
});
|
|
4961
5132
|
await flushLoopMicrotasks(20);
|
|
4962
|
-
|
|
5133
|
+
// The 33-event mixed delivery peels its first 32 bounded paths into the
|
|
5134
|
+
// fast lane (17 acme + 15 beta); the final beta event remains slow.
|
|
5135
|
+
expect(runPass).toHaveBeenCalledTimes(2);
|
|
5136
|
+
expect(runPass.mock.calls.flat().join(" ")).toContain("a-16.md");
|
|
5137
|
+
expect(runPass.mock.calls.flat().join(" ")).toContain("b-14.md");
|
|
4963
5138
|
releaseBulk();
|
|
4964
5139
|
await receiverDrain;
|
|
4965
5140
|
triggerShutdown();
|
|
@@ -5023,6 +5198,65 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
5023
5198
|
await loop;
|
|
5024
5199
|
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
5025
5200
|
});
|
|
5201
|
+
it("REGRESSION: a queued bulk receiver drain gets a turn before later fast arrivals", async () => {
|
|
5202
|
+
const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-bulk-drain-fairness-"));
|
|
5203
|
+
const watcher = makeWatcherStub();
|
|
5204
|
+
let receiverSync;
|
|
5205
|
+
let triggerShutdown = () => { };
|
|
5206
|
+
let releaseFirstFast = () => { };
|
|
5207
|
+
const firstFast = new Promise((resolve) => { releaseFirstFast = resolve; });
|
|
5208
|
+
const order = [];
|
|
5209
|
+
const journal = readJournal("acme");
|
|
5210
|
+
journal.files["realtime.md"] = {
|
|
5211
|
+
hash: "sha256:x", size: 16, syncedAt: "2026-08-26T00:00:00.000Z", direction: "down",
|
|
5212
|
+
};
|
|
5213
|
+
writeJournal("acme", journal);
|
|
5214
|
+
const runPass = vi.fn().mockImplementation(async (argv) => {
|
|
5215
|
+
const scope = argv[argv.indexOf("--scope-path") + 1];
|
|
5216
|
+
if (scope === "realtime.md") {
|
|
5217
|
+
order.push(`fast-${order.filter((item) => item.startsWith("fast-")).length + 1}`);
|
|
5218
|
+
if (order.length === 1)
|
|
5219
|
+
await firstFast;
|
|
5220
|
+
}
|
|
5221
|
+
else {
|
|
5222
|
+
order.push("bulk");
|
|
5223
|
+
}
|
|
5224
|
+
return completedPassOutcome();
|
|
5225
|
+
});
|
|
5226
|
+
const loop = runRunnerWithLoop(["--companies", "--watch", "--event-push", "--hq-root", hqRoot], {
|
|
5227
|
+
runPass,
|
|
5228
|
+
createWatcher: () => watcher,
|
|
5229
|
+
createReceiver: ({ syncBatchFn }) => {
|
|
5230
|
+
receiverSync = syncBatchFn;
|
|
5231
|
+
return { connected: true, start: async () => { }, dispose: async () => { } };
|
|
5232
|
+
},
|
|
5233
|
+
sleep: () => new Promise(() => { }),
|
|
5234
|
+
onShutdownSignal: (handler) => {
|
|
5235
|
+
triggerShutdown = handler;
|
|
5236
|
+
return () => { };
|
|
5237
|
+
},
|
|
5238
|
+
});
|
|
5239
|
+
await flushLoopMicrotasks();
|
|
5240
|
+
order.splice(0);
|
|
5241
|
+
const event = (relativePath, sequenceNumber) => ({
|
|
5242
|
+
kind: "upsert", relativePath, contentHash: "sha256:x", mtime: "2026-08-26T00:00:00.000Z",
|
|
5243
|
+
originDeviceId: "peer", originTenantId: "tenant-acme", sequenceNumber,
|
|
5244
|
+
eventTimestamp: "2026-08-26T00:00:00.000Z",
|
|
5245
|
+
});
|
|
5246
|
+
const fastOne = receiverSync({ events: [event("companies/acme/realtime.md", 1)], signal: new AbortController().signal });
|
|
5247
|
+
await flushLoopMicrotasks();
|
|
5248
|
+
const bulk = receiverSync({ events: [event("companies/acme/bulk.log", 2)], signal: new AbortController().signal });
|
|
5249
|
+
const fastTwo = receiverSync({ events: [event("companies/acme/realtime.md", 3)], signal: new AbortController().signal });
|
|
5250
|
+
releaseFirstFast();
|
|
5251
|
+
await flushLoopMicrotasks(30);
|
|
5252
|
+
// origin/main failure: ["fast-1", "fast-2"] — the queued bulk path is
|
|
5253
|
+
// perpetually leapfrogged and the SQS receiver cannot issue its next poll.
|
|
5254
|
+
expect(order.slice(0, 2)).toEqual(["fast-1", "bulk"]);
|
|
5255
|
+
await Promise.all([fastOne, bulk, fastTwo]);
|
|
5256
|
+
triggerShutdown();
|
|
5257
|
+
await loop;
|
|
5258
|
+
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
5259
|
+
});
|
|
5026
5260
|
it("fast lane: keeps disjoint journal rows under a 16-path interleave with a bulk pass", async () => {
|
|
5027
5261
|
const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-fast-lane-journal-"));
|
|
5028
5262
|
const paths = Array.from({ length: 16 }, (_, index) => {
|
|
@@ -5171,6 +5405,30 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
5171
5405
|
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
5172
5406
|
}
|
|
5173
5407
|
});
|
|
5408
|
+
it("fast lane: keeps a latest large revision in the slow residue and caps 33 small paths", () => {
|
|
5409
|
+
const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-fast-lane-partition-"));
|
|
5410
|
+
const rel = "companies/indigo/revised.md";
|
|
5411
|
+
const absolute = path.join(hqRoot, rel);
|
|
5412
|
+
fs.mkdirSync(path.dirname(absolute), { recursive: true });
|
|
5413
|
+
// The map is the coalescing/affinity boundary: its one value is the latest
|
|
5414
|
+
// revision, so this path cannot appear in both sub-batches.
|
|
5415
|
+
fs.writeFileSync(absolute, Buffer.alloc(DEFAULT_FAST_LANE_MAX_FILE_BYTES + 1));
|
|
5416
|
+
const revised = partitionLocalBatch({ paths: new Map([[absolute, rel]]) });
|
|
5417
|
+
expect(revised.fast).toBeNull();
|
|
5418
|
+
expect(revised.slow?.paths.get(absolute)).toBe(rel);
|
|
5419
|
+
const entries = [];
|
|
5420
|
+
for (let index = 0; index < DEFAULT_FAST_LANE_MAX_FILES + 1; index++) {
|
|
5421
|
+
const file = path.join(hqRoot, `companies/indigo/small-${index}.md`);
|
|
5422
|
+
fs.writeFileSync(file, "x");
|
|
5423
|
+
entries.push([file, `companies/indigo/small-${index}.md`]);
|
|
5424
|
+
}
|
|
5425
|
+
const capped = partitionLocalBatch({ paths: new Map(entries) });
|
|
5426
|
+
expect(capped.fast?.paths.size).toBe(DEFAULT_FAST_LANE_MAX_FILES);
|
|
5427
|
+
// The 33rd path deliberately remains in the slow residue, rather than
|
|
5428
|
+
// creating a second concurrent realtime dispatch.
|
|
5429
|
+
expect(capped.slow?.paths.size).toBe(1);
|
|
5430
|
+
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
5431
|
+
});
|
|
5174
5432
|
it("U07: drains the first watcher event after activation without guarded overlap", async () => {
|
|
5175
5433
|
const watcher = makeWatcherStub();
|
|
5176
5434
|
let triggerShutdown = () => { };
|