@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.
Files changed (51) hide show
  1. package/dist/bin/sync-runner-company.d.ts.map +1 -1
  2. package/dist/bin/sync-runner-company.js +15 -0
  3. package/dist/bin/sync-runner-company.js.map +1 -1
  4. package/dist/bin/sync-runner-rollup.d.ts +7 -0
  5. package/dist/bin/sync-runner-rollup.d.ts.map +1 -1
  6. package/dist/bin/sync-runner-rollup.js +15 -0
  7. package/dist/bin/sync-runner-rollup.js.map +1 -1
  8. package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
  9. package/dist/bin/sync-runner-watch-loop.js +10 -1
  10. package/dist/bin/sync-runner-watch-loop.js.map +1 -1
  11. package/dist/bin/sync-runner.d.ts +7 -0
  12. package/dist/bin/sync-runner.d.ts.map +1 -1
  13. package/dist/bin/sync-runner.js +36 -7
  14. package/dist/bin/sync-runner.js.map +1 -1
  15. package/dist/bin/sync-runner.test.js +142 -0
  16. package/dist/bin/sync-runner.test.js.map +1 -1
  17. package/dist/cli/share.js +15 -2
  18. package/dist/cli/share.js.map +1 -1
  19. package/dist/cli/share.test.js +39 -0
  20. package/dist/cli/share.test.js.map +1 -1
  21. package/dist/cli/sync.d.ts +22 -0
  22. package/dist/cli/sync.d.ts.map +1 -1
  23. package/dist/cli/sync.js +14 -0
  24. package/dist/cli/sync.js.map +1 -1
  25. package/dist/lib/net-errors.d.ts +36 -0
  26. package/dist/lib/net-errors.d.ts.map +1 -0
  27. package/dist/lib/net-errors.js +81 -0
  28. package/dist/lib/net-errors.js.map +1 -0
  29. package/dist/lib/net-errors.test.d.ts +2 -0
  30. package/dist/lib/net-errors.test.d.ts.map +1 -0
  31. package/dist/lib/net-errors.test.js +47 -0
  32. package/dist/lib/net-errors.test.js.map +1 -0
  33. package/dist/qmd-reindex.d.ts +14 -0
  34. package/dist/qmd-reindex.d.ts.map +1 -1
  35. package/dist/qmd-reindex.js +93 -19
  36. package/dist/qmd-reindex.js.map +1 -1
  37. package/dist/qmd-reindex.test.js +47 -0
  38. package/dist/qmd-reindex.test.js.map +1 -1
  39. package/package.json +1 -1
  40. package/src/bin/sync-runner-company.ts +21 -0
  41. package/src/bin/sync-runner-rollup.ts +23 -0
  42. package/src/bin/sync-runner-watch-loop.ts +11 -1
  43. package/src/bin/sync-runner.test.ts +167 -0
  44. package/src/bin/sync-runner.ts +49 -9
  45. package/src/cli/share.test.ts +46 -0
  46. package/src/cli/share.ts +14 -2
  47. package/src/cli/sync.ts +40 -0
  48. package/src/lib/net-errors.test.ts +53 -0
  49. package/src/lib/net-errors.ts +83 -0
  50. package/src/qmd-reindex.test.ts +50 -0
  51. package/src/qmd-reindex.ts +124 -18
@@ -37,6 +37,7 @@ const defaultExec = (args) => {
37
37
  });
38
38
  return { status: res.status, stdout: res.stdout ?? "" };
39
39
  };
40
+ const DEFAULT_CHANGED_PATH_DEBOUNCE_MS = 60_000;
40
41
  /**
41
42
  * Reindex qmd for an HQ tree after a sync. Never throws — all failures are
42
43
  * swallowed so a reindex problem can never mask or fail the sync result.
@@ -46,11 +47,29 @@ const defaultExec = (args) => {
46
47
  export function reindexAfterSync(hqRoot, opts = {}) {
47
48
  const exec = opts.exec ?? defaultExec;
48
49
  const existsSync = opts.existsSync ?? fs.existsSync;
49
- const result = { qmdAvailable: false, collectionsAdded: [], updated: false, embedded: false };
50
+ const result = {
51
+ qmdAvailable: false,
52
+ collectionsAdded: [],
53
+ updated: false,
54
+ embedded: false,
55
+ pendingDirty: false,
56
+ };
50
57
  try {
51
58
  // Guard: only operate on a real HQ tree.
52
59
  if (!existsSync(path.join(hqRoot, "core", "core.yaml")))
53
60
  return result;
61
+ const statePath = opts.statePath ?? path.join(hqRoot, ".hq", "qmd-reindex-state.json");
62
+ const state = readState(statePath);
63
+ const changedPaths = opts.changedPaths;
64
+ const dirtyFromChanges = changedPaths === undefined ||
65
+ changedPaths.some(isKnowledgeMarkdownPath);
66
+ const registrationMayBeStale = opts.forceCollectionRefresh === true ||
67
+ changedPaths === undefined ||
68
+ dirtyFromChanges;
69
+ const pendingDirty = state.pendingDirty === true;
70
+ if (!dirtyFromChanges && !registrationMayBeStale && !pendingDirty) {
71
+ return result;
72
+ }
54
73
  // Guard: qmd must be installed. `qmd collection list` doubles as the
55
74
  // availability probe AND the source for which collections already exist.
56
75
  const list = exec(["collection", "list"]);
@@ -59,27 +78,61 @@ export function reindexAfterSync(hqRoot, opts = {}) {
59
78
  result.qmdAvailable = true;
60
79
  const existingCollections = list.stdout;
61
80
  // 1. Auto-register missing company knowledge collections.
62
- const companiesDir = path.join(hqRoot, "companies");
63
- const slugs = (opts.readCompanies ?? defaultReadCompanies)(companiesDir);
64
- for (const slug of slugs) {
65
- const knowledgeDir = path.join(companiesDir, slug, "knowledge");
66
- if (!existsSync(knowledgeDir))
67
- continue;
68
- const hasMd = (opts.hasIndexableMarkdown ?? defaultHasIndexableMarkdown)(knowledgeDir);
69
- if (!hasMd)
70
- continue;
71
- // Already registered? qmd collection URIs look like `qmd://<slug>/`.
72
- if (existingCollections.includes(`qmd://${slug}/`))
73
- continue;
74
- const add = exec(["collection", "add", knowledgeDir, "--name", slug, "--mask", "**/*.md"]);
75
- if (add.status === 0) {
76
- exec(["context", "add", `qmd://${slug}`, `Knowledge base for ${slug}.`]);
77
- result.collectionsAdded.push(slug);
81
+ if (registrationMayBeStale) {
82
+ const companiesDir = path.join(hqRoot, "companies");
83
+ const slugs = (opts.readCompanies ?? defaultReadCompanies)(companiesDir);
84
+ for (const slug of slugs) {
85
+ const knowledgeDir = path.join(companiesDir, slug, "knowledge");
86
+ if (!existsSync(knowledgeDir))
87
+ continue;
88
+ const hasMd = (opts.hasIndexableMarkdown ?? defaultHasIndexableMarkdown)(knowledgeDir);
89
+ if (!hasMd)
90
+ continue;
91
+ // Already registered? qmd collection URIs look like `qmd://<slug>/`.
92
+ if (existingCollections.includes(`qmd://${slug}/`))
93
+ continue;
94
+ const add = exec(["collection", "add", knowledgeDir, "--name", slug, "--mask", "**/*.md"]);
95
+ if (add.status === 0) {
96
+ exec(["context", "add", `qmd://${slug}`, `Knowledge base for ${slug}.`]);
97
+ result.collectionsAdded.push(slug);
98
+ }
78
99
  }
79
100
  }
80
101
  // 2. Incremental lexical reindex.
81
- const update = exec(["update"]);
82
- result.updated = update.status === 0;
102
+ const shouldUpdate = dirtyFromChanges || pendingDirty;
103
+ if (shouldUpdate) {
104
+ const nowMs = opts.nowMs ?? Date.now();
105
+ const debounceMs = opts.debounceMs ??
106
+ (changedPaths === undefined ? 0 : DEFAULT_CHANGED_PATH_DEBOUNCE_MS);
107
+ const pendingSinceMs = pendingDirty && typeof state.pendingSinceMs === "number"
108
+ ? state.pendingSinceMs
109
+ : nowMs;
110
+ if (debounceMs > 0 && nowMs - pendingSinceMs < debounceMs) {
111
+ writeState(statePath, {
112
+ ...state,
113
+ pendingDirty: true,
114
+ pendingSinceMs,
115
+ });
116
+ result.pendingDirty = true;
117
+ return result;
118
+ }
119
+ const update = exec(["update"]);
120
+ result.updated = update.status === 0;
121
+ if (result.updated) {
122
+ writeState(statePath, {
123
+ pendingDirty: false,
124
+ lastSuccessMs: nowMs,
125
+ });
126
+ }
127
+ else {
128
+ writeState(statePath, {
129
+ ...state,
130
+ pendingDirty: true,
131
+ pendingSinceMs,
132
+ });
133
+ result.pendingDirty = true;
134
+ }
135
+ }
83
136
  // 3. Embeddings only on explicit request.
84
137
  if (opts.embed) {
85
138
  const embed = exec(["embed"]);
@@ -102,6 +155,27 @@ export function reindexAfterSync(hqRoot, opts = {}) {
102
155
  }
103
156
  return result;
104
157
  }
158
+ function readState(statePath) {
159
+ try {
160
+ return JSON.parse(fs.readFileSync(statePath, "utf8"));
161
+ }
162
+ catch {
163
+ return {};
164
+ }
165
+ }
166
+ function writeState(statePath, state) {
167
+ try {
168
+ fs.mkdirSync(path.dirname(statePath), { recursive: true });
169
+ fs.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n");
170
+ }
171
+ catch {
172
+ // State is an optimization/guarantee aid; QMD failures must not fail sync.
173
+ }
174
+ }
175
+ function isKnowledgeMarkdownPath(relPath) {
176
+ const normalized = relPath.split(path.sep).join("/");
177
+ return /^companies\/[^/]+\/knowledge\/.+\.md$/i.test(normalized);
178
+ }
105
179
  function defaultReadCompanies(companiesDir) {
106
180
  try {
107
181
  return fs
@@ -1 +1 @@
1
- {"version":3,"file":"qmd-reindex.js","sourceRoot":"","sources":["../src/qmd-reindex.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"qmd-reindex.js","sourceRoot":"","sources":["../src/qmd-reindex.ts"],"names":[],"mappings":"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"}
@@ -89,6 +89,53 @@ describe("reindexAfterSync", () => {
89
89
  expect(r.collectionsAdded).toEqual([]);
90
90
  expect(calls).toEqual([["collection", "list"], ["update"]]);
91
91
  });
92
+ it("skips qmd entirely when provided changed paths do not touch knowledge markdown", () => {
93
+ const { exec, calls } = fakeExec({ listStdout: "" });
94
+ const r = reindexAfterSync("/hq", {
95
+ exec,
96
+ existsSync: () => true,
97
+ changedPaths: ["companies/acme/docs/readme.txt"],
98
+ });
99
+ expect(r.qmdAvailable).toBe(false);
100
+ expect(r.updated).toBe(false);
101
+ expect(calls).toEqual([]);
102
+ });
103
+ it("debounces dirty updates with a pending marker and flushes the final update later", () => {
104
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "hq-qmd-debounce-"));
105
+ try {
106
+ const statePath = path.join(dir, "qmd-state.json");
107
+ const { exec, calls } = fakeExec({ listStdout: "acme (qmd://acme/)\n" });
108
+ const first = reindexAfterSync("/hq", {
109
+ exec,
110
+ existsSync: () => true,
111
+ changedPaths: ["companies/acme/knowledge/note.md"],
112
+ debounceMs: 60_000,
113
+ nowMs: 100_000,
114
+ statePath,
115
+ });
116
+ expect(first.updated).toBe(false);
117
+ expect(first.pendingDirty).toBe(true);
118
+ expect(calls).toEqual([["collection", "list"]]);
119
+ const second = reindexAfterSync("/hq", {
120
+ exec,
121
+ existsSync: () => true,
122
+ changedPaths: [],
123
+ debounceMs: 60_000,
124
+ nowMs: 161_000,
125
+ statePath,
126
+ });
127
+ expect(second.updated).toBe(true);
128
+ expect(second.pendingDirty).toBe(false);
129
+ expect(calls).toEqual([
130
+ ["collection", "list"],
131
+ ["collection", "list"],
132
+ ["update"],
133
+ ]);
134
+ }
135
+ finally {
136
+ fs.rmSync(dir, { recursive: true, force: true });
137
+ }
138
+ });
92
139
  it("runs embed only when embed:true", () => {
93
140
  const { exec, calls } = fakeExec({ listStdout: "" });
94
141
  const r = reindexAfterSync("/hq", {
@@ -1 +1 @@
1
- {"version":3,"file":"qmd-reindex.test.js","sourceRoot":"","sources":["../src/qmd-reindex.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"qmd-reindex.test.js","sourceRoot":"","sources":["../src/qmd-reindex.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cloud",
3
- "version": "6.13.3",
3
+ "version": "6.13.5",
4
4
  "description": "HQ by Indigo cloud sync engine — bidirectional S3 sync for mobile access",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -168,6 +168,7 @@ export async function executeCompanyFanout(options: {
168
168
  filesOutOfScope: 0,
169
169
  scopeOrphansRemoved: 0,
170
170
  scopeOrphansBlocked: 0,
171
+ changedPaths: [],
171
172
  };
172
173
 
173
174
  // Local (non-cloud) companies always sync to the personal vault. The
@@ -255,6 +256,7 @@ export async function executeCompanyFanout(options: {
255
256
  ...(options.operationLockAlreadyHeld
256
257
  ? { operationLockAlreadyHeld: true }
257
258
  : {}),
259
+ skipReindex: true,
258
260
  onEvent: tagAndEmit,
259
261
  });
260
262
  }
@@ -273,6 +275,12 @@ export async function executeCompanyFanout(options: {
273
275
 
274
276
  state.filesDownloaded = pullResult.filesDownloaded;
275
277
  state.bytesDownloaded = pullResult.bytesDownloaded;
278
+ state.filesTombstoned = pullResult.filesTombstoned;
279
+ state.scopeOrphansRemoved = pullResult.scopeOrphansRemoved;
280
+ state.changedPaths = normalizePullChangedPaths(
281
+ target,
282
+ pullResult.changedPaths,
283
+ );
276
284
  state.filesUploaded = pushResult.filesUploaded;
277
285
  state.bytesUploaded = pushResult.bytesUploaded;
278
286
  state.status = companyHadTransferError
@@ -342,6 +350,7 @@ export async function executeCompanyFanout(options: {
342
350
  filesOutOfScope: 0,
343
351
  scopeOrphansRemoved: 0,
344
352
  scopeOrphansBlocked: 0,
353
+ changedPaths: [],
345
354
  });
346
355
  options.emit({
347
356
  type: "error",
@@ -354,3 +363,15 @@ export async function executeCompanyFanout(options: {
354
363
 
355
364
  return { stateByCompany, errors, allConflicts };
356
365
  }
366
+
367
+ function normalizePullChangedPaths(
368
+ target: RunnerTarget,
369
+ changedPaths: string[] | undefined,
370
+ ): string[] {
371
+ const paths = changedPaths ?? [];
372
+ return paths.map((p) => {
373
+ const normalized = p.split(path.sep).join("/");
374
+ if (target.personalMode === true) return normalized;
375
+ return `companies/${target.slug}/${normalized}`;
376
+ });
377
+ }
@@ -8,6 +8,9 @@ export interface CompanyState {
8
8
  status: CompanyStatus;
9
9
  filesDownloaded: number;
10
10
  bytesDownloaded: number;
11
+ filesTombstoned: number;
12
+ scopeOrphansRemoved: number;
13
+ changedPaths: string[];
11
14
  filesUploaded: number;
12
15
  bytesUploaded: number;
13
16
  }
@@ -15,8 +18,12 @@ export interface CompanyState {
15
18
  export interface AllCompleteRollup {
16
19
  totalDownloaded: number;
17
20
  totalDownloadedBytes: number;
21
+ totalTombstoned: number;
22
+ totalScopeOrphansRemoved: number;
18
23
  totalUploaded: number;
19
24
  totalUploadedBytes: number;
25
+ changedPaths: string[];
26
+ needsReindex: boolean;
20
27
  partial: boolean;
21
28
  companies: Extract<RunnerEvent, { type: "all-complete" }>["companies"];
22
29
  }
@@ -27,6 +34,9 @@ export function createCompanyState(company: string): CompanyState {
27
34
  status: "errored",
28
35
  filesDownloaded: 0,
29
36
  bytesDownloaded: 0,
37
+ filesTombstoned: 0,
38
+ scopeOrphansRemoved: 0,
39
+ changedPaths: [],
30
40
  filesUploaded: 0,
31
41
  bytesUploaded: 0,
32
42
  };
@@ -38,8 +48,11 @@ export function rollupAllComplete(
38
48
  ): AllCompleteRollup {
39
49
  let totalDownloaded = 0;
40
50
  let totalDownloadedBytes = 0;
51
+ let totalTombstoned = 0;
52
+ let totalScopeOrphansRemoved = 0;
41
53
  let totalUploaded = 0;
42
54
  let totalUploadedBytes = 0;
55
+ const changedPaths: string[] = [];
43
56
  let partial = false;
44
57
  const companies: AllCompleteRollup["companies"] = [];
45
58
 
@@ -48,8 +61,11 @@ export function rollupAllComplete(
48
61
  if (!s) continue;
49
62
  totalDownloaded += s.filesDownloaded;
50
63
  totalDownloadedBytes += s.bytesDownloaded;
64
+ totalTombstoned += s.filesTombstoned;
65
+ totalScopeOrphansRemoved += s.scopeOrphansRemoved;
51
66
  totalUploaded += s.filesUploaded;
52
67
  totalUploadedBytes += s.bytesUploaded;
68
+ changedPaths.push(...s.changedPaths);
53
69
  if (s.status !== "complete") partial = true;
54
70
  companies.push({
55
71
  company: s.company,
@@ -64,8 +80,15 @@ export function rollupAllComplete(
64
80
  return {
65
81
  totalDownloaded,
66
82
  totalDownloadedBytes,
83
+ totalTombstoned,
84
+ totalScopeOrphansRemoved,
67
85
  totalUploaded,
68
86
  totalUploadedBytes,
87
+ changedPaths,
88
+ needsReindex:
89
+ totalDownloaded > 0 ||
90
+ totalTombstoned > 0 ||
91
+ totalScopeOrphansRemoved > 0,
69
92
  partial,
70
93
  companies,
71
94
  };
@@ -8,6 +8,7 @@ import {
8
8
  OPERATION_LOCKED_EXIT,
9
9
  } from "../operation-lock.js";
10
10
  import { describeError } from "../lib/describe-error.js";
11
+ import { TRANSIENT_NETWORK_EXIT } from "../lib/net-errors.js";
11
12
  import { getOrCreateMachineId } from "../lib/machine-id.js";
12
13
  import {
13
14
  TreeWatcher,
@@ -430,7 +431,16 @@ export async function runWatchLoop(
430
431
  try {
431
432
  while (!stopped) {
432
433
  const result = await runGuarded(passArgv);
433
- if (result !== 0) {
434
+ if (result === TRANSIENT_NETWORK_EXIT) {
435
+ // The machine was briefly offline (the discovery fetch failed after
436
+ // retries). That is NOT a crash — stay alive and retry on the next
437
+ // poll. Returning it here would exit the watcher and the menubar would
438
+ // report "watcher exited unexpectedly" for every network blip
439
+ // (HQ-SYNC-1W). Log to stderr so it still shows up as a breadcrumb.
440
+ process.stderr.write(
441
+ "hq-sync-runner: watch pass skipped — transient network failure; retrying next poll\n",
442
+ );
443
+ } else if (result !== 0) {
434
444
  return result;
435
445
  }
436
446
  await Promise.race([sleep(pollMs), stoppedSignal]);
@@ -35,6 +35,7 @@ import { FakeClock } from "../watcher.js";
35
35
  import { PERSONAL_VAULT_JOURNAL_SLUG } from "../journal.js";
36
36
  import { HQ_CLOUD_VERSION } from "../version.js";
37
37
  import { lockPathFor, OPERATION_LOCKED_EXIT } from "../operation-lock.js";
38
+ import { TRANSIENT_NETWORK_EXIT } from "../lib/net-errors.js";
38
39
  import type { SyncResult, SyncOptions } from "../cli/sync.js";
39
40
  import type { ShareResult, ShareOptions } from "../cli/share.js";
40
41
  import type {
@@ -112,6 +113,7 @@ function defaultSyncResult(overrides: Partial<SyncResult> = {}): SyncResult {
112
113
  filesOutOfScope: 0,
113
114
  scopeOrphansRemoved: 0,
114
115
  scopeOrphansBlocked: 0,
116
+ changedPaths: [],
115
117
  ...overrides,
116
118
  };
117
119
  }
@@ -205,6 +207,14 @@ function makeDeps(overrides: Partial<RunnerDeps> = {}): TestDeps {
205
207
  getAccessToken: vi.fn().mockResolvedValue("test-access-token"),
206
208
  createVaultClient: vi.fn().mockImplementation(() => makeVaultStub()),
207
209
  sync: vi.fn().mockResolvedValue(defaultSyncResult()),
210
+ reindex: vi.fn(() => ({ status: 0 })),
211
+ qmdReindex: vi.fn(() => ({
212
+ qmdAvailable: true,
213
+ collectionsAdded: [],
214
+ updated: true,
215
+ embedded: false,
216
+ pendingDirty: false,
217
+ })),
208
218
  ...overrides,
209
219
  stdout,
210
220
  stderr,
@@ -388,6 +398,49 @@ describe("memberships retry", () => {
388
398
  });
389
399
  });
390
400
 
401
+ it("a transient network failure (fetch failed) during discovery returns TRANSIENT_NETWORK_EXIT, not 1 (HQ-SYNC-1W)", async () => {
402
+ // Regression for the "auto-sync watcher exited unexpectedly (code=Some(1))"
403
+ // cluster: a machine that stays offline through all 3 membership retries
404
+ // used to `return 1`, which the watch loop propagated and the menubar
405
+ // reported as a crash for every network blip. undici surfaces the blip as
406
+ // `TypeError: fetch failed`; that must map to the retryable exit code so the
407
+ // watcher stays alive instead of crash-reporting. A one-shot run still sees
408
+ // a non-zero exit.
409
+ let listCalls = 0;
410
+ const stub = makeVaultStub();
411
+ stub.listMyMemberships = () => {
412
+ listCalls++;
413
+ return Promise.reject(new TypeError("fetch failed"));
414
+ };
415
+ const deps = makeDeps({ createVaultClient: () => stub });
416
+ const code = await runRunner(["--companies"], deps);
417
+ expect(code).toBe(TRANSIENT_NETWORK_EXIT);
418
+ expect(listCalls).toBe(3);
419
+ // Still surfaced for observability — just no longer a "code 1" crash.
420
+ const events = deps.stderr.events();
421
+ expect(events).toHaveLength(1);
422
+ expect(events[0]).toMatchObject({
423
+ type: "error",
424
+ message: "fetch failed",
425
+ path: "(discovery)",
426
+ });
427
+ });
428
+
429
+ it("a NON-transient discovery failure still returns 1 (unchanged)", async () => {
430
+ // Guard the conservative classifier: a generic error (not a transport
431
+ // shape) is still a hard exit 1, exactly as before.
432
+ let listCalls = 0;
433
+ const stub = makeVaultStub();
434
+ stub.listMyMemberships = () => {
435
+ listCalls++;
436
+ return Promise.reject(new Error("unexpected 500 from vault api"));
437
+ };
438
+ const deps = makeDeps({ createVaultClient: () => stub });
439
+ const code = await runRunner(["--companies"], deps);
440
+ expect(code).toBe(1);
441
+ expect(listCalls).toBe(3);
442
+ });
443
+
391
444
  it("VaultAuthError short-circuits retry (no retries on auth failure)", async () => {
392
445
  let listCalls = 0;
393
446
  const stub = makeVaultStub();
@@ -1602,6 +1655,87 @@ describe("all-complete aggregate", () => {
1602
1655
  });
1603
1656
  });
1604
1657
 
1658
+ it("skips per-target native reindex and runs one final reindex for tombstones or scope orphans", async () => {
1659
+ const slugs: Record<string, string> = { cmp_a: "acme", cmp_b: "beta" };
1660
+ const reindex = vi.fn(() => ({ status: 0 }));
1661
+ const syncSpy = vi
1662
+ .fn<(opts: SyncOptions) => Promise<SyncResult>>()
1663
+ .mockResolvedValueOnce(defaultSyncResult({ filesTombstoned: 1 }))
1664
+ .mockResolvedValueOnce(defaultSyncResult({ scopeOrphansRemoved: 1 }));
1665
+ const deps = makeDeps({
1666
+ operationLockAlreadyHeld: true,
1667
+ createVaultClient: () =>
1668
+ makeVaultStub({
1669
+ memberships: [{ companyUid: "cmp_a" }, { companyUid: "cmp_b" }],
1670
+ entityGet: (uid: string) =>
1671
+ Promise.resolve({ uid, slug: slugs[uid] ?? uid } as unknown as EntityInfo),
1672
+ }),
1673
+ sync: syncSpy,
1674
+ reindex,
1675
+ } as Partial<RunnerDeps>);
1676
+
1677
+ const code = await runRunner(["--companies", "--hq-root", "/tmp/fake-hq"], deps);
1678
+
1679
+ expect(code).toBe(0);
1680
+ expect(syncSpy).toHaveBeenCalledTimes(2);
1681
+ expect(syncSpy.mock.calls.map(([opts]) => opts.skipReindex)).toEqual([
1682
+ true,
1683
+ true,
1684
+ ]);
1685
+ expect(reindex).toHaveBeenCalledTimes(1);
1686
+ expect(reindex).toHaveBeenCalledWith({
1687
+ repoRoot: "/tmp/fake-hq",
1688
+ skipLock: true,
1689
+ });
1690
+ });
1691
+
1692
+ it("gates qmd to knowledge markdown changes and propagates hq-relative changed paths", async () => {
1693
+ const slugs: Record<string, string> = { cmp_a: "acme", cmp_b: "beta" };
1694
+ const qmdReindex = vi.fn(() => ({
1695
+ qmdAvailable: true,
1696
+ collectionsAdded: [],
1697
+ updated: true,
1698
+ embedded: false,
1699
+ pendingDirty: false,
1700
+ }));
1701
+ const deps = makeDeps({
1702
+ createVaultClient: () =>
1703
+ makeVaultStub({
1704
+ memberships: [{ companyUid: "cmp_a" }, { companyUid: "cmp_b" }],
1705
+ entityGet: (uid: string) =>
1706
+ Promise.resolve({ uid, slug: slugs[uid] ?? uid } as unknown as EntityInfo),
1707
+ }),
1708
+ sync: vi
1709
+ .fn<(opts: SyncOptions) => Promise<SyncResult>>()
1710
+ .mockResolvedValueOnce(
1711
+ defaultSyncResult({
1712
+ filesDownloaded: 1,
1713
+ changedPaths: ["docs/readme.txt"],
1714
+ } as Partial<SyncResult>),
1715
+ )
1716
+ .mockResolvedValueOnce(
1717
+ defaultSyncResult({
1718
+ filesTombstoned: 1,
1719
+ changedPaths: ["knowledge/old.md"],
1720
+ } as Partial<SyncResult>),
1721
+ ),
1722
+ reindex: vi.fn(() => ({ status: 0 })),
1723
+ qmdReindex,
1724
+ } as Partial<RunnerDeps>);
1725
+
1726
+ const code = await runRunner(["--companies", "--hq-root", "/tmp/fake-hq"], deps);
1727
+
1728
+ expect(code).toBe(0);
1729
+ expect(qmdReindex).toHaveBeenCalledTimes(1);
1730
+ expect(qmdReindex).toHaveBeenCalledWith(
1731
+ "/tmp/fake-hq",
1732
+ expect.objectContaining({
1733
+ changedPaths: ["companies/beta/knowledge/old.md"],
1734
+ forceCollectionRefresh: true,
1735
+ }),
1736
+ );
1737
+ });
1738
+
1605
1739
  it("collects company-level errors into the all-complete errors array", async () => {
1606
1740
  const slugs: Record<string, string> = { cmp_a: "acme", cmp_b: "beta" };
1607
1741
  const deps = makeDeps({
@@ -3217,6 +3351,39 @@ describe("runRunnerWithLoop — event-push wiring", () => {
3217
3351
  expect(watcher.disposed).toBe(true);
3218
3352
  });
3219
3353
 
3354
+ it("a TRANSIENT_NETWORK_EXIT poll pass does NOT surface as a crash — the loop retries (HQ-SYNC-1W)", async () => {
3355
+ // A transient offline blip must keep the watcher alive: the loop logs and
3356
+ // polls again instead of returning the code and letting the process exit
3357
+ // (which the menubar reports as "watcher exited unexpectedly"). A genuine
3358
+ // hard error on a later pass still terminates the loop.
3359
+ const watcher = makeWatcherStub();
3360
+ let n = 0;
3361
+ const runPass = vi.fn().mockImplementation(async () => {
3362
+ n++;
3363
+ // First pass: machine is offline (transient). Second pass: a real hard
3364
+ // error, used here only to end the otherwise-infinite loop deterministically.
3365
+ return n === 1 ? TRANSIENT_NETWORK_EXIT : 2;
3366
+ });
3367
+
3368
+ const code = await runRunnerWithLoop(
3369
+ ["--companies", "--watch", "--event-push", "--hq-root", "/tmp/hq"],
3370
+ {
3371
+ runPass,
3372
+ clock: new FakeClock(),
3373
+ createWatcher: () => watcher,
3374
+ sleep: () => Promise.resolve(),
3375
+ onShutdownSignal: () => () => {},
3376
+ },
3377
+ );
3378
+
3379
+ // The transient pass did NOT end the loop — it ran again…
3380
+ expect(runPass).toHaveBeenCalledTimes(2);
3381
+ // …and the transient code was never surfaced as the watcher's exit code.
3382
+ expect(code).toBe(2);
3383
+ expect(code).not.toBe(TRANSIENT_NETWORK_EXIT);
3384
+ expect(watcher.disposed).toBe(true);
3385
+ });
3386
+
3220
3387
  it("without --event-push, no watcher is created (poll-only safety net)", async () => {
3221
3388
  let triggerShutdown = () => {};
3222
3389
  const createWatcher = vi.fn(() => makeWatcherStub());