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