@indigoai-us/hq-cloud 6.15.3 → 6.15.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 (150) 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-company.test.js +71 -0
  5. package/dist/bin/sync-runner-company.test.js.map +1 -1
  6. package/dist/bin/sync-runner-events.test.js +21 -0
  7. package/dist/bin/sync-runner-events.test.js.map +1 -1
  8. package/dist/bin/sync-runner.d.ts +5 -0
  9. package/dist/bin/sync-runner.d.ts.map +1 -1
  10. package/dist/bin/sync-runner.js.map +1 -1
  11. package/dist/cli/push-scan-exclusions.test.d.ts +2 -0
  12. package/dist/cli/push-scan-exclusions.test.d.ts.map +1 -0
  13. package/dist/cli/push-scan-exclusions.test.js +87 -0
  14. package/dist/cli/push-scan-exclusions.test.js.map +1 -0
  15. package/dist/cli/share.d.ts.map +1 -1
  16. package/dist/cli/share.js +44 -7
  17. package/dist/cli/share.js.map +1 -1
  18. package/dist/cli/share.test.js +136 -1
  19. package/dist/cli/share.test.js.map +1 -1
  20. package/dist/cli/sync.d.ts +47 -0
  21. package/dist/cli/sync.d.ts.map +1 -1
  22. package/dist/cli/sync.js +46 -1
  23. package/dist/cli/sync.js.map +1 -1
  24. package/dist/cli/sync.test.js +153 -0
  25. package/dist/cli/sync.test.js.map +1 -1
  26. package/dist/outposts/billing.d.ts +41 -0
  27. package/dist/outposts/billing.d.ts.map +1 -0
  28. package/dist/outposts/billing.js +53 -0
  29. package/dist/outposts/billing.js.map +1 -0
  30. package/dist/outposts/cli-billing.d.ts +59 -0
  31. package/dist/outposts/cli-billing.d.ts.map +1 -0
  32. package/dist/outposts/cli-billing.js +93 -0
  33. package/dist/outposts/cli-billing.js.map +1 -0
  34. package/dist/outposts/cli.d.ts +13 -0
  35. package/dist/outposts/cli.d.ts.map +1 -0
  36. package/dist/outposts/cli.js +13 -0
  37. package/dist/outposts/cli.js.map +1 -0
  38. package/dist/outposts/client.d.ts +140 -0
  39. package/dist/outposts/client.d.ts.map +1 -0
  40. package/dist/outposts/client.js +271 -0
  41. package/dist/outposts/client.js.map +1 -0
  42. package/dist/outposts/client.test.d.ts +9 -0
  43. package/dist/outposts/client.test.d.ts.map +1 -0
  44. package/dist/outposts/client.test.js +226 -0
  45. package/dist/outposts/client.test.js.map +1 -0
  46. package/dist/outposts/command.d.ts +74 -0
  47. package/dist/outposts/command.d.ts.map +1 -0
  48. package/dist/outposts/command.js +661 -0
  49. package/dist/outposts/command.js.map +1 -0
  50. package/dist/outposts/command.test.d.ts +18 -0
  51. package/dist/outposts/command.test.d.ts.map +1 -0
  52. package/dist/outposts/command.test.js +933 -0
  53. package/dist/outposts/command.test.js.map +1 -0
  54. package/dist/outposts/errors.d.ts +32 -0
  55. package/dist/outposts/errors.d.ts.map +1 -0
  56. package/dist/outposts/errors.js +72 -0
  57. package/dist/outposts/errors.js.map +1 -0
  58. package/dist/outposts/heartbeat-command.d.ts +106 -0
  59. package/dist/outposts/heartbeat-command.d.ts.map +1 -0
  60. package/dist/outposts/heartbeat-command.js +186 -0
  61. package/dist/outposts/heartbeat-command.js.map +1 -0
  62. package/dist/outposts/host-deps.d.ts +49 -0
  63. package/dist/outposts/host-deps.d.ts.map +1 -0
  64. package/dist/outposts/host-deps.js +93 -0
  65. package/dist/outposts/host-deps.js.map +1 -0
  66. package/dist/outposts/index.d.ts +36 -0
  67. package/dist/outposts/index.d.ts.map +1 -0
  68. package/dist/outposts/index.js +32 -0
  69. package/dist/outposts/index.js.map +1 -0
  70. package/dist/outposts/node.d.ts +23 -0
  71. package/dist/outposts/node.d.ts.map +1 -0
  72. package/dist/outposts/node.js +22 -0
  73. package/dist/outposts/node.js.map +1 -0
  74. package/dist/outposts/remote-command.d.ts +30 -0
  75. package/dist/outposts/remote-command.d.ts.map +1 -0
  76. package/dist/outposts/remote-command.js +39 -0
  77. package/dist/outposts/remote-command.js.map +1 -0
  78. package/dist/outposts/replica-sync.d.ts +40 -0
  79. package/dist/outposts/replica-sync.d.ts.map +1 -0
  80. package/dist/outposts/replica-sync.js +188 -0
  81. package/dist/outposts/replica-sync.js.map +1 -0
  82. package/dist/outposts/replica-sync.test.d.ts +2 -0
  83. package/dist/outposts/replica-sync.test.d.ts.map +1 -0
  84. package/dist/outposts/replica-sync.test.js +224 -0
  85. package/dist/outposts/replica-sync.test.js.map +1 -0
  86. package/dist/outposts/self-deploy.d.ts +47 -0
  87. package/dist/outposts/self-deploy.d.ts.map +1 -0
  88. package/dist/outposts/self-deploy.js +188 -0
  89. package/dist/outposts/self-deploy.js.map +1 -0
  90. package/dist/outposts/self-deploy.test.d.ts +2 -0
  91. package/dist/outposts/self-deploy.test.d.ts.map +1 -0
  92. package/dist/outposts/self-deploy.test.js +186 -0
  93. package/dist/outposts/self-deploy.test.js.map +1 -0
  94. package/dist/outposts/session-heartbeat-bounds.test.d.ts +25 -0
  95. package/dist/outposts/session-heartbeat-bounds.test.d.ts.map +1 -0
  96. package/dist/outposts/session-heartbeat-bounds.test.js +156 -0
  97. package/dist/outposts/session-heartbeat-bounds.test.js.map +1 -0
  98. package/dist/outposts/session-heartbeat-guard.test.d.ts +16 -0
  99. package/dist/outposts/session-heartbeat-guard.test.d.ts.map +1 -0
  100. package/dist/outposts/session-heartbeat-guard.test.js +88 -0
  101. package/dist/outposts/session-heartbeat-guard.test.js.map +1 -0
  102. package/dist/outposts/session-heartbeat-publisher.d.ts +76 -0
  103. package/dist/outposts/session-heartbeat-publisher.d.ts.map +1 -0
  104. package/dist/outposts/session-heartbeat-publisher.js +117 -0
  105. package/dist/outposts/session-heartbeat-publisher.js.map +1 -0
  106. package/dist/outposts/session-heartbeat-publisher.test.d.ts +10 -0
  107. package/dist/outposts/session-heartbeat-publisher.test.d.ts.map +1 -0
  108. package/dist/outposts/session-heartbeat-publisher.test.js +153 -0
  109. package/dist/outposts/session-heartbeat-publisher.test.js.map +1 -0
  110. package/dist/outposts/session-heartbeat-retain-guard.test.d.ts +18 -0
  111. package/dist/outposts/session-heartbeat-retain-guard.test.d.ts.map +1 -0
  112. package/dist/outposts/session-heartbeat-retain-guard.test.js +106 -0
  113. package/dist/outposts/session-heartbeat-retain-guard.test.js.map +1 -0
  114. package/dist/outposts/session-heartbeat.d.ts +210 -0
  115. package/dist/outposts/session-heartbeat.d.ts.map +1 -0
  116. package/dist/outposts/session-heartbeat.js +657 -0
  117. package/dist/outposts/session-heartbeat.js.map +1 -0
  118. package/dist/outposts/session-heartbeat.test.d.ts +11 -0
  119. package/dist/outposts/session-heartbeat.test.d.ts.map +1 -0
  120. package/dist/outposts/session-heartbeat.test.js +370 -0
  121. package/dist/outposts/session-heartbeat.test.js.map +1 -0
  122. package/dist/outposts/ssh.d.ts +29 -0
  123. package/dist/outposts/ssh.d.ts.map +1 -0
  124. package/dist/outposts/ssh.js +66 -0
  125. package/dist/outposts/ssh.js.map +1 -0
  126. package/dist/outposts/transport.d.ts +76 -0
  127. package/dist/outposts/transport.d.ts.map +1 -0
  128. package/dist/outposts/transport.js +91 -0
  129. package/dist/outposts/transport.js.map +1 -0
  130. package/dist/outposts/transport.test.d.ts +10 -0
  131. package/dist/outposts/transport.test.d.ts.map +1 -0
  132. package/dist/outposts/transport.test.js +116 -0
  133. package/dist/outposts/transport.test.js.map +1 -0
  134. package/dist/outposts/types.d.ts +125 -0
  135. package/dist/outposts/types.d.ts.map +1 -0
  136. package/dist/outposts/types.js +13 -0
  137. package/dist/outposts/types.js.map +1 -0
  138. package/dist/personal-vault.d.ts +69 -0
  139. package/dist/personal-vault.d.ts.map +1 -1
  140. package/dist/personal-vault.js +100 -0
  141. package/dist/personal-vault.js.map +1 -1
  142. package/dist/personal-vault.test.js +140 -1
  143. package/dist/personal-vault.test.js.map +1 -1
  144. package/dist/s3.d.ts +36 -0
  145. package/dist/s3.d.ts.map +1 -1
  146. package/dist/s3.js +61 -1
  147. package/dist/s3.js.map +1 -1
  148. package/dist/s3.symlink-materialize.test.js +161 -1
  149. package/dist/s3.symlink-materialize.test.js.map +1 -1
  150. package/package.json +39 -3
@@ -118,6 +118,7 @@ import * as s3Module from "../s3.js";
118
118
  import { reindex } from "./reindex.js";
119
119
  import { readSyncProgress } from "../sync-progress.js";
120
120
  import { VaultAuthError } from "../vault-client.js";
121
+ import { readJournal, getEntry } from "../journal.js";
121
122
  const mockConfig = {
122
123
  apiUrl: "https://vault-api.test",
123
124
  authToken: "test-jwt-token",
@@ -266,6 +267,158 @@ describe("sync", () => {
266
267
  expect(result.filesDownloaded).toBe(2);
267
268
  expect(reindex).not.toHaveBeenCalled();
268
269
  });
270
+ it("HQ-DESKTOP-54: a Windows symlink-privilege refusal is a deliberate skip (never type:error) and is left unjournaled", async () => {
271
+ // On a Windows host without SeCreateSymbolicLinkPrivilege, materializing a
272
+ // file-flavored vault symlink throws EPERM, which s3 surfaces as a typed
273
+ // WindowsSymlinkPrivilegeError. downloadOne must downgrade it to the
274
+ // dedicated skip event — NOT type:"error" — so the runner never counts it
275
+ // toward errors[] and never exits 2. The refused key must also stay
276
+ // unjournaled so a later privileged pass retries it.
277
+ vi.mocked(s3Module.listRemoteFiles).mockResolvedValueOnce([
278
+ { key: "docs/skill-link.md", size: 10, lastModified: new Date(), etag: '"link"' },
279
+ ]);
280
+ vi.mocked(s3Module.downloadFile).mockRejectedValueOnce(new s3Module.WindowsSymlinkPrivilegeError("../target.md", "EPERM"));
281
+ const events = [];
282
+ const result = await sync({
283
+ company: "acme",
284
+ vaultConfig: mockConfig,
285
+ hqRoot: tmpDir,
286
+ skipReindex: true,
287
+ onEvent: (e) => events.push(e),
288
+ });
289
+ const skip = events.find((e) => e.type === "skip-symlink-privilege");
290
+ expect(skip).toMatchObject({
291
+ type: "skip-symlink-privilege",
292
+ path: "docs/skill-link.md",
293
+ target: "../target.md",
294
+ });
295
+ expect(skip.remedy).toMatch(/Developer Mode/i);
296
+ // Crucially, no error event — this is what keeps the runner at exit 0.
297
+ expect(events.some((e) => e.type === "error")).toBe(false);
298
+ expect(result.filesSkipped).toBeGreaterThanOrEqual(1);
299
+ expect(result.filesDownloaded).toBe(0);
300
+ // Convergence: the refused key is NOT journaled.
301
+ expect(getEntry(readJournal("acme"), "docs/skill-link.md")).toBeUndefined();
302
+ });
303
+ it("HQ-DESKTOP-54 convergence: after a refused pass, the next pass that CAN materialize the link journals it (no manual reset)", async () => {
304
+ const remote = [
305
+ { key: "docs/skill-link.md", size: 10, lastModified: new Date(), etag: '"link"' },
306
+ ];
307
+ // Pass 1 — privilege refusal: skipped, not journaled.
308
+ vi.mocked(s3Module.listRemoteFiles).mockResolvedValueOnce(remote);
309
+ vi.mocked(s3Module.downloadFile).mockRejectedValueOnce(new s3Module.WindowsSymlinkPrivilegeError("../target.md", "EPERM"));
310
+ await sync({
311
+ company: "acme",
312
+ vaultConfig: mockConfig,
313
+ hqRoot: tmpDir,
314
+ skipReindex: true,
315
+ });
316
+ expect(getEntry(readJournal("acme"), "docs/skill-link.md")).toBeUndefined();
317
+ // Pass 2 — the privilege now exists: the symlink materializes and journals.
318
+ vi.mocked(s3Module.listRemoteFiles).mockResolvedValueOnce(remote);
319
+ vi.mocked(s3Module.downloadFile).mockImplementationOnce(async (_ctx, _key, localPath) => {
320
+ fs.mkdirSync(path.dirname(localPath), { recursive: true });
321
+ fs.symlinkSync("../target.md", localPath);
322
+ return { metadata: {} };
323
+ });
324
+ const result = await sync({
325
+ company: "acme",
326
+ vaultConfig: mockConfig,
327
+ hqRoot: tmpDir,
328
+ skipReindex: true,
329
+ });
330
+ expect(result.filesDownloaded).toBe(1);
331
+ const entry = getEntry(readJournal("acme"), "docs/skill-link.md");
332
+ expect(entry).toBeDefined();
333
+ expect(entry.kind).toBe("symlink");
334
+ });
335
+ it("an unrelated download failure still surfaces as type:error — only the privilege refusal is downgraded", async () => {
336
+ vi.mocked(s3Module.listRemoteFiles).mockResolvedValueOnce([
337
+ { key: "docs/x.md", size: 10, lastModified: new Date(), etag: '"x"' },
338
+ ]);
339
+ vi.mocked(s3Module.downloadFile).mockRejectedValueOnce(new Error("network boom"));
340
+ const events = [];
341
+ await sync({
342
+ company: "acme",
343
+ vaultConfig: mockConfig,
344
+ hqRoot: tmpDir,
345
+ skipReindex: true,
346
+ onEvent: (e) => events.push(e),
347
+ });
348
+ const errs = events.filter((e) => e.type === "error");
349
+ expect(errs).toHaveLength(1);
350
+ expect(errs[0].path).toBe("docs/x.md");
351
+ expect(events.some((e) => e.type === "skip-symlink-privilege")).toBe(false);
352
+ });
353
+ it("HQ-DESKTOP-54: a privilege refusal during the conflict-convergence probe is a skip, not an error or a counted conflict", async () => {
354
+ // The conflict path also materializes the remote object (to compare it
355
+ // against the diverged local copy). On an unprivileged Windows host that
356
+ // materialization throws the same typed error; it must be downgraded to the
357
+ // deliberate skip so the run stays complete and exits 0 — never type:error,
358
+ // and not counted as an unresolved conflict.
359
+ const companyDocs = path.join(tmpDir, "companies", "acme", "docs");
360
+ fs.mkdirSync(companyDocs, { recursive: true });
361
+ fs.writeFileSync(path.join(companyDocs, "handoff.md"), "local version");
362
+ fs.writeFileSync(journalPath, JSON.stringify({
363
+ version: "1",
364
+ lastSync: new Date().toISOString(),
365
+ files: {
366
+ "docs/handoff.md": {
367
+ hash: "old-hash-from-last-sync",
368
+ size: 20,
369
+ syncedAt: new Date(Date.now() - 3600000).toISOString(),
370
+ direction: "down",
371
+ },
372
+ },
373
+ }));
374
+ // Remote changed too → the planner classifies this as a conflict, so the
375
+ // convergence probe (a downloadFile) runs and refuses with the typed error.
376
+ vi.mocked(s3Module.listRemoteFiles).mockResolvedValueOnce([
377
+ { key: "docs/handoff.md", size: 20, lastModified: new Date(), etag: '"remote-changed"' },
378
+ ]);
379
+ vi.mocked(s3Module.downloadFile).mockRejectedValueOnce(new s3Module.WindowsSymlinkPrivilegeError("../target.md", "EPERM"));
380
+ const events = [];
381
+ const result = await sync({
382
+ company: "acme",
383
+ onConflict: "keep",
384
+ vaultConfig: mockConfig,
385
+ hqRoot: tmpDir,
386
+ skipReindex: true,
387
+ onEvent: (e) => events.push(e),
388
+ });
389
+ expect(events).toContainEqual(expect.objectContaining({
390
+ type: "skip-symlink-privilege",
391
+ path: "docs/handoff.md",
392
+ target: "../target.md",
393
+ }));
394
+ expect(events.some((e) => e.type === "error")).toBe(false);
395
+ // Skipped, so it is NOT tallied as an unresolved conflict.
396
+ expect(result.conflicts).toBe(0);
397
+ expect(result.filesSkipped).toBeGreaterThanOrEqual(1);
398
+ });
399
+ it("HQ-DESKTOP-54: the default CLI console logger renders the symlink-privilege skip and its remedy", async () => {
400
+ // With no onEvent (ordinary `hq sync`), the run uses defaultConsoleLogger.
401
+ // The skipped path and remediation must reach the user, not vanish.
402
+ vi.mocked(s3Module.listRemoteFiles).mockResolvedValueOnce([
403
+ { key: "docs/skill-link.md", size: 10, lastModified: new Date(), etag: '"link"' },
404
+ ]);
405
+ vi.mocked(s3Module.downloadFile).mockRejectedValueOnce(new s3Module.WindowsSymlinkPrivilegeError("../target.md", "EPERM"));
406
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { });
407
+ try {
408
+ await sync({
409
+ company: "acme",
410
+ vaultConfig: mockConfig,
411
+ hqRoot: tmpDir,
412
+ skipReindex: true,
413
+ });
414
+ const rendered = warnSpy.mock.calls.map((c) => String(c[0])).join("\n");
415
+ expect(rendered).toContain("docs/skill-link.md");
416
+ expect(rendered).toMatch(/Developer Mode/i);
417
+ }
418
+ finally {
419
+ warnSpy.mockRestore();
420
+ }
421
+ });
269
422
  it("downloads remote files under companies/{slug}/ so two companies don't collide", async () => {
270
423
  const result = await sync({
271
424
  company: "acme",