@indigoai-us/hq-cloud 6.18.32 → 6.18.34
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-delete-snapshot.bench.test.js +13 -3
- package/dist/bin/sync-runner-delete-snapshot.bench.test.js.map +1 -1
- package/dist/bin/sync-runner-watch-loop.d.ts +9 -1
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-loop.js +139 -35
- package/dist/bin/sync-runner-watch-loop.js.map +1 -1
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +8 -4
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +226 -36
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/reindex-async.d.ts +9 -0
- package/dist/cli/reindex-async.d.ts.map +1 -0
- package/dist/cli/reindex-async.js +76 -0
- package/dist/cli/reindex-async.js.map +1 -0
- package/dist/cli/reindex.d.ts +4 -0
- package/dist/cli/reindex.d.ts.map +1 -1
- package/dist/cli/reindex.js +3 -3
- package/dist/cli/reindex.js.map +1 -1
- package/dist/cli/rescue-core.d.ts +2 -0
- package/dist/cli/rescue-core.d.ts.map +1 -1
- package/dist/cli/rescue-core.js +19 -11
- package/dist/cli/rescue-core.js.map +1 -1
- package/dist/cli/rescue-rsync-preflight.test.js +1 -1
- package/dist/cli/rescue-rsync-preflight.test.js.map +1 -1
- package/dist/cli/rescue-source-switch-floor.test.js +39 -2
- package/dist/cli/rescue-source-switch-floor.test.js.map +1 -1
- package/dist/cognito-auth.d.ts +5 -1
- package/dist/cognito-auth.d.ts.map +1 -1
- package/dist/cognito-auth.js +19 -8
- package/dist/cognito-auth.js.map +1 -1
- package/dist/http-keepalive.d.ts +7 -0
- package/dist/http-keepalive.d.ts.map +1 -0
- package/dist/http-keepalive.js +66 -0
- package/dist/http-keepalive.js.map +1 -0
- package/dist/journal.d.ts +1 -0
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +1 -1
- package/dist/journal.js.map +1 -1
- package/dist/machine-auth.test.js +87 -0
- package/dist/machine-auth.test.js.map +1 -1
- package/dist/object-io.d.ts +2 -1
- package/dist/object-io.d.ts.map +1 -1
- package/dist/object-io.js +37 -32
- package/dist/object-io.js.map +1 -1
- package/dist/object-io.test.js +8 -1
- package/dist/object-io.test.js.map +1 -1
- package/dist/s3.d.ts.map +1 -1
- package/dist/s3.js +2 -1
- package/dist/s3.js.map +1 -1
- package/dist/session-log-capture.d.ts +4 -0
- package/dist/session-log-capture.d.ts.map +1 -1
- package/dist/session-log-capture.js +56 -19
- package/dist/session-log-capture.js.map +1 -1
- package/dist/session-log-capture.test.js +79 -0
- package/dist/session-log-capture.test.js.map +1 -1
- package/dist/sync/multipart-uploader.d.ts +2 -0
- package/dist/sync/multipart-uploader.d.ts.map +1 -1
- package/dist/sync/multipart-uploader.js +31 -1
- package/dist/sync/multipart-uploader.js.map +1 -1
- package/dist/sync/multipart-uploader.test.js +17 -1
- package/dist/sync/multipart-uploader.test.js.map +1 -1
- package/dist/sync/realtime-network-integration.test.d.ts +2 -0
- package/dist/sync/realtime-network-integration.test.d.ts.map +1 -0
- package/dist/sync/realtime-network-integration.test.js +83 -0
- package/dist/sync/realtime-network-integration.test.js.map +1 -0
- package/dist/sync/realtime-telemetry.d.ts +20 -3
- package/dist/sync/realtime-telemetry.d.ts.map +1 -1
- package/dist/sync/realtime-telemetry.js +85 -7
- package/dist/sync/realtime-telemetry.js.map +1 -1
- package/dist/sync/realtime-telemetry.test.js +68 -6
- package/dist/sync/realtime-telemetry.test.js.map +1 -1
- package/dist/sync/sync-work.d.ts +1 -0
- package/dist/sync/sync-work.d.ts.map +1 -1
- package/dist/sync/sync-work.js.map +1 -1
- package/dist/vault-client.d.ts.map +1 -1
- package/dist/vault-client.js +8 -2
- package/dist/vault-client.js.map +1 -1
- package/dist/watcher-replan-sweep.test.js +21 -3
- package/dist/watcher-replan-sweep.test.js.map +1 -1
- package/dist/watcher.d.ts +2 -0
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +166 -50
- package/dist/watcher.js.map +1 -1
- package/dist/watcher.test.js +59 -5
- package/dist/watcher.test.js.map +1 -1
- package/dist/watcher.warmup-cancel.test.d.ts +2 -0
- package/dist/watcher.warmup-cancel.test.d.ts.map +1 -0
- package/dist/watcher.warmup-cancel.test.js +59 -0
- package/dist/watcher.warmup-cancel.test.js.map +1 -0
- package/dist/watcher.warmup-responsive.test.d.ts +2 -0
- package/dist/watcher.warmup-responsive.test.d.ts.map +1 -0
- package/dist/watcher.warmup-responsive.test.js +62 -0
- package/dist/watcher.warmup-responsive.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -20,10 +20,10 @@ import { AUTH_WAIT_INITIAL_RECHECK_MS, AUTH_WAIT_RESUME_MESSAGE, AUTH_WAIT_START
|
|
|
20
20
|
import { PresignObjectIO, S3SdkObjectIO } from "../object-io.js";
|
|
21
21
|
import { BandwidthGovernor, setBandwidthGovernorForTesting, } from "../bandwidth.js";
|
|
22
22
|
import { POST_SYNC_FLAG_KEYS, } from "./sync-runner-post-sync-flags.js";
|
|
23
|
-
import { FakeClock, TreeWatcher, } from "../watcher.js";
|
|
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";
|
|
23
|
+
import { FakeClock, TreeWatcher, createWatchPathFilter, } from "../watcher.js";
|
|
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, isLargeLocalUpload, localUploadSizeClass, isChoreLocalPush, isSessionLogsPushOnlyPath, isFastReceiverBatch, partitionLocalBatch, journalEntriesForWatcherDelete, resolveEventBatchLimit, resolveFullReconcileMs, resolveWatchPollMs, CHORE_STARVATION_CAP_MS, CooperativePassScheduler, REALTIME_ADMISSION_DEADLINE_MS, REALTIME_DRAIN_BURST_MS, REALTIME_WORKER_OCCUPANCY_CAP, REALTIME_LARGE_UPLOAD_THRESHOLD_BYTES, 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, readJournalForPass, readJournalScoped, readJournalScopedAsync, writeJournal, } from "../journal.js";
|
|
26
|
+
import { AreaLedgerMigration, JournalStore, getJournalPath, 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";
|
|
@@ -3145,11 +3145,12 @@ describe("all-complete aggregate", () => {
|
|
|
3145
3145
|
true,
|
|
3146
3146
|
]);
|
|
3147
3147
|
expect(reindex).toHaveBeenCalledTimes(1);
|
|
3148
|
-
// Session logs are
|
|
3148
|
+
// Session logs are captured only after ten minutes without source changes.
|
|
3149
3149
|
expect(reindex).toHaveBeenCalledWith({
|
|
3150
3150
|
repoRoot: "/tmp/fake-hq",
|
|
3151
3151
|
skipLock: true,
|
|
3152
|
-
|
|
3152
|
+
sessionLogQuietForMs: 600_000,
|
|
3153
|
+
sessionLogMaxStalenessMs: 3_600_000,
|
|
3153
3154
|
});
|
|
3154
3155
|
});
|
|
3155
3156
|
it("gates qmd to indexable knowledge and project changes and propagates hq-relative paths", async () => {
|
|
@@ -6360,6 +6361,8 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
6360
6361
|
writeJournal("personal", legacy);
|
|
6361
6362
|
const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-warmup-rows-"));
|
|
6362
6363
|
fs.mkdirSync(path.join(hqRoot, "companies", "indigo", "repos", "x"), { recursive: true });
|
|
6364
|
+
fs.mkdirSync(path.join(hqRoot, "companies", "bad-json"), { recursive: true });
|
|
6365
|
+
fs.writeFileSync(getJournalPath("bad-json"), "{corrupt legacy journal");
|
|
6363
6366
|
fs.writeFileSync(path.join(hqRoot, "companies", "indigo", "repos", "x", "a.md"), "keep local repository checkout");
|
|
6364
6367
|
const watcher = makeBatchWatcherStub();
|
|
6365
6368
|
let markReady = () => { };
|
|
@@ -6735,7 +6738,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
6735
6738
|
});
|
|
6736
6739
|
await expect(superseded).resolves.toBeUndefined();
|
|
6737
6740
|
expect(runPass.mock.calls.filter(([argv]) => argv.includes("superseded.md"))).toHaveLength(0);
|
|
6738
|
-
expect(stderr.mock.calls.map(([chunk]) => String(chunk)).join("")).toContain('"event":"sync.realtime.timing","schemaVersion":
|
|
6741
|
+
expect(stderr.mock.calls.map(([chunk]) => String(chunk)).join("")).toContain('"event":"sync.realtime.timing","schemaVersion":5,"source":"receiver","direction":"pull","sizeClass":"unknown","outcome":"refused"');
|
|
6739
6742
|
releaseHeld();
|
|
6740
6743
|
await heldReceiverWork;
|
|
6741
6744
|
await replacement;
|
|
@@ -9073,6 +9076,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
9073
9076
|
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
9074
9077
|
});
|
|
9075
9078
|
it("U07: drains the first watcher event after activation without guarded overlap", async () => {
|
|
9079
|
+
const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-watch-first-event-"));
|
|
9076
9080
|
const watcher = makeWatcherStub();
|
|
9077
9081
|
let triggerShutdown = () => { };
|
|
9078
9082
|
let releaseFirstPass = () => { };
|
|
@@ -9097,7 +9101,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
9097
9101
|
"--poll-remote-ms",
|
|
9098
9102
|
"600000",
|
|
9099
9103
|
"--hq-root",
|
|
9100
|
-
|
|
9104
|
+
hqRoot,
|
|
9101
9105
|
], {
|
|
9102
9106
|
runPass,
|
|
9103
9107
|
createWatcher: () => watcher,
|
|
@@ -9114,13 +9118,17 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
9114
9118
|
expect(watcher.started).toBe(true);
|
|
9115
9119
|
// Activation happens synchronously after the maintenance-bearing pass,
|
|
9116
9120
|
// before the next wait is registered. Its first event must still drain.
|
|
9121
|
+
const eventPath = path.join(hqRoot, "companies/indigo/pre-registration.md");
|
|
9122
|
+
fs.mkdirSync(path.dirname(eventPath), { recursive: true });
|
|
9123
|
+
fs.writeFileSync(eventPath, "small watcher edit");
|
|
9117
9124
|
watcher.emit("companies/indigo/pre-registration.md");
|
|
9118
|
-
await flushLoopMicrotasks();
|
|
9125
|
+
await flushLoopMicrotasks(32);
|
|
9119
9126
|
expect(spans).toHaveLength(3);
|
|
9120
9127
|
expect(spans[1].enteredAt >= spans[0].exitedAt).toBe(true);
|
|
9121
9128
|
expect(spans[2].enteredAt >= spans[1].exitedAt).toBe(true);
|
|
9122
9129
|
triggerShutdown();
|
|
9123
9130
|
await loop;
|
|
9131
|
+
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
9124
9132
|
});
|
|
9125
9133
|
it("F05: guard-held watcher changes stay accumulated, then drain immediately after the guarded pass", async () => {
|
|
9126
9134
|
const watcher = makeWatcherStub();
|
|
@@ -9343,7 +9351,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
9343
9351
|
return () => { };
|
|
9344
9352
|
},
|
|
9345
9353
|
});
|
|
9346
|
-
await
|
|
9354
|
+
await waitForPollSleep(sleep);
|
|
9347
9355
|
sleep.tick();
|
|
9348
9356
|
await flushLoopMicrotasks();
|
|
9349
9357
|
// The transient pass did NOT end the loop — it ran again, and neither
|
|
@@ -9385,6 +9393,7 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
9385
9393
|
},
|
|
9386
9394
|
});
|
|
9387
9395
|
await flushLoopMicrotasks();
|
|
9396
|
+
await waitForPollSleep(sleep);
|
|
9388
9397
|
sleep.tick();
|
|
9389
9398
|
await flushLoopMicrotasks();
|
|
9390
9399
|
expect(routedArgv).toEqual([
|
|
@@ -9924,6 +9933,152 @@ describe("runRunnerWithLoop — U58 realtime scheduler guard", () => {
|
|
|
9924
9933
|
});
|
|
9925
9934
|
});
|
|
9926
9935
|
describe("CooperativePassScheduler", () => {
|
|
9936
|
+
it("identifies session-log backups for the chore-only route", () => {
|
|
9937
|
+
const transcript = "workspace/.session-logs/codex/2026/10/01/live.jsonl";
|
|
9938
|
+
expect(isSessionLogsPushOnlyPath(transcript)).toBe(true);
|
|
9939
|
+
expect(isSessionLogsPushOnlyPath("workspace/notes.md")).toBe(false);
|
|
9940
|
+
expect(isChoreLocalPush([transcript], false)).toBe(true);
|
|
9941
|
+
expect(isChoreLocalPush(["workspace/notes.md"], false)).toBe(false);
|
|
9942
|
+
expect(isChoreLocalPush(["workspace/notes.md"], true)).toBe(true);
|
|
9943
|
+
});
|
|
9944
|
+
it("reserves a realtime occupant for each transfer direction", async () => {
|
|
9945
|
+
const scheduler = new CooperativePassScheduler(() => 0);
|
|
9946
|
+
const started = [];
|
|
9947
|
+
let releasePush1 = () => { };
|
|
9948
|
+
const push1Gate = new Promise((resolve) => { releasePush1 = resolve; });
|
|
9949
|
+
let releasePush2 = () => { };
|
|
9950
|
+
const push2Gate = new Promise((resolve) => { releasePush2 = resolve; });
|
|
9951
|
+
let releaseCheckpoint = () => { };
|
|
9952
|
+
const checkpointReady = new Promise((resolve) => { releaseCheckpoint = resolve; });
|
|
9953
|
+
const chore = scheduler.submit(async ({ checkpoint }) => {
|
|
9954
|
+
await checkpointReady;
|
|
9955
|
+
await checkpoint();
|
|
9956
|
+
return 0;
|
|
9957
|
+
}, "chore");
|
|
9958
|
+
await Promise.resolve();
|
|
9959
|
+
const push1 = scheduler.submit(async () => {
|
|
9960
|
+
started.push("push-1");
|
|
9961
|
+
await push1Gate;
|
|
9962
|
+
return 0;
|
|
9963
|
+
}, "realtime", "slow", false, undefined, false, false, "push");
|
|
9964
|
+
releaseCheckpoint();
|
|
9965
|
+
await new Promise((resolve) => setTimeout(resolve, 2));
|
|
9966
|
+
const push2 = scheduler.submit(async () => {
|
|
9967
|
+
started.push("push-2");
|
|
9968
|
+
await push2Gate;
|
|
9969
|
+
return 0;
|
|
9970
|
+
}, "realtime", "slow", false, undefined, false, true, "push");
|
|
9971
|
+
await new Promise((resolve) => setTimeout(resolve, 2));
|
|
9972
|
+
const pull = scheduler.submit(async () => {
|
|
9973
|
+
started.push("pull");
|
|
9974
|
+
return 0;
|
|
9975
|
+
}, "realtime", "slow", false, undefined, false, true, "pull");
|
|
9976
|
+
try {
|
|
9977
|
+
await new Promise((resolve) => setTimeout(resolve, 2));
|
|
9978
|
+
expect(started).toContain("pull");
|
|
9979
|
+
}
|
|
9980
|
+
finally {
|
|
9981
|
+
releasePush1();
|
|
9982
|
+
releasePush2();
|
|
9983
|
+
await Promise.all([chore, push1, push2, pull]);
|
|
9984
|
+
}
|
|
9985
|
+
});
|
|
9986
|
+
it("classifies uploads at the multipart part-size boundary", () => {
|
|
9987
|
+
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "hq-large-upload-class-"));
|
|
9988
|
+
try {
|
|
9989
|
+
const small = path.join(directory, "small.txt");
|
|
9990
|
+
const large = path.join(directory, "large.bin");
|
|
9991
|
+
const missing = path.join(directory, "not-created-yet.txt");
|
|
9992
|
+
fs.writeFileSync(small, "small edit");
|
|
9993
|
+
const fd = fs.openSync(large, "w");
|
|
9994
|
+
fs.ftruncateSync(fd, REALTIME_LARGE_UPLOAD_THRESHOLD_BYTES);
|
|
9995
|
+
fs.closeSync(fd);
|
|
9996
|
+
expect(REALTIME_LARGE_UPLOAD_THRESHOLD_BYTES).toBe(8 * 1024 * 1024);
|
|
9997
|
+
expect(isLargeLocalUpload([small])).toBe(false);
|
|
9998
|
+
expect(isLargeLocalUpload([large])).toBe(true);
|
|
9999
|
+
expect(localUploadSizeClass([small])).toBe("small");
|
|
10000
|
+
expect(localUploadSizeClass([large])).toBe("large");
|
|
10001
|
+
expect(localUploadSizeClass([missing])).toBe("unknown");
|
|
10002
|
+
expect(isLargeLocalUpload([missing])).toBe(true);
|
|
10003
|
+
}
|
|
10004
|
+
finally {
|
|
10005
|
+
fs.rmSync(directory, { recursive: true, force: true });
|
|
10006
|
+
}
|
|
10007
|
+
});
|
|
10008
|
+
it("keeps small pulls under 5s during a 60s fake S3 PUT despite excluded path floods", async () => {
|
|
10009
|
+
vi.useFakeTimers();
|
|
10010
|
+
const watchRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-realtime-excluded-flood-"));
|
|
10011
|
+
try {
|
|
10012
|
+
const scheduler = new CooperativePassScheduler(() => Date.now());
|
|
10013
|
+
const shouldWatch = createWatchPathFilter(watchRoot, false);
|
|
10014
|
+
const fakeS3 = new Map();
|
|
10015
|
+
const localFiles = new Map();
|
|
10016
|
+
const mirrorJournal = new Map();
|
|
10017
|
+
const remoteChanges = Array.from({ length: 20 }, (_, index) => [`remote-${index}`, `value-${index}`]);
|
|
10018
|
+
for (const [key, value] of remoteChanges)
|
|
10019
|
+
fakeS3.set(key, value);
|
|
10020
|
+
let markPutStarted = () => { };
|
|
10021
|
+
const putStarted = new Promise((resolve) => { markPutStarted = resolve; });
|
|
10022
|
+
let largePutComplete = false;
|
|
10023
|
+
const upload = scheduler.submit(async ({ checkpoint }) => {
|
|
10024
|
+
const transfer = await checkpoint.runChoreNetworkTransfer(async () => {
|
|
10025
|
+
markPutStarted();
|
|
10026
|
+
await new Promise((resolve) => { setTimeout(resolve, 60_000); });
|
|
10027
|
+
fakeS3.set("large-upload", "large payload");
|
|
10028
|
+
return "large-upload";
|
|
10029
|
+
});
|
|
10030
|
+
largePutComplete = true;
|
|
10031
|
+
mirrorJournal.set(transfer.value, fakeS3.get(transfer.value) ?? "");
|
|
10032
|
+
transfer.complete();
|
|
10033
|
+
return 0;
|
|
10034
|
+
}, "chore");
|
|
10035
|
+
await putStarted;
|
|
10036
|
+
for (let index = 0; index < 1_000; index += 1) {
|
|
10037
|
+
const excluded = path.join(watchRoot, "workspace", "worktrees", `lane-${index}`, "output.ts");
|
|
10038
|
+
if (shouldWatch(excluded)) {
|
|
10039
|
+
void scheduler.submit(async () => 0, "realtime", "slow", false, undefined, false, true, "push", "small");
|
|
10040
|
+
}
|
|
10041
|
+
}
|
|
10042
|
+
expect(scheduler.activeRealtimeWorkerCount).toBe(0);
|
|
10043
|
+
expect(scheduler.pendingLaneCount("slow")).toBe(0);
|
|
10044
|
+
let allPullsSettled = false;
|
|
10045
|
+
const pullWaits = remoteChanges.map(([key]) => {
|
|
10046
|
+
const arrivedAt = Date.now();
|
|
10047
|
+
return scheduler.submit(async () => {
|
|
10048
|
+
const value = fakeS3.get(key);
|
|
10049
|
+
if (value === undefined)
|
|
10050
|
+
throw new Error(`fake S3 object missing: ${key}`);
|
|
10051
|
+
localFiles.set(key, value);
|
|
10052
|
+
mirrorJournal.set(key, value);
|
|
10053
|
+
return Date.now() - arrivedAt;
|
|
10054
|
+
}, "realtime", "slow", false, undefined, false, true, "pull", "small");
|
|
10055
|
+
});
|
|
10056
|
+
const allPullWaits = Promise.all(pullWaits).then((waits) => {
|
|
10057
|
+
allPullsSettled = true;
|
|
10058
|
+
return waits;
|
|
10059
|
+
});
|
|
10060
|
+
await vi.advanceTimersByTimeAsync(5_000);
|
|
10061
|
+
const pullsMetWorkerBound = allPullsSettled;
|
|
10062
|
+
if (!pullsMetWorkerBound)
|
|
10063
|
+
await vi.advanceTimersByTimeAsync(55_000);
|
|
10064
|
+
const waits = await allPullWaits;
|
|
10065
|
+
expect(pullsMetWorkerBound).toBe(true);
|
|
10066
|
+
expect(waits.every((waitMs) => waitMs < 5_000)).toBe(true);
|
|
10067
|
+
expect(localFiles.size).toBe(remoteChanges.length);
|
|
10068
|
+
expect(largePutComplete).toBe(false);
|
|
10069
|
+
expect(mirrorJournal.has("large-upload")).toBe(false);
|
|
10070
|
+
if (!largePutComplete)
|
|
10071
|
+
await vi.advanceTimersByTimeAsync(55_000);
|
|
10072
|
+
await upload;
|
|
10073
|
+
expect(largePutComplete).toBe(true);
|
|
10074
|
+
expect(fakeS3.get("large-upload")).toBe("large payload");
|
|
10075
|
+
expect(mirrorJournal.get("large-upload")).toBe("large payload");
|
|
10076
|
+
}
|
|
10077
|
+
finally {
|
|
10078
|
+
vi.useRealTimers();
|
|
10079
|
+
fs.rmSync(watchRoot, { recursive: true, force: true });
|
|
10080
|
+
}
|
|
10081
|
+
});
|
|
9927
10082
|
it("admits realtime during payload I/O and fences the next chore transfer until the result is committed", async () => {
|
|
9928
10083
|
const scheduler = new CooperativePassScheduler(() => 0);
|
|
9929
10084
|
const order = [];
|
|
@@ -10581,6 +10736,7 @@ describe("CooperativePassScheduler", () => {
|
|
|
10581
10736
|
});
|
|
10582
10737
|
describe("first-tick realtime yield", () => {
|
|
10583
10738
|
it("commits a watcher push at the first chore checkpoint without a wall-clock wait", async () => {
|
|
10739
|
+
const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-first-tick-realtime-"));
|
|
10584
10740
|
const previousCoordinator = process.env.HQ_SYNC_PARALLEL_COORDINATOR;
|
|
10585
10741
|
const previousInterval = process.env.HQ_SYNC_FULL_RECONCILE_MS;
|
|
10586
10742
|
process.env.HQ_SYNC_PARALLEL_COORDINATOR = "1";
|
|
@@ -10635,7 +10791,7 @@ describe("first-tick realtime yield", () => {
|
|
|
10635
10791
|
"--direction",
|
|
10636
10792
|
"both",
|
|
10637
10793
|
"--hq-root",
|
|
10638
|
-
|
|
10794
|
+
hqRoot,
|
|
10639
10795
|
"--poll-remote-ms",
|
|
10640
10796
|
"60000",
|
|
10641
10797
|
], {
|
|
@@ -10651,9 +10807,12 @@ describe("first-tick realtime yield", () => {
|
|
|
10651
10807
|
});
|
|
10652
10808
|
try {
|
|
10653
10809
|
await flushLoopMicrotasks(40);
|
|
10810
|
+
const notePath = path.join(hqRoot, "companies/indigo/note.md");
|
|
10811
|
+
fs.mkdirSync(path.dirname(notePath), { recursive: true });
|
|
10812
|
+
fs.writeFileSync(notePath, "small watcher edit");
|
|
10654
10813
|
watcher.emit("companies/indigo/note.md", {
|
|
10655
10814
|
paths: new Map([
|
|
10656
|
-
[
|
|
10815
|
+
[notePath, "companies/indigo/note.md"],
|
|
10657
10816
|
]),
|
|
10658
10817
|
});
|
|
10659
10818
|
await new Promise((resolve) => setImmediate(resolve));
|
|
@@ -10687,6 +10846,7 @@ describe("first-tick realtime yield", () => {
|
|
|
10687
10846
|
delete process.env.HQ_SYNC_FULL_RECONCILE_MS;
|
|
10688
10847
|
else
|
|
10689
10848
|
process.env.HQ_SYNC_FULL_RECONCILE_MS = previousInterval;
|
|
10849
|
+
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
10690
10850
|
}
|
|
10691
10851
|
});
|
|
10692
10852
|
});
|
|
@@ -11844,6 +12004,10 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
11844
12004
|
await loop;
|
|
11845
12005
|
});
|
|
11846
12006
|
it("push-only scoped ticks run the scoped push and no pull leg", async () => {
|
|
12007
|
+
const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-push-only-tick-"));
|
|
12008
|
+
const file = path.join(hqRoot, "companies/indigo/knowledge/a.md");
|
|
12009
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
12010
|
+
fs.writeFileSync(file, "small watcher edit");
|
|
11847
12011
|
const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
|
|
11848
12012
|
const { loop, watcher, tick, shutdown } = startDrainLoopWithArgv(runPass, [
|
|
11849
12013
|
"--companies",
|
|
@@ -11852,15 +12016,15 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
11852
12016
|
"--direction",
|
|
11853
12017
|
"push",
|
|
11854
12018
|
"--hq-root",
|
|
11855
|
-
|
|
12019
|
+
hqRoot,
|
|
11856
12020
|
"--poll-remote-ms",
|
|
11857
12021
|
"60000",
|
|
11858
12022
|
]);
|
|
11859
|
-
await flushLoopMicrotasks();
|
|
12023
|
+
await flushLoopMicrotasks(30);
|
|
11860
12024
|
runPass.mockClear();
|
|
11861
12025
|
watcher.emit("companies/indigo/knowledge/a.md");
|
|
11862
12026
|
tick();
|
|
11863
|
-
await flushLoopMicrotasks();
|
|
12027
|
+
await flushLoopMicrotasks(30);
|
|
11864
12028
|
expect(runPass.mock.calls.map((call) => call[0])).toEqual([
|
|
11865
12029
|
[
|
|
11866
12030
|
"--company",
|
|
@@ -11870,12 +12034,13 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
11870
12034
|
"--scope-path",
|
|
11871
12035
|
"knowledge/a.md",
|
|
11872
12036
|
"--hq-root",
|
|
11873
|
-
|
|
12037
|
+
hqRoot,
|
|
11874
12038
|
],
|
|
11875
12039
|
]);
|
|
11876
12040
|
shutdown();
|
|
11877
12041
|
tick();
|
|
11878
12042
|
await loop;
|
|
12043
|
+
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
11879
12044
|
});
|
|
11880
12045
|
it("overflow and an elapsed hour run a full unscoped reconcile", async () => {
|
|
11881
12046
|
const clockState = { now: 0 };
|
|
@@ -12141,11 +12306,14 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
12141
12306
|
}
|
|
12142
12307
|
});
|
|
12143
12308
|
it("a 5,000-path batch under the shipped default stays on the targeted path", async () => {
|
|
12309
|
+
const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-5000-path-batch-"));
|
|
12310
|
+
const argv = [...watchArgv];
|
|
12311
|
+
argv[argv.indexOf("/tmp/hq")] = hqRoot;
|
|
12144
12312
|
const watcher = makeBatchWatcherStub();
|
|
12145
12313
|
const sleep = makeSteppableSleep();
|
|
12146
12314
|
let triggerShutdown = () => { };
|
|
12147
12315
|
const runPass = vi.fn().mockResolvedValue(completedPassOutcome());
|
|
12148
|
-
const loop = runRunnerWithLoop(
|
|
12316
|
+
const loop = runRunnerWithLoop(argv, {
|
|
12149
12317
|
runPass,
|
|
12150
12318
|
clock: new FakeClock(),
|
|
12151
12319
|
createWatcher: () => watcher,
|
|
@@ -12158,13 +12326,16 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
12158
12326
|
await flushLoopMicrotasks();
|
|
12159
12327
|
runPass.mockClear();
|
|
12160
12328
|
watcher.emit("companies/indigo/a.md", {
|
|
12161
|
-
paths: new Map(Array.from({ length: 5_000 }, (_, index) =>
|
|
12162
|
-
|
|
12163
|
-
|
|
12164
|
-
|
|
12329
|
+
paths: new Map(Array.from({ length: 5_000 }, (_, index) => {
|
|
12330
|
+
const relativePath = `companies/indigo/${index}.md`;
|
|
12331
|
+
const absolutePath = path.join(hqRoot, relativePath);
|
|
12332
|
+
fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
|
|
12333
|
+
fs.writeFileSync(absolutePath, "x");
|
|
12334
|
+
return [absolutePath, relativePath];
|
|
12335
|
+
})),
|
|
12165
12336
|
});
|
|
12166
12337
|
sleep.tick();
|
|
12167
|
-
await
|
|
12338
|
+
await waitForCondition("5,000-path batch to split into two scoped passes", () => runPass.mock.calls.length === 2);
|
|
12168
12339
|
const calls = runPass.mock.calls.map((call) => call[0]);
|
|
12169
12340
|
expect(calls).not.toContainEqual(fullArgv);
|
|
12170
12341
|
expect(calls).toHaveLength(2);
|
|
@@ -12178,6 +12349,7 @@ describe("runRunnerWithLoop — accumulate-and-drain scoped push", () => {
|
|
|
12178
12349
|
expect(calls[1]?.filter((arg) => arg === "--scope-path")).toHaveLength(5_000);
|
|
12179
12350
|
triggerShutdown();
|
|
12180
12351
|
await loop;
|
|
12352
|
+
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
12181
12353
|
});
|
|
12182
12354
|
it("a batch above the shipped 10,000-path default stays on chunked targeted passes before the interval", async () => {
|
|
12183
12355
|
const saved = process.env.HQ_SYNC_FULL_RECONCILE_MS;
|
|
@@ -13835,12 +14007,13 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
13835
14007
|
]]),
|
|
13836
14008
|
});
|
|
13837
14009
|
tick();
|
|
13838
|
-
await microtasks();
|
|
14010
|
+
await microtasks(30);
|
|
13839
14011
|
expect(publishBatch).not.toHaveBeenCalled();
|
|
13840
14012
|
shutdown();
|
|
13841
14013
|
await loop;
|
|
13842
14014
|
});
|
|
13843
14015
|
it("parallel coordinator publishes only durable watcher paths after a subset is superseded", async () => {
|
|
14016
|
+
const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-subset-superseded-"));
|
|
13844
14017
|
const previousCoordinator = process.env.HQ_SYNC_PARALLEL_COORDINATOR;
|
|
13845
14018
|
process.env.HQ_SYNC_PARALLEL_COORDINATOR = "1";
|
|
13846
14019
|
const publishBatch = vi.fn();
|
|
@@ -13859,36 +14032,42 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
13859
14032
|
});
|
|
13860
14033
|
const { loop, watcher, clock, waitForPoll, shutdown } = runLoop({
|
|
13861
14034
|
claims: EMAIL_CLAIMS,
|
|
14035
|
+
hqRoot,
|
|
13862
14036
|
startEventSync,
|
|
13863
14037
|
runPass,
|
|
13864
14038
|
});
|
|
13865
14039
|
const heldPath = "companies/indigo/held.md";
|
|
13866
14040
|
const supersededPath = "companies/indigo/superseded.md";
|
|
13867
14041
|
const durablePath = "companies/indigo/durable.md";
|
|
14042
|
+
for (const relativePath of [heldPath, supersededPath, durablePath]) {
|
|
14043
|
+
const absolutePath = path.join(hqRoot, relativePath);
|
|
14044
|
+
fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
|
|
14045
|
+
fs.writeFileSync(absolutePath, "small watcher edit");
|
|
14046
|
+
}
|
|
13868
14047
|
try {
|
|
13869
14048
|
await waitForPoll();
|
|
13870
14049
|
await waitForObservable("watcher subscription to become active", () => watcher.started && watcher.listenerCount() > 0);
|
|
13871
14050
|
runPass.mockClear();
|
|
13872
14051
|
watcher.emit(heldPath, {
|
|
13873
|
-
paths: new Map([[
|
|
13874
|
-
changes: new Map([[
|
|
14052
|
+
paths: new Map([[path.join(hqRoot, heldPath), heldPath]]),
|
|
14053
|
+
changes: new Map([[path.join(hqRoot, heldPath), { kind: "change" }]]),
|
|
13875
14054
|
});
|
|
13876
14055
|
clock.advance(0);
|
|
13877
14056
|
await waitForObservable("held watcher push to start", () => runPass.mock.calls.some(([argv]) => argv.includes("held.md")));
|
|
13878
14057
|
watcher.emit(supersededPath, {
|
|
13879
14058
|
paths: new Map([
|
|
13880
|
-
[
|
|
13881
|
-
[
|
|
14059
|
+
[path.join(hqRoot, supersededPath), supersededPath],
|
|
14060
|
+
[path.join(hqRoot, durablePath), durablePath],
|
|
13882
14061
|
]),
|
|
13883
14062
|
changes: new Map([
|
|
13884
|
-
[
|
|
13885
|
-
[
|
|
14063
|
+
[path.join(hqRoot, supersededPath), { kind: "change" }],
|
|
14064
|
+
[path.join(hqRoot, durablePath), { kind: "change" }],
|
|
13886
14065
|
]),
|
|
13887
14066
|
});
|
|
13888
14067
|
clock.advance(0);
|
|
13889
14068
|
watcher.emit(supersededPath, {
|
|
13890
|
-
paths: new Map([[
|
|
13891
|
-
changes: new Map([[
|
|
14069
|
+
paths: new Map([[path.join(hqRoot, supersededPath), supersededPath]]),
|
|
14070
|
+
changes: new Map([[path.join(hqRoot, supersededPath), { kind: "change" }]]),
|
|
13892
14071
|
});
|
|
13893
14072
|
clock.advance(0);
|
|
13894
14073
|
releaseHeld();
|
|
@@ -13907,9 +14086,11 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
13907
14086
|
delete process.env.HQ_SYNC_PARALLEL_COORDINATOR;
|
|
13908
14087
|
else
|
|
13909
14088
|
process.env.HQ_SYNC_PARALLEL_COORDINATOR = previousCoordinator;
|
|
14089
|
+
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
13910
14090
|
}
|
|
13911
14091
|
});
|
|
13912
14092
|
it("parallel coordinator publishes nothing for a wholly superseded watcher batch", async () => {
|
|
14093
|
+
const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-wholly-superseded-"));
|
|
13913
14094
|
const previousCoordinator = process.env.HQ_SYNC_PARALLEL_COORDINATOR;
|
|
13914
14095
|
process.env.HQ_SYNC_PARALLEL_COORDINATOR = "1";
|
|
13915
14096
|
const publishBatch = vi.fn();
|
|
@@ -13928,26 +14109,32 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
13928
14109
|
});
|
|
13929
14110
|
const { loop, watcher, clock, shutdown } = runLoop({
|
|
13930
14111
|
claims: EMAIL_CLAIMS,
|
|
14112
|
+
hqRoot,
|
|
13931
14113
|
startEventSync,
|
|
13932
14114
|
runPass,
|
|
13933
14115
|
});
|
|
13934
14116
|
const heldPath = "companies/indigo/held.md";
|
|
13935
14117
|
const supersededPath = "companies/indigo/wholly-superseded.md";
|
|
14118
|
+
const heldAbsolutePath = path.join(hqRoot, heldPath);
|
|
14119
|
+
fs.mkdirSync(path.dirname(heldAbsolutePath), { recursive: true });
|
|
14120
|
+
fs.writeFileSync(heldAbsolutePath, "small watcher edit");
|
|
13936
14121
|
try {
|
|
13937
14122
|
await microtasks(30);
|
|
13938
14123
|
runPass.mockClear();
|
|
13939
14124
|
watcher.emit(heldPath, {
|
|
13940
|
-
paths: new Map([[
|
|
14125
|
+
paths: new Map([[path.join(hqRoot, heldPath), heldPath]]),
|
|
13941
14126
|
});
|
|
13942
14127
|
clock.advance(0);
|
|
13943
14128
|
await microtasks(30);
|
|
13944
14129
|
watcher.emit(supersededPath, {
|
|
13945
|
-
paths: new Map([[
|
|
14130
|
+
paths: new Map([[path.join(hqRoot, supersededPath), supersededPath]]),
|
|
14131
|
+
changes: new Map([[path.join(hqRoot, supersededPath), { kind: "unlink" }]]),
|
|
13946
14132
|
});
|
|
13947
14133
|
clock.advance(0);
|
|
13948
14134
|
await microtasks(30);
|
|
13949
14135
|
watcher.emit(supersededPath, {
|
|
13950
|
-
paths: new Map([[
|
|
14136
|
+
paths: new Map([[path.join(hqRoot, supersededPath), supersededPath]]),
|
|
14137
|
+
changes: new Map([[path.join(hqRoot, supersededPath), { kind: "unlink" }]]),
|
|
13951
14138
|
});
|
|
13952
14139
|
clock.advance(0);
|
|
13953
14140
|
await microtasks(30);
|
|
@@ -13967,7 +14154,9 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
13967
14154
|
// overlapping batch is reconciled. It has no accepted delete result,
|
|
13968
14155
|
// so it must not publish an event.
|
|
13969
14156
|
expect(supersededPushes).toHaveLength(1);
|
|
13970
|
-
|
|
14157
|
+
const publishedPaths = publishBatch.mock.calls.flatMap(([batch]) => [...batch.paths.values()]);
|
|
14158
|
+
expect(publishedPaths).toContain(heldPath);
|
|
14159
|
+
expect(publishedPaths).not.toContain(supersededPath);
|
|
13971
14160
|
let loopExited = false;
|
|
13972
14161
|
void loop.then(() => { loopExited = true; });
|
|
13973
14162
|
await microtasks();
|
|
@@ -13982,6 +14171,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
13982
14171
|
delete process.env.HQ_SYNC_PARALLEL_COORDINATOR;
|
|
13983
14172
|
else
|
|
13984
14173
|
process.env.HQ_SYNC_PARALLEL_COORDINATOR = previousCoordinator;
|
|
14174
|
+
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
13985
14175
|
}
|
|
13986
14176
|
});
|
|
13987
14177
|
it("excludes refused descendants while publishing accepted unlinkDir deletes", async () => {
|
|
@@ -14029,7 +14219,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
14029
14219
|
changes: new Map([[absolutePath, { kind: "unlinkDir" }]]),
|
|
14030
14220
|
});
|
|
14031
14221
|
tick();
|
|
14032
|
-
await microtasks();
|
|
14222
|
+
await microtasks(30);
|
|
14033
14223
|
expect(publishBatch).toHaveBeenCalledTimes(1);
|
|
14034
14224
|
expect([...publishBatch.mock.calls[0][0].paths.values()]).toEqual([
|
|
14035
14225
|
"companies/indigo/archive/accepted.md",
|
|
@@ -14078,7 +14268,7 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
14078
14268
|
changes: new Map([[absoluteDirectory, { kind: "unlinkDir" }]]),
|
|
14079
14269
|
});
|
|
14080
14270
|
tick();
|
|
14081
|
-
await microtasks();
|
|
14271
|
+
await microtasks(30);
|
|
14082
14272
|
expect(publishBatch).toHaveBeenCalledTimes(1);
|
|
14083
14273
|
expect([...publishBatch.mock.calls[0][0].paths.values()]).toEqual([
|
|
14084
14274
|
"companies/indigo/archive/first.md",
|