@indigoai-us/hq-cloud 6.13.3 → 6.13.5
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.map +1 -1
- package/dist/bin/sync-runner-company.js +15 -0
- package/dist/bin/sync-runner-company.js.map +1 -1
- package/dist/bin/sync-runner-rollup.d.ts +7 -0
- package/dist/bin/sync-runner-rollup.d.ts.map +1 -1
- package/dist/bin/sync-runner-rollup.js +15 -0
- package/dist/bin/sync-runner-rollup.js.map +1 -1
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-loop.js +10 -1
- package/dist/bin/sync-runner-watch-loop.js.map +1 -1
- package/dist/bin/sync-runner.d.ts +7 -0
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +36 -7
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +142 -0
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/share.js +15 -2
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +39 -0
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/sync.d.ts +22 -0
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +14 -0
- package/dist/cli/sync.js.map +1 -1
- package/dist/lib/net-errors.d.ts +36 -0
- package/dist/lib/net-errors.d.ts.map +1 -0
- package/dist/lib/net-errors.js +81 -0
- package/dist/lib/net-errors.js.map +1 -0
- package/dist/lib/net-errors.test.d.ts +2 -0
- package/dist/lib/net-errors.test.d.ts.map +1 -0
- package/dist/lib/net-errors.test.js +47 -0
- package/dist/lib/net-errors.test.js.map +1 -0
- package/dist/qmd-reindex.d.ts +14 -0
- package/dist/qmd-reindex.d.ts.map +1 -1
- package/dist/qmd-reindex.js +93 -19
- package/dist/qmd-reindex.js.map +1 -1
- package/dist/qmd-reindex.test.js +47 -0
- package/dist/qmd-reindex.test.js.map +1 -1
- package/package.json +1 -1
- package/src/bin/sync-runner-company.ts +21 -0
- package/src/bin/sync-runner-rollup.ts +23 -0
- package/src/bin/sync-runner-watch-loop.ts +11 -1
- package/src/bin/sync-runner.test.ts +167 -0
- package/src/bin/sync-runner.ts +49 -9
- package/src/cli/share.test.ts +46 -0
- package/src/cli/share.ts +14 -2
- package/src/cli/sync.ts +40 -0
- package/src/lib/net-errors.test.ts +53 -0
- package/src/lib/net-errors.ts +83 -0
- package/src/qmd-reindex.test.ts +50 -0
- package/src/qmd-reindex.ts +124 -18
|
@@ -16,6 +16,7 @@ import { FakeClock } from "../watcher.js";
|
|
|
16
16
|
import { PERSONAL_VAULT_JOURNAL_SLUG } from "../journal.js";
|
|
17
17
|
import { HQ_CLOUD_VERSION } from "../version.js";
|
|
18
18
|
import { lockPathFor, OPERATION_LOCKED_EXIT } from "../operation-lock.js";
|
|
19
|
+
import { TRANSIENT_NETWORK_EXIT } from "../lib/net-errors.js";
|
|
19
20
|
import { VaultAuthError } from "../vault-client.js";
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
21
22
|
// Hermetic journal state — the runner now calls migratePersonalVaultJournal()
|
|
@@ -68,6 +69,7 @@ function defaultSyncResult(overrides = {}) {
|
|
|
68
69
|
filesOutOfScope: 0,
|
|
69
70
|
scopeOrphansRemoved: 0,
|
|
70
71
|
scopeOrphansBlocked: 0,
|
|
72
|
+
changedPaths: [],
|
|
71
73
|
...overrides,
|
|
72
74
|
};
|
|
73
75
|
}
|
|
@@ -134,6 +136,14 @@ function makeDeps(overrides = {}) {
|
|
|
134
136
|
getAccessToken: vi.fn().mockResolvedValue("test-access-token"),
|
|
135
137
|
createVaultClient: vi.fn().mockImplementation(() => makeVaultStub()),
|
|
136
138
|
sync: vi.fn().mockResolvedValue(defaultSyncResult()),
|
|
139
|
+
reindex: vi.fn(() => ({ status: 0 })),
|
|
140
|
+
qmdReindex: vi.fn(() => ({
|
|
141
|
+
qmdAvailable: true,
|
|
142
|
+
collectionsAdded: [],
|
|
143
|
+
updated: true,
|
|
144
|
+
embedded: false,
|
|
145
|
+
pendingDirty: false,
|
|
146
|
+
})),
|
|
137
147
|
...overrides,
|
|
138
148
|
stdout,
|
|
139
149
|
stderr,
|
|
@@ -298,6 +308,47 @@ describe("memberships retry", () => {
|
|
|
298
308
|
path: "(discovery)",
|
|
299
309
|
});
|
|
300
310
|
});
|
|
311
|
+
it("a transient network failure (fetch failed) during discovery returns TRANSIENT_NETWORK_EXIT, not 1 (HQ-SYNC-1W)", async () => {
|
|
312
|
+
// Regression for the "auto-sync watcher exited unexpectedly (code=Some(1))"
|
|
313
|
+
// cluster: a machine that stays offline through all 3 membership retries
|
|
314
|
+
// used to `return 1`, which the watch loop propagated and the menubar
|
|
315
|
+
// reported as a crash for every network blip. undici surfaces the blip as
|
|
316
|
+
// `TypeError: fetch failed`; that must map to the retryable exit code so the
|
|
317
|
+
// watcher stays alive instead of crash-reporting. A one-shot run still sees
|
|
318
|
+
// a non-zero exit.
|
|
319
|
+
let listCalls = 0;
|
|
320
|
+
const stub = makeVaultStub();
|
|
321
|
+
stub.listMyMemberships = () => {
|
|
322
|
+
listCalls++;
|
|
323
|
+
return Promise.reject(new TypeError("fetch failed"));
|
|
324
|
+
};
|
|
325
|
+
const deps = makeDeps({ createVaultClient: () => stub });
|
|
326
|
+
const code = await runRunner(["--companies"], deps);
|
|
327
|
+
expect(code).toBe(TRANSIENT_NETWORK_EXIT);
|
|
328
|
+
expect(listCalls).toBe(3);
|
|
329
|
+
// Still surfaced for observability — just no longer a "code 1" crash.
|
|
330
|
+
const events = deps.stderr.events();
|
|
331
|
+
expect(events).toHaveLength(1);
|
|
332
|
+
expect(events[0]).toMatchObject({
|
|
333
|
+
type: "error",
|
|
334
|
+
message: "fetch failed",
|
|
335
|
+
path: "(discovery)",
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
it("a NON-transient discovery failure still returns 1 (unchanged)", async () => {
|
|
339
|
+
// Guard the conservative classifier: a generic error (not a transport
|
|
340
|
+
// shape) is still a hard exit 1, exactly as before.
|
|
341
|
+
let listCalls = 0;
|
|
342
|
+
const stub = makeVaultStub();
|
|
343
|
+
stub.listMyMemberships = () => {
|
|
344
|
+
listCalls++;
|
|
345
|
+
return Promise.reject(new Error("unexpected 500 from vault api"));
|
|
346
|
+
};
|
|
347
|
+
const deps = makeDeps({ createVaultClient: () => stub });
|
|
348
|
+
const code = await runRunner(["--companies"], deps);
|
|
349
|
+
expect(code).toBe(1);
|
|
350
|
+
expect(listCalls).toBe(3);
|
|
351
|
+
});
|
|
301
352
|
it("VaultAuthError short-circuits retry (no retries on auth failure)", async () => {
|
|
302
353
|
let listCalls = 0;
|
|
303
354
|
const stub = makeVaultStub();
|
|
@@ -1330,6 +1381,70 @@ describe("all-complete aggregate", () => {
|
|
|
1330
1381
|
],
|
|
1331
1382
|
});
|
|
1332
1383
|
});
|
|
1384
|
+
it("skips per-target native reindex and runs one final reindex for tombstones or scope orphans", async () => {
|
|
1385
|
+
const slugs = { cmp_a: "acme", cmp_b: "beta" };
|
|
1386
|
+
const reindex = vi.fn(() => ({ status: 0 }));
|
|
1387
|
+
const syncSpy = vi
|
|
1388
|
+
.fn()
|
|
1389
|
+
.mockResolvedValueOnce(defaultSyncResult({ filesTombstoned: 1 }))
|
|
1390
|
+
.mockResolvedValueOnce(defaultSyncResult({ scopeOrphansRemoved: 1 }));
|
|
1391
|
+
const deps = makeDeps({
|
|
1392
|
+
operationLockAlreadyHeld: true,
|
|
1393
|
+
createVaultClient: () => makeVaultStub({
|
|
1394
|
+
memberships: [{ companyUid: "cmp_a" }, { companyUid: "cmp_b" }],
|
|
1395
|
+
entityGet: (uid) => Promise.resolve({ uid, slug: slugs[uid] ?? uid }),
|
|
1396
|
+
}),
|
|
1397
|
+
sync: syncSpy,
|
|
1398
|
+
reindex,
|
|
1399
|
+
});
|
|
1400
|
+
const code = await runRunner(["--companies", "--hq-root", "/tmp/fake-hq"], deps);
|
|
1401
|
+
expect(code).toBe(0);
|
|
1402
|
+
expect(syncSpy).toHaveBeenCalledTimes(2);
|
|
1403
|
+
expect(syncSpy.mock.calls.map(([opts]) => opts.skipReindex)).toEqual([
|
|
1404
|
+
true,
|
|
1405
|
+
true,
|
|
1406
|
+
]);
|
|
1407
|
+
expect(reindex).toHaveBeenCalledTimes(1);
|
|
1408
|
+
expect(reindex).toHaveBeenCalledWith({
|
|
1409
|
+
repoRoot: "/tmp/fake-hq",
|
|
1410
|
+
skipLock: true,
|
|
1411
|
+
});
|
|
1412
|
+
});
|
|
1413
|
+
it("gates qmd to knowledge markdown changes and propagates hq-relative changed paths", async () => {
|
|
1414
|
+
const slugs = { cmp_a: "acme", cmp_b: "beta" };
|
|
1415
|
+
const qmdReindex = vi.fn(() => ({
|
|
1416
|
+
qmdAvailable: true,
|
|
1417
|
+
collectionsAdded: [],
|
|
1418
|
+
updated: true,
|
|
1419
|
+
embedded: false,
|
|
1420
|
+
pendingDirty: false,
|
|
1421
|
+
}));
|
|
1422
|
+
const deps = makeDeps({
|
|
1423
|
+
createVaultClient: () => makeVaultStub({
|
|
1424
|
+
memberships: [{ companyUid: "cmp_a" }, { companyUid: "cmp_b" }],
|
|
1425
|
+
entityGet: (uid) => Promise.resolve({ uid, slug: slugs[uid] ?? uid }),
|
|
1426
|
+
}),
|
|
1427
|
+
sync: vi
|
|
1428
|
+
.fn()
|
|
1429
|
+
.mockResolvedValueOnce(defaultSyncResult({
|
|
1430
|
+
filesDownloaded: 1,
|
|
1431
|
+
changedPaths: ["docs/readme.txt"],
|
|
1432
|
+
}))
|
|
1433
|
+
.mockResolvedValueOnce(defaultSyncResult({
|
|
1434
|
+
filesTombstoned: 1,
|
|
1435
|
+
changedPaths: ["knowledge/old.md"],
|
|
1436
|
+
})),
|
|
1437
|
+
reindex: vi.fn(() => ({ status: 0 })),
|
|
1438
|
+
qmdReindex,
|
|
1439
|
+
});
|
|
1440
|
+
const code = await runRunner(["--companies", "--hq-root", "/tmp/fake-hq"], deps);
|
|
1441
|
+
expect(code).toBe(0);
|
|
1442
|
+
expect(qmdReindex).toHaveBeenCalledTimes(1);
|
|
1443
|
+
expect(qmdReindex).toHaveBeenCalledWith("/tmp/fake-hq", expect.objectContaining({
|
|
1444
|
+
changedPaths: ["companies/beta/knowledge/old.md"],
|
|
1445
|
+
forceCollectionRefresh: true,
|
|
1446
|
+
}));
|
|
1447
|
+
});
|
|
1333
1448
|
it("collects company-level errors into the all-complete errors array", async () => {
|
|
1334
1449
|
const slugs = { cmp_a: "acme", cmp_b: "beta" };
|
|
1335
1450
|
const deps = makeDeps({
|
|
@@ -2587,6 +2702,33 @@ describe("runRunnerWithLoop — event-push wiring", () => {
|
|
|
2587
2702
|
expect(code).toBe(2);
|
|
2588
2703
|
expect(watcher.disposed).toBe(true);
|
|
2589
2704
|
});
|
|
2705
|
+
it("a TRANSIENT_NETWORK_EXIT poll pass does NOT surface as a crash — the loop retries (HQ-SYNC-1W)", async () => {
|
|
2706
|
+
// A transient offline blip must keep the watcher alive: the loop logs and
|
|
2707
|
+
// polls again instead of returning the code and letting the process exit
|
|
2708
|
+
// (which the menubar reports as "watcher exited unexpectedly"). A genuine
|
|
2709
|
+
// hard error on a later pass still terminates the loop.
|
|
2710
|
+
const watcher = makeWatcherStub();
|
|
2711
|
+
let n = 0;
|
|
2712
|
+
const runPass = vi.fn().mockImplementation(async () => {
|
|
2713
|
+
n++;
|
|
2714
|
+
// First pass: machine is offline (transient). Second pass: a real hard
|
|
2715
|
+
// error, used here only to end the otherwise-infinite loop deterministically.
|
|
2716
|
+
return n === 1 ? TRANSIENT_NETWORK_EXIT : 2;
|
|
2717
|
+
});
|
|
2718
|
+
const code = await runRunnerWithLoop(["--companies", "--watch", "--event-push", "--hq-root", "/tmp/hq"], {
|
|
2719
|
+
runPass,
|
|
2720
|
+
clock: new FakeClock(),
|
|
2721
|
+
createWatcher: () => watcher,
|
|
2722
|
+
sleep: () => Promise.resolve(),
|
|
2723
|
+
onShutdownSignal: () => () => { },
|
|
2724
|
+
});
|
|
2725
|
+
// The transient pass did NOT end the loop — it ran again…
|
|
2726
|
+
expect(runPass).toHaveBeenCalledTimes(2);
|
|
2727
|
+
// …and the transient code was never surfaced as the watcher's exit code.
|
|
2728
|
+
expect(code).toBe(2);
|
|
2729
|
+
expect(code).not.toBe(TRANSIENT_NETWORK_EXIT);
|
|
2730
|
+
expect(watcher.disposed).toBe(true);
|
|
2731
|
+
});
|
|
2590
2732
|
it("without --event-push, no watcher is created (poll-only safety net)", async () => {
|
|
2591
2733
|
let triggerShutdown = () => { };
|
|
2592
2734
|
const createWatcher = vi.fn(() => makeWatcherStub());
|