@indigoai-us/hq-cloud 6.11.11 → 6.11.13
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-company.d.ts +35 -0
- package/dist/bin/sync-runner-company.d.ts.map +1 -0
- package/dist/bin/sync-runner-company.js +290 -0
- package/dist/bin/sync-runner-company.js.map +1 -0
- package/dist/bin/sync-runner-events.d.ts +12 -0
- package/dist/bin/sync-runner-events.d.ts.map +1 -0
- package/dist/bin/sync-runner-events.js +12 -0
- package/dist/bin/sync-runner-events.js.map +1 -0
- package/dist/bin/sync-runner-planning.d.ts +53 -0
- package/dist/bin/sync-runner-planning.d.ts.map +1 -0
- package/dist/bin/sync-runner-planning.js +59 -0
- package/dist/bin/sync-runner-planning.js.map +1 -0
- package/dist/bin/sync-runner-rollup.d.ts +24 -0
- package/dist/bin/sync-runner-rollup.d.ts.map +1 -0
- package/dist/bin/sync-runner-rollup.js +46 -0
- package/dist/bin/sync-runner-rollup.js.map +1 -0
- package/dist/bin/sync-runner-telemetry.d.ts +5 -0
- package/dist/bin/sync-runner-telemetry.d.ts.map +1 -0
- package/dist/bin/sync-runner-telemetry.js +5 -0
- package/dist/bin/sync-runner-telemetry.js.map +1 -0
- package/dist/bin/sync-runner-watch-loop.d.ts +17 -0
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -0
- package/dist/bin/sync-runner-watch-loop.js +372 -0
- package/dist/bin/sync-runner-watch-loop.js.map +1 -0
- package/dist/bin/sync-runner-watch-routes.d.ts +25 -0
- package/dist/bin/sync-runner-watch-routes.d.ts.map +1 -0
- package/dist/bin/sync-runner-watch-routes.js +74 -0
- package/dist/bin/sync-runner-watch-routes.js.map +1 -0
- package/dist/bin/sync-runner.d.ts +5 -54
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +76 -978
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +265 -11
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/reindex.d.ts.map +1 -1
- package/dist/cli/reindex.js +34 -17
- package/dist/cli/reindex.js.map +1 -1
- package/dist/cli/reindex.test.js +39 -5
- package/dist/cli/reindex.test.js.map +1 -1
- package/dist/cli/rescue-classify-ordering.test.js +75 -0
- package/dist/cli/rescue-classify-ordering.test.js.map +1 -1
- package/dist/cli/rescue-core.d.ts +45 -0
- package/dist/cli/rescue-core.d.ts.map +1 -1
- package/dist/cli/rescue-core.js +320 -170
- package/dist/cli/rescue-core.js.map +1 -1
- package/dist/cli/share.d.ts +2 -1
- package/dist/cli/share.d.ts.map +1 -1
- package/dist/cli/share.js +276 -660
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +30 -0
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/sync.d.ts +28 -1
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +541 -748
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +382 -1
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/cognito-auth.d.ts.map +1 -1
- package/dist/cognito-auth.js +55 -10
- package/dist/cognito-auth.js.map +1 -1
- package/dist/cognito-auth.test.js +61 -0
- package/dist/cognito-auth.test.js.map +1 -1
- package/dist/daemon-worker.d.ts +2 -2
- package/dist/daemon-worker.js +3 -3
- package/dist/daemon-worker.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +93 -6
- package/dist/journal.js.map +1 -1
- package/dist/journal.test.js +59 -0
- package/dist/journal.test.js.map +1 -1
- package/dist/machine-auth.test.js +60 -2
- package/dist/machine-auth.test.js.map +1 -1
- package/dist/object-io.d.ts +37 -1
- package/dist/object-io.d.ts.map +1 -1
- package/dist/object-io.js +149 -30
- package/dist/object-io.js.map +1 -1
- package/dist/object-io.test.js +121 -0
- package/dist/object-io.test.js.map +1 -1
- package/dist/operation-lock.d.ts +8 -8
- package/dist/operation-lock.d.ts.map +1 -1
- package/dist/operation-lock.js +99 -32
- package/dist/operation-lock.js.map +1 -1
- package/dist/operation-lock.test.js +51 -4
- package/dist/operation-lock.test.js.map +1 -1
- package/dist/personal-vault.d.ts.map +1 -1
- package/dist/personal-vault.js +8 -2
- package/dist/personal-vault.js.map +1 -1
- package/dist/personal-vault.test.js +34 -0
- package/dist/personal-vault.test.js.map +1 -1
- package/dist/prefix-coalesce.d.ts +20 -9
- package/dist/prefix-coalesce.d.ts.map +1 -1
- package/dist/prefix-coalesce.js +124 -28
- package/dist/prefix-coalesce.js.map +1 -1
- package/dist/prefix-coalesce.test.js +57 -2
- package/dist/prefix-coalesce.test.js.map +1 -1
- package/dist/remote-pull.d.ts +8 -3
- package/dist/remote-pull.d.ts.map +1 -1
- package/dist/remote-pull.js +85 -16
- package/dist/remote-pull.js.map +1 -1
- package/dist/remote-pull.test.js +213 -2
- package/dist/remote-pull.test.js.map +1 -1
- package/dist/s3.d.ts +2 -0
- package/dist/s3.d.ts.map +1 -1
- package/dist/s3.js +197 -116
- package/dist/s3.js.map +1 -1
- package/dist/s3.test.js +109 -0
- package/dist/s3.test.js.map +1 -1
- package/dist/scope-shrink.d.ts +3 -2
- package/dist/scope-shrink.d.ts.map +1 -1
- package/dist/scope-shrink.js +1 -1
- package/dist/scope-shrink.js.map +1 -1
- package/dist/skill-telemetry.d.ts +1 -1
- package/dist/skill-telemetry.d.ts.map +1 -1
- package/dist/skill-telemetry.js +69 -9
- package/dist/skill-telemetry.js.map +1 -1
- package/dist/skill-telemetry.test.js +86 -0
- package/dist/skill-telemetry.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 +34 -1
- package/dist/sync/event-sync.js.map +1 -1
- package/dist/sync/event-sync.test.js +73 -0
- package/dist/sync/event-sync.test.js.map +1 -1
- package/dist/sync/metrics.d.ts +17 -1
- package/dist/sync/metrics.d.ts.map +1 -1
- package/dist/sync/metrics.js +32 -1
- package/dist/sync/metrics.js.map +1 -1
- package/dist/sync/metrics.test.js +74 -1
- package/dist/sync/metrics.test.js.map +1 -1
- package/dist/sync/pull-scope.d.ts.map +1 -1
- package/dist/sync/pull-scope.js +15 -7
- package/dist/sync/pull-scope.js.map +1 -1
- package/dist/sync/push-receiver.d.ts +12 -5
- package/dist/sync/push-receiver.d.ts.map +1 -1
- package/dist/sync/push-receiver.js +45 -17
- package/dist/sync/push-receiver.js.map +1 -1
- package/dist/sync/push-receiver.test.js +67 -1
- package/dist/sync/push-receiver.test.js.map +1 -1
- package/dist/sync-core.d.ts +27 -0
- package/dist/sync-core.d.ts.map +1 -0
- package/dist/sync-core.js +54 -0
- package/dist/sync-core.js.map +1 -0
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +59 -6
- package/dist/telemetry.js.map +1 -1
- package/dist/telemetry.test.js +74 -0
- package/dist/telemetry.test.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/vault-client.d.ts.map +1 -1
- package/dist/vault-client.js +284 -36
- package/dist/vault-client.js.map +1 -1
- package/dist/vault-client.test.js +59 -0
- package/dist/vault-client.test.js.map +1 -1
- package/dist/watcher.d.ts +38 -20
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +155 -143
- package/dist/watcher.js.map +1 -1
- package/dist/watcher.test.js +103 -0
- package/dist/watcher.test.js.map +1 -1
- package/package.json +1 -1
- package/src/bin/sync-runner-company.ts +350 -0
- package/src/bin/sync-runner-events.ts +25 -0
- package/src/bin/sync-runner-planning.ts +121 -0
- package/src/bin/sync-runner-rollup.ts +72 -0
- package/src/bin/sync-runner-telemetry.ts +8 -0
- package/src/bin/sync-runner-watch-loop.ts +443 -0
- package/src/bin/sync-runner-watch-routes.ts +86 -0
- package/src/bin/sync-runner.test.ts +298 -11
- package/src/bin/sync-runner.ts +99 -1054
- package/src/cli/reindex.test.ts +41 -3
- package/src/cli/reindex.ts +35 -19
- package/src/cli/rescue-classify-ordering.test.ts +81 -0
- package/src/cli/rescue-core.ts +400 -165
- package/src/cli/share.test.ts +38 -0
- package/src/cli/share.ts +420 -693
- package/src/cli/sync.test.ts +460 -1
- package/src/cli/sync.ts +788 -825
- package/src/cognito-auth.test.ts +77 -0
- package/src/cognito-auth.ts +73 -11
- package/src/daemon-worker.ts +3 -3
- package/src/index.ts +8 -0
- package/src/journal.test.ts +72 -0
- package/src/journal.ts +95 -8
- package/src/machine-auth.test.ts +64 -2
- package/src/object-io.test.ts +142 -0
- package/src/object-io.ts +183 -31
- package/src/operation-lock.test.ts +63 -4
- package/src/operation-lock.ts +99 -31
- package/src/personal-vault.test.ts +42 -0
- package/src/personal-vault.ts +8 -2
- package/src/prefix-coalesce.test.ts +71 -1
- package/src/prefix-coalesce.ts +155 -30
- package/src/remote-pull.test.ts +235 -1
- package/src/remote-pull.ts +106 -18
- package/src/s3.test.ts +126 -0
- package/src/s3.ts +237 -122
- package/src/scope-shrink.ts +6 -3
- package/src/skill-telemetry.test.ts +109 -0
- package/src/skill-telemetry.ts +82 -14
- package/src/sync/event-sync.test.ts +75 -0
- package/src/sync/event-sync.ts +54 -1
- package/src/sync/metrics.test.ts +81 -0
- package/src/sync/metrics.ts +59 -4
- package/src/sync/pull-scope.ts +23 -7
- package/src/sync/push-receiver.test.ts +73 -1
- package/src/sync/push-receiver.ts +56 -20
- package/src/sync-core.ts +58 -0
- package/src/telemetry.test.ts +85 -0
- package/src/telemetry.ts +69 -6
- package/src/types.ts +8 -0
- package/src/vault-client.test.ts +74 -0
- package/src/vault-client.ts +395 -43
- package/src/watcher.test.ts +117 -0
- package/src/watcher.ts +215 -174
|
@@ -42,6 +42,7 @@ import type {
|
|
|
42
42
|
PendingInviteByEmail,
|
|
43
43
|
} from "../vault-client.js";
|
|
44
44
|
import { VaultAuthError } from "../vault-client.js";
|
|
45
|
+
import type { PushEvent } from "../sync/push-event.js";
|
|
45
46
|
|
|
46
47
|
// ---------------------------------------------------------------------------
|
|
47
48
|
// Hermetic journal state — the runner now calls migratePersonalVaultJournal()
|
|
@@ -1008,7 +1009,7 @@ describe("per-company fanout", () => {
|
|
|
1008
1009
|
]);
|
|
1009
1010
|
});
|
|
1010
1011
|
|
|
1011
|
-
it("
|
|
1012
|
+
it("F09: per-file transfer errors make the run partial and non-zero", async () => {
|
|
1012
1013
|
const deps = makeDeps({
|
|
1013
1014
|
createVaultClient: () =>
|
|
1014
1015
|
makeVaultStub({
|
|
@@ -1027,7 +1028,7 @@ describe("per-company fanout", () => {
|
|
|
1027
1028
|
});
|
|
1028
1029
|
|
|
1029
1030
|
const code = await runRunner(["--companies"], deps);
|
|
1030
|
-
expect(code).toBe(
|
|
1031
|
+
expect(code).toBe(2);
|
|
1031
1032
|
// Per-file errors are routed to stderr (error-class events).
|
|
1032
1033
|
const errs = deps.stderr
|
|
1033
1034
|
.events()
|
|
@@ -1042,6 +1043,14 @@ describe("per-company fanout", () => {
|
|
|
1042
1043
|
message: "access denied",
|
|
1043
1044
|
},
|
|
1044
1045
|
]);
|
|
1046
|
+
const all = deps.stdout
|
|
1047
|
+
.events()
|
|
1048
|
+
.find(
|
|
1049
|
+
(e): e is Extract<RunnerEvent, { type: "all-complete" }> =>
|
|
1050
|
+
e.type === "all-complete",
|
|
1051
|
+
);
|
|
1052
|
+
expect(all).toBeDefined();
|
|
1053
|
+
expect(all?.partial).toBe(true);
|
|
1045
1054
|
});
|
|
1046
1055
|
|
|
1047
1056
|
it("emits complete event per company with the SyncResult spread", async () => {
|
|
@@ -3056,6 +3065,73 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
3056
3065
|
await loop;
|
|
3057
3066
|
});
|
|
3058
3067
|
|
|
3068
|
+
it("F05: guard-held watcher push is queued after the active poll pass", async () => {
|
|
3069
|
+
const watcher = makeWatcherStub();
|
|
3070
|
+
const clock = new FakeClock();
|
|
3071
|
+
let triggerShutdown = () => {};
|
|
3072
|
+
|
|
3073
|
+
let releasePoll: () => void = () => {};
|
|
3074
|
+
const pollGate = new Promise<void>((resolve) => {
|
|
3075
|
+
releasePoll = resolve;
|
|
3076
|
+
});
|
|
3077
|
+
let active = 0;
|
|
3078
|
+
let maxConcurrent = 0;
|
|
3079
|
+
let calls = 0;
|
|
3080
|
+
const runPass = vi.fn().mockImplementation(async () => {
|
|
3081
|
+
active++;
|
|
3082
|
+
maxConcurrent = Math.max(maxConcurrent, active);
|
|
3083
|
+
calls++;
|
|
3084
|
+
if (calls === 1) await pollGate;
|
|
3085
|
+
active--;
|
|
3086
|
+
return 0;
|
|
3087
|
+
});
|
|
3088
|
+
|
|
3089
|
+
const loop = runRunnerWithLoop(
|
|
3090
|
+
["--companies", "--watch", "--event-push", "--hq-root", "/tmp/hq"],
|
|
3091
|
+
{
|
|
3092
|
+
runPass,
|
|
3093
|
+
clock,
|
|
3094
|
+
createWatcher: () => watcher,
|
|
3095
|
+
sleep: () => new Promise<void>(() => {}),
|
|
3096
|
+
onShutdownSignal: (handler) => {
|
|
3097
|
+
triggerShutdown = handler;
|
|
3098
|
+
return () => {};
|
|
3099
|
+
},
|
|
3100
|
+
},
|
|
3101
|
+
);
|
|
3102
|
+
|
|
3103
|
+
await Promise.resolve();
|
|
3104
|
+
await Promise.resolve();
|
|
3105
|
+
expect(active).toBe(1);
|
|
3106
|
+
|
|
3107
|
+
watcher.emit("companies/indigo/queued.md");
|
|
3108
|
+
clock.advance(0);
|
|
3109
|
+
await Promise.resolve();
|
|
3110
|
+
await Promise.resolve();
|
|
3111
|
+
expect(maxConcurrent).toBe(1);
|
|
3112
|
+
|
|
3113
|
+
releasePoll();
|
|
3114
|
+
for (let i = 0; i < 8; i++) await Promise.resolve();
|
|
3115
|
+
clock.advance(0);
|
|
3116
|
+
for (let i = 0; i < 8; i++) await Promise.resolve();
|
|
3117
|
+
|
|
3118
|
+
triggerShutdown();
|
|
3119
|
+
await loop;
|
|
3120
|
+
|
|
3121
|
+
expect(maxConcurrent).toBe(1);
|
|
3122
|
+
const targetedCall = runPass.mock.calls.find((c) =>
|
|
3123
|
+
(c[0] as string[]).includes("--company"),
|
|
3124
|
+
);
|
|
3125
|
+
expect(targetedCall?.[0]).toEqual([
|
|
3126
|
+
"--company",
|
|
3127
|
+
"indigo",
|
|
3128
|
+
"--direction",
|
|
3129
|
+
"push",
|
|
3130
|
+
"--hq-root",
|
|
3131
|
+
"/tmp/hq",
|
|
3132
|
+
]);
|
|
3133
|
+
});
|
|
3134
|
+
|
|
3059
3135
|
it("poll-still-runs: the poll loop fires passes independent of the watcher", async () => {
|
|
3060
3136
|
const watcher = makeWatcherStub();
|
|
3061
3137
|
let triggerShutdown = () => {};
|
|
@@ -3813,7 +3889,7 @@ describe("readPinnedPrefixes", () => {
|
|
|
3813
3889
|
});
|
|
3814
3890
|
|
|
3815
3891
|
// ---------------------------------------------------------------------------
|
|
3816
|
-
// Operation lock —
|
|
3892
|
+
// Operation lock — sync operations serialize per root.
|
|
3817
3893
|
// ---------------------------------------------------------------------------
|
|
3818
3894
|
describe("runRunnerWithLoop — operation lock", () => {
|
|
3819
3895
|
const HQ = "/tmp/hq-oplock";
|
|
@@ -3931,14 +4007,29 @@ describe("runRunnerWithLoop — operation lock", () => {
|
|
|
3931
4007
|
expect(out).toContain("reindex");
|
|
3932
4008
|
}, 20_000);
|
|
3933
4009
|
|
|
3934
|
-
it("
|
|
4010
|
+
it("F03: watch runner refuses when another operation holds the root lock", async () => {
|
|
3935
4011
|
const lp = writeLiveHolder("sync");
|
|
3936
4012
|
const watcher = makeWatcherStub();
|
|
3937
4013
|
let triggerShutdown = () => {};
|
|
3938
4014
|
const runPass = vi.fn().mockResolvedValue(0);
|
|
4015
|
+
const errs: string[] = [];
|
|
4016
|
+
const spy = vi
|
|
4017
|
+
.spyOn(process.stderr, "write")
|
|
4018
|
+
.mockImplementation((chunk: string | Uint8Array) => {
|
|
4019
|
+
errs.push(String(chunk));
|
|
4020
|
+
return true;
|
|
4021
|
+
});
|
|
3939
4022
|
|
|
3940
4023
|
const loop = runRunnerWithLoop(
|
|
3941
|
-
[
|
|
4024
|
+
[
|
|
4025
|
+
"--companies",
|
|
4026
|
+
"--watch",
|
|
4027
|
+
"--event-push",
|
|
4028
|
+
"--hq-root",
|
|
4029
|
+
HQ,
|
|
4030
|
+
"--lock-timeout",
|
|
4031
|
+
"0",
|
|
4032
|
+
],
|
|
3942
4033
|
{
|
|
3943
4034
|
runPass,
|
|
3944
4035
|
clock: new FakeClock(),
|
|
@@ -3953,14 +4044,18 @@ describe("runRunnerWithLoop — operation lock", () => {
|
|
|
3953
4044
|
|
|
3954
4045
|
await Promise.resolve();
|
|
3955
4046
|
await Promise.resolve();
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
4047
|
+
if (watcher.started || runPass.mock.calls.length > 0) {
|
|
4048
|
+
triggerShutdown();
|
|
4049
|
+
}
|
|
4050
|
+
const code = await loop;
|
|
4051
|
+
spy.mockRestore();
|
|
3959
4052
|
|
|
3960
|
-
|
|
3961
|
-
|
|
4053
|
+
expect(code).toBe(OPERATION_LOCKED_EXIT);
|
|
4054
|
+
expect(watcher.started).toBe(false);
|
|
4055
|
+
expect(runPass).not.toHaveBeenCalled();
|
|
4056
|
+
expect(errs.join("")).toContain("sync");
|
|
3962
4057
|
|
|
3963
|
-
// The pre-existing holder lock is untouched
|
|
4058
|
+
// The pre-existing holder lock is untouched.
|
|
3964
4059
|
const held = JSON.parse(fs.readFileSync(lp, "utf8"));
|
|
3965
4060
|
expect(held.pid).toBe(1);
|
|
3966
4061
|
expect(held.command).toBe("sync");
|
|
@@ -4147,6 +4242,198 @@ describe("runRunnerWithLoop — Phase 3 event-sync wiring (US-017/018/019)", ()
|
|
|
4147
4242
|
await loop;
|
|
4148
4243
|
});
|
|
4149
4244
|
|
|
4245
|
+
it("F04: mixed-route watcher batches publish only routes that were pushed", async () => {
|
|
4246
|
+
const publishBatch = vi.fn();
|
|
4247
|
+
const startEventSync = vi.fn().mockResolvedValue({
|
|
4248
|
+
publishBatch,
|
|
4249
|
+
receiver: { start: async () => {}, dispose: async () => {}, connected: true },
|
|
4250
|
+
ownDeviceId: "dev-test",
|
|
4251
|
+
dispose: vi.fn().mockResolvedValue(undefined),
|
|
4252
|
+
});
|
|
4253
|
+
const runPass = vi.fn().mockResolvedValue(0);
|
|
4254
|
+
const { loop, watcher, clock, shutdown } = runLoop({
|
|
4255
|
+
claims: ENROLLED_CLAIMS,
|
|
4256
|
+
startEventSync,
|
|
4257
|
+
runPass,
|
|
4258
|
+
});
|
|
4259
|
+
await microtasks();
|
|
4260
|
+
runPass.mockClear();
|
|
4261
|
+
|
|
4262
|
+
watcher.emit("companies/indigo/a.md", {
|
|
4263
|
+
paths: new Map([
|
|
4264
|
+
["/tmp/hq/companies/indigo/a.md", "companies/indigo/a.md"],
|
|
4265
|
+
["/tmp/hq/companies/acme/b.md", "companies/acme/b.md"],
|
|
4266
|
+
["/tmp/hq/notes/personal.md", "notes/personal.md"],
|
|
4267
|
+
]),
|
|
4268
|
+
});
|
|
4269
|
+
clock.advance(0);
|
|
4270
|
+
await microtasks();
|
|
4271
|
+
|
|
4272
|
+
const pushedRoutes = new Set<string>();
|
|
4273
|
+
let pushedEverything = false;
|
|
4274
|
+
for (const call of runPass.mock.calls) {
|
|
4275
|
+
const passArgv = call[0] as string[];
|
|
4276
|
+
const directionIdx = passArgv.indexOf("--direction");
|
|
4277
|
+
if (passArgv[directionIdx + 1] !== "push") continue;
|
|
4278
|
+
const companyIdx = passArgv.indexOf("--company");
|
|
4279
|
+
if (companyIdx >= 0) {
|
|
4280
|
+
pushedRoutes.add(`company:${passArgv[companyIdx + 1]}`);
|
|
4281
|
+
} else if (passArgv.includes("--companies")) {
|
|
4282
|
+
pushedEverything = true;
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
const publishedRelPaths = publishBatch.mock.calls.flatMap((call) => {
|
|
4286
|
+
const batch = call[0] as { paths: Map<string, string> };
|
|
4287
|
+
return [...batch.paths.values()];
|
|
4288
|
+
});
|
|
4289
|
+
const publishedWithoutPush = publishedRelPaths.filter((relPath) => {
|
|
4290
|
+
if (pushedEverything) return false;
|
|
4291
|
+
const route = routeChangeToTarget(relPath);
|
|
4292
|
+
if (!route) return true;
|
|
4293
|
+
if (route.kind === "company") {
|
|
4294
|
+
return !pushedRoutes.has(`company:${route.slug}`);
|
|
4295
|
+
}
|
|
4296
|
+
return !pushedRoutes.has("personal");
|
|
4297
|
+
});
|
|
4298
|
+
|
|
4299
|
+
expect(publishedRelPaths).toContain("companies/indigo/a.md");
|
|
4300
|
+
expect(publishedWithoutPush).toEqual([]);
|
|
4301
|
+
|
|
4302
|
+
shutdown();
|
|
4303
|
+
await loop;
|
|
4304
|
+
});
|
|
4305
|
+
|
|
4306
|
+
it("R-F04F05: preserves queued watcher push targets across overlap", async () => {
|
|
4307
|
+
const publishBatch = vi.fn();
|
|
4308
|
+
const startEventSync = vi.fn().mockResolvedValue({
|
|
4309
|
+
publishBatch,
|
|
4310
|
+
receiver: { start: async () => {}, dispose: async () => {}, connected: true },
|
|
4311
|
+
ownDeviceId: "dev-test",
|
|
4312
|
+
dispose: vi.fn().mockResolvedValue(undefined),
|
|
4313
|
+
});
|
|
4314
|
+
let receiverSync:
|
|
4315
|
+
| ((ctx: {
|
|
4316
|
+
event: PushEvent;
|
|
4317
|
+
signal: AbortSignal;
|
|
4318
|
+
}) => Promise<void>)
|
|
4319
|
+
| null = null;
|
|
4320
|
+
const watcher = makeBatchWatcherStub();
|
|
4321
|
+
const clock = new FakeClock();
|
|
4322
|
+
let triggerShutdown = () => {};
|
|
4323
|
+
let releasePoll: () => void = () => {};
|
|
4324
|
+
const pollGate = new Promise<void>((resolve) => {
|
|
4325
|
+
releasePoll = resolve;
|
|
4326
|
+
});
|
|
4327
|
+
let passCount = 0;
|
|
4328
|
+
const runPass = vi.fn().mockImplementation(async () => {
|
|
4329
|
+
passCount += 1;
|
|
4330
|
+
if (passCount === 1) await pollGate;
|
|
4331
|
+
return 0;
|
|
4332
|
+
});
|
|
4333
|
+
|
|
4334
|
+
const loop = runRunnerWithLoop(
|
|
4335
|
+
["--companies", "--watch", "--event-push", "--hq-root", "/tmp/hq"],
|
|
4336
|
+
{
|
|
4337
|
+
runPass,
|
|
4338
|
+
clock,
|
|
4339
|
+
createWatcher: () => watcher,
|
|
4340
|
+
createReceiver: ({ syncFn }) => {
|
|
4341
|
+
receiverSync = syncFn;
|
|
4342
|
+
return {
|
|
4343
|
+
connected: true,
|
|
4344
|
+
start: async () => {},
|
|
4345
|
+
dispose: async () => {},
|
|
4346
|
+
};
|
|
4347
|
+
},
|
|
4348
|
+
sleep: () => new Promise<void>(() => {}),
|
|
4349
|
+
onShutdownSignal: (handler) => {
|
|
4350
|
+
triggerShutdown = handler;
|
|
4351
|
+
return () => {};
|
|
4352
|
+
},
|
|
4353
|
+
getIdTokenClaims: () => ENROLLED_CLAIMS as never,
|
|
4354
|
+
getAccessToken: async () => "jwt-test",
|
|
4355
|
+
startEventSync,
|
|
4356
|
+
},
|
|
4357
|
+
);
|
|
4358
|
+
await microtasks();
|
|
4359
|
+
expect(passCount).toBe(1);
|
|
4360
|
+
expect(receiverSync).toBeTruthy();
|
|
4361
|
+
|
|
4362
|
+
const indigoBatch = {
|
|
4363
|
+
paths: new Map([
|
|
4364
|
+
["/tmp/hq/companies/indigo/a.md", "companies/indigo/a.md"],
|
|
4365
|
+
]),
|
|
4366
|
+
};
|
|
4367
|
+
const betaBatch = {
|
|
4368
|
+
paths: new Map([
|
|
4369
|
+
["/tmp/hq/companies/beta/b.md", "companies/beta/b.md"],
|
|
4370
|
+
]),
|
|
4371
|
+
};
|
|
4372
|
+
watcher.emit("companies/indigo/a.md", indigoBatch);
|
|
4373
|
+
clock.advance(0);
|
|
4374
|
+
await microtasks();
|
|
4375
|
+
watcher.emit("companies/beta/b.md", betaBatch);
|
|
4376
|
+
clock.advance(0);
|
|
4377
|
+
await microtasks();
|
|
4378
|
+
|
|
4379
|
+
const remotePull = receiverSync!({
|
|
4380
|
+
event: {
|
|
4381
|
+
kind: "upsert",
|
|
4382
|
+
relativePath: "companies/acme/remote.md",
|
|
4383
|
+
contentHash: "sha256:remote",
|
|
4384
|
+
mtime: "2026-06-18T12:00:00.000Z",
|
|
4385
|
+
originDeviceId: "peer-device",
|
|
4386
|
+
originTenantId: "tenant-acme",
|
|
4387
|
+
sequenceNumber: 7,
|
|
4388
|
+
eventTimestamp: "2026-06-18T12:00:00.000Z",
|
|
4389
|
+
},
|
|
4390
|
+
signal: new AbortController().signal,
|
|
4391
|
+
});
|
|
4392
|
+
await microtasks();
|
|
4393
|
+
|
|
4394
|
+
releasePoll();
|
|
4395
|
+
await remotePull;
|
|
4396
|
+
await microtasks();
|
|
4397
|
+
clock.advance(0);
|
|
4398
|
+
await microtasks();
|
|
4399
|
+
|
|
4400
|
+
const passArgvs = runPass.mock.calls.map((call) => call[0] as string[]);
|
|
4401
|
+
expect(passArgvs).toEqual(
|
|
4402
|
+
expect.arrayContaining([
|
|
4403
|
+
["--company", "indigo", "--direction", "push", "--hq-root", "/tmp/hq"],
|
|
4404
|
+
["--company", "beta", "--direction", "push", "--hq-root", "/tmp/hq"],
|
|
4405
|
+
["--company", "acme", "--direction", "pull", "--hq-root", "/tmp/hq"],
|
|
4406
|
+
]),
|
|
4407
|
+
);
|
|
4408
|
+
|
|
4409
|
+
const pushedRoutes = new Set<string>();
|
|
4410
|
+
for (const passArgv of passArgvs) {
|
|
4411
|
+
const directionIdx = passArgv.indexOf("--direction");
|
|
4412
|
+
if (passArgv[directionIdx + 1] !== "push") continue;
|
|
4413
|
+
const companyIdx = passArgv.indexOf("--company");
|
|
4414
|
+
if (companyIdx >= 0) pushedRoutes.add(`company:${passArgv[companyIdx + 1]}`);
|
|
4415
|
+
}
|
|
4416
|
+
const publishedRelPaths = publishBatch.mock.calls.flatMap((call) => {
|
|
4417
|
+
const batch = call[0] as { paths: Map<string, string> };
|
|
4418
|
+
return [...batch.paths.values()];
|
|
4419
|
+
});
|
|
4420
|
+
const publishedWithoutPush = publishedRelPaths.filter((relPath) => {
|
|
4421
|
+
const route = routeChangeToTarget(relPath);
|
|
4422
|
+
return route?.kind !== "company" || !pushedRoutes.has(`company:${route.slug}`);
|
|
4423
|
+
});
|
|
4424
|
+
|
|
4425
|
+
expect(publishedRelPaths).toEqual(
|
|
4426
|
+
expect.arrayContaining([
|
|
4427
|
+
"companies/indigo/a.md",
|
|
4428
|
+
"companies/beta/b.md",
|
|
4429
|
+
]),
|
|
4430
|
+
);
|
|
4431
|
+
expect(publishedWithoutPush).toEqual([]);
|
|
4432
|
+
|
|
4433
|
+
triggerShutdown();
|
|
4434
|
+
await loop;
|
|
4435
|
+
});
|
|
4436
|
+
|
|
4150
4437
|
it("falls back to a synthesized single-path batch when the watcher emits a bare path", async () => {
|
|
4151
4438
|
const publishBatch = vi.fn();
|
|
4152
4439
|
const startEventSync = vi.fn().mockResolvedValue({
|