@indigoai-us/hq-cloud 6.15.65 → 6.15.66
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 +24 -2
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-loop.js +157 -37
- package/dist/bin/sync-runner-watch-loop.js.map +1 -1
- package/dist/bin/sync-runner.test.js +106 -2
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/sync/event-sync.d.ts +4 -0
- package/dist/sync/event-sync.d.ts.map +1 -1
- package/dist/sync/event-sync.js +2 -0
- package/dist/sync/event-sync.js.map +1 -1
- package/dist/sync/push-receiver.d.ts +8 -1
- package/dist/sync/push-receiver.d.ts.map +1 -1
- package/dist/sync/push-receiver.js +20 -8
- package/dist/sync/push-receiver.js.map +1 -1
- package/dist/sync/push-receiver.test.js +24 -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, 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";
|
|
@@ -4697,6 +4697,62 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
4697
4697
|
await loop;
|
|
4698
4698
|
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
4699
4699
|
});
|
|
4700
|
+
it("fast lane: peels a tiny watcher path from mixed multi-megabyte residue", async () => {
|
|
4701
|
+
const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-fast-lane-mixed-watch-"));
|
|
4702
|
+
const tinyRel = "companies/indigo/realtime.md";
|
|
4703
|
+
const tiny = path.join(hqRoot, tinyRel);
|
|
4704
|
+
const bulk = [0, 1, 2].map((index) => path.join(hqRoot, `companies/indigo/session-${index}.log`));
|
|
4705
|
+
fs.mkdirSync(path.dirname(tiny), { recursive: true });
|
|
4706
|
+
fs.writeFileSync(tiny, "tiny realtime edit");
|
|
4707
|
+
for (const file of bulk)
|
|
4708
|
+
fs.writeFileSync(file, Buffer.alloc(8 * 1024 * 1024));
|
|
4709
|
+
const watcher = makeBatchWatcherStub();
|
|
4710
|
+
let triggerShutdown = () => { };
|
|
4711
|
+
let releaseBulk = () => { };
|
|
4712
|
+
const bulkGate = new Promise((resolve) => { releaseBulk = resolve; });
|
|
4713
|
+
const calls = [];
|
|
4714
|
+
let fullPasses = 0;
|
|
4715
|
+
const runPass = vi.fn().mockImplementation(async (passArgv) => {
|
|
4716
|
+
calls.push(passArgv);
|
|
4717
|
+
if (!passArgv.includes("--scope-path")) {
|
|
4718
|
+
fullPasses += 1;
|
|
4719
|
+
if (fullPasses === 2)
|
|
4720
|
+
await bulkGate;
|
|
4721
|
+
}
|
|
4722
|
+
return completedPassOutcome();
|
|
4723
|
+
});
|
|
4724
|
+
const loop = runRunnerWithLoop(["--companies", "--watch", "--event-push", "--direction", "push", "--hq-root", hqRoot], {
|
|
4725
|
+
runPass,
|
|
4726
|
+
clock: new FakeClock(),
|
|
4727
|
+
createWatcher: () => watcher,
|
|
4728
|
+
sleep: () => new Promise(() => { }),
|
|
4729
|
+
onShutdownSignal: (handler) => {
|
|
4730
|
+
triggerShutdown = handler;
|
|
4731
|
+
return () => { };
|
|
4732
|
+
},
|
|
4733
|
+
});
|
|
4734
|
+
await flushLoopMicrotasks();
|
|
4735
|
+
watcher.emit();
|
|
4736
|
+
await flushLoopMicrotasks();
|
|
4737
|
+
watcher.emit(tinyRel, {
|
|
4738
|
+
paths: new Map([
|
|
4739
|
+
[tiny, tinyRel],
|
|
4740
|
+
...bulk.map((file, index) => [file, `companies/indigo/session-${index}.log`]),
|
|
4741
|
+
]),
|
|
4742
|
+
});
|
|
4743
|
+
await flushLoopMicrotasks(30);
|
|
4744
|
+
// origin/main failure: expected a scoped tiny path while the bulk pass is
|
|
4745
|
+
// held, but #408 kept all four paths in the slow lane.
|
|
4746
|
+
expect(calls.filter((argv) => argv.includes("--scope-path"))).toHaveLength(1);
|
|
4747
|
+
expect(calls.flat().join(" ")).toContain("realtime.md");
|
|
4748
|
+
releaseBulk();
|
|
4749
|
+
await flushLoopMicrotasks(30);
|
|
4750
|
+
expect(calls.filter((argv) => argv.includes("--scope-path"))).toHaveLength(2);
|
|
4751
|
+
expect(calls.flat().join(" ")).toContain("session-0.log");
|
|
4752
|
+
triggerShutdown();
|
|
4753
|
+
await loop;
|
|
4754
|
+
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
4755
|
+
});
|
|
4700
4756
|
it("fast lane: a small receiver pull dispatches during a slow bulk pass", async () => {
|
|
4701
4757
|
const watcher = makeWatcherStub();
|
|
4702
4758
|
let triggerShutdown = () => { };
|
|
@@ -4705,6 +4761,14 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
4705
4761
|
const bulkGate = new Promise((resolve) => { releaseBulk = resolve; });
|
|
4706
4762
|
const order = [];
|
|
4707
4763
|
let fullPasses = 0;
|
|
4764
|
+
const journal = readJournal("acme");
|
|
4765
|
+
journal.files["realtime.md"] = {
|
|
4766
|
+
hash: "sha256:x",
|
|
4767
|
+
size: 16,
|
|
4768
|
+
syncedAt: "2026-08-26T00:00:00.000Z",
|
|
4769
|
+
direction: "down",
|
|
4770
|
+
};
|
|
4771
|
+
writeJournal("acme", journal);
|
|
4708
4772
|
const runPass = vi.fn().mockImplementation(async (passArgv) => {
|
|
4709
4773
|
if (passArgv.includes("--scope-path")) {
|
|
4710
4774
|
order.push("fast-pull");
|
|
@@ -4954,12 +5018,28 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
4954
5018
|
eventTimestamp: "2026-08-26T00:00:00.000Z",
|
|
4955
5019
|
})),
|
|
4956
5020
|
];
|
|
5021
|
+
for (const [slug, prefix, count] of [["acme", "a", 17], ["beta", "b", 16]]) {
|
|
5022
|
+
const journal = readJournal(slug);
|
|
5023
|
+
for (let index = 0; index < count; index++) {
|
|
5024
|
+
journal.files[`${prefix}-${index}.md`] = {
|
|
5025
|
+
hash: "sha256:x",
|
|
5026
|
+
size: 16,
|
|
5027
|
+
syncedAt: "2026-08-26T00:00:00.000Z",
|
|
5028
|
+
direction: "down",
|
|
5029
|
+
};
|
|
5030
|
+
}
|
|
5031
|
+
writeJournal(slug, journal);
|
|
5032
|
+
}
|
|
4957
5033
|
const receiverDrain = receiverSync({
|
|
4958
5034
|
events,
|
|
4959
5035
|
signal: new AbortController().signal,
|
|
4960
5036
|
});
|
|
4961
5037
|
await flushLoopMicrotasks(20);
|
|
4962
|
-
|
|
5038
|
+
// The 33-event mixed delivery peels its first 32 bounded paths into the
|
|
5039
|
+
// fast lane (17 acme + 15 beta); the final beta event remains slow.
|
|
5040
|
+
expect(runPass).toHaveBeenCalledTimes(2);
|
|
5041
|
+
expect(runPass.mock.calls.flat().join(" ")).toContain("a-16.md");
|
|
5042
|
+
expect(runPass.mock.calls.flat().join(" ")).toContain("b-14.md");
|
|
4963
5043
|
releaseBulk();
|
|
4964
5044
|
await receiverDrain;
|
|
4965
5045
|
triggerShutdown();
|
|
@@ -5171,6 +5251,30 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
5171
5251
|
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
5172
5252
|
}
|
|
5173
5253
|
});
|
|
5254
|
+
it("fast lane: keeps a latest large revision in the slow residue and caps 33 small paths", () => {
|
|
5255
|
+
const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-fast-lane-partition-"));
|
|
5256
|
+
const rel = "companies/indigo/revised.md";
|
|
5257
|
+
const absolute = path.join(hqRoot, rel);
|
|
5258
|
+
fs.mkdirSync(path.dirname(absolute), { recursive: true });
|
|
5259
|
+
// The map is the coalescing/affinity boundary: its one value is the latest
|
|
5260
|
+
// revision, so this path cannot appear in both sub-batches.
|
|
5261
|
+
fs.writeFileSync(absolute, Buffer.alloc(DEFAULT_FAST_LANE_MAX_FILE_BYTES + 1));
|
|
5262
|
+
const revised = partitionLocalBatch({ paths: new Map([[absolute, rel]]) });
|
|
5263
|
+
expect(revised.fast).toBeNull();
|
|
5264
|
+
expect(revised.slow?.paths.get(absolute)).toBe(rel);
|
|
5265
|
+
const entries = [];
|
|
5266
|
+
for (let index = 0; index < DEFAULT_FAST_LANE_MAX_FILES + 1; index++) {
|
|
5267
|
+
const file = path.join(hqRoot, `companies/indigo/small-${index}.md`);
|
|
5268
|
+
fs.writeFileSync(file, "x");
|
|
5269
|
+
entries.push([file, `companies/indigo/small-${index}.md`]);
|
|
5270
|
+
}
|
|
5271
|
+
const capped = partitionLocalBatch({ paths: new Map(entries) });
|
|
5272
|
+
expect(capped.fast?.paths.size).toBe(DEFAULT_FAST_LANE_MAX_FILES);
|
|
5273
|
+
// The 33rd path deliberately remains in the slow residue, rather than
|
|
5274
|
+
// creating a second concurrent realtime dispatch.
|
|
5275
|
+
expect(capped.slow?.paths.size).toBe(1);
|
|
5276
|
+
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
5277
|
+
});
|
|
5174
5278
|
it("U07: drains the first watcher event after activation without guarded overlap", async () => {
|
|
5175
5279
|
const watcher = makeWatcherStub();
|
|
5176
5280
|
let triggerShutdown = () => { };
|