@indigoai-us/hq-cloud 6.16.58-alpha.3 → 6.16.59

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 (154) hide show
  1. package/dist/bin/sync-runner-company.d.ts +0 -14
  2. package/dist/bin/sync-runner-company.d.ts.map +1 -1
  3. package/dist/bin/sync-runner-company.js +89 -57
  4. package/dist/bin/sync-runner-company.js.map +1 -1
  5. package/dist/bin/sync-runner-company.test.js +313 -56
  6. package/dist/bin/sync-runner-company.test.js.map +1 -1
  7. package/dist/bin/sync-runner-planning.d.ts +3 -12
  8. package/dist/bin/sync-runner-planning.d.ts.map +1 -1
  9. package/dist/bin/sync-runner-planning.js +3 -65
  10. package/dist/bin/sync-runner-planning.js.map +1 -1
  11. package/dist/bin/sync-runner-planning.test.js +1 -172
  12. package/dist/bin/sync-runner-planning.test.js.map +1 -1
  13. package/dist/bin/sync-runner-watch-loop.d.ts +2 -51
  14. package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
  15. package/dist/bin/sync-runner-watch-loop.js +165 -409
  16. package/dist/bin/sync-runner-watch-loop.js.map +1 -1
  17. package/dist/bin/sync-runner-watch-routes.d.ts +2 -19
  18. package/dist/bin/sync-runner-watch-routes.d.ts.map +1 -1
  19. package/dist/bin/sync-runner-watch-routes.js +5 -40
  20. package/dist/bin/sync-runner-watch-routes.js.map +1 -1
  21. package/dist/bin/sync-runner-watch-routes.test.js +0 -47
  22. package/dist/bin/sync-runner-watch-routes.test.js.map +1 -1
  23. package/dist/bin/sync-runner.d.ts +13 -59
  24. package/dist/bin/sync-runner.d.ts.map +1 -1
  25. package/dist/bin/sync-runner.js +4 -48
  26. package/dist/bin/sync-runner.js.map +1 -1
  27. package/dist/bin/sync-runner.test.js +229 -1499
  28. package/dist/bin/sync-runner.test.js.map +1 -1
  29. package/dist/cli/share.d.ts +0 -19
  30. package/dist/cli/share.d.ts.map +1 -1
  31. package/dist/cli/share.js +0 -55
  32. package/dist/cli/share.js.map +1 -1
  33. package/dist/cli/share.test.js +1 -87
  34. package/dist/cli/share.test.js.map +1 -1
  35. package/dist/cli/sync-scope.test.js +0 -8
  36. package/dist/cli/sync-scope.test.js.map +1 -1
  37. package/dist/cli/sync.d.ts +8 -12
  38. package/dist/cli/sync.d.ts.map +1 -1
  39. package/dist/cli/sync.js +152 -302
  40. package/dist/cli/sync.js.map +1 -1
  41. package/dist/cli/sync.test.js +126 -256
  42. package/dist/cli/sync.test.js.map +1 -1
  43. package/dist/cognito-auth.d.ts +34 -1
  44. package/dist/cognito-auth.d.ts.map +1 -1
  45. package/dist/cognito-auth.js +92 -21
  46. package/dist/cognito-auth.js.map +1 -1
  47. package/dist/context.d.ts +5 -0
  48. package/dist/context.d.ts.map +1 -1
  49. package/dist/context.js +92 -12
  50. package/dist/context.js.map +1 -1
  51. package/dist/context.test.js +77 -2
  52. package/dist/context.test.js.map +1 -1
  53. package/dist/ignore.d.ts +10 -0
  54. package/dist/ignore.d.ts.map +1 -1
  55. package/dist/ignore.js +47 -5
  56. package/dist/ignore.js.map +1 -1
  57. package/dist/ignore.test.js +30 -3
  58. package/dist/ignore.test.js.map +1 -1
  59. package/dist/journal-row-store.d.ts +5 -40
  60. package/dist/journal-row-store.d.ts.map +1 -1
  61. package/dist/journal-row-store.js +21 -240
  62. package/dist/journal-row-store.js.map +1 -1
  63. package/dist/journal-row-store.test.js +0 -54
  64. package/dist/journal-row-store.test.js.map +1 -1
  65. package/dist/journal-snapshot-v4.d.ts.map +1 -1
  66. package/dist/journal-snapshot-v4.js +6 -11
  67. package/dist/journal-snapshot-v4.js.map +1 -1
  68. package/dist/journal-store.test.js +7 -324
  69. package/dist/journal-store.test.js.map +1 -1
  70. package/dist/journal.d.ts +16 -51
  71. package/dist/journal.d.ts.map +1 -1
  72. package/dist/journal.js +79 -303
  73. package/dist/journal.js.map +1 -1
  74. package/dist/lib/exit-codes.d.ts +0 -15
  75. package/dist/lib/exit-codes.d.ts.map +1 -1
  76. package/dist/lib/exit-codes.js +0 -15
  77. package/dist/lib/exit-codes.js.map +1 -1
  78. package/dist/lib/net-errors.d.ts +12 -0
  79. package/dist/lib/net-errors.d.ts.map +1 -1
  80. package/dist/lib/net-errors.js +35 -0
  81. package/dist/lib/net-errors.js.map +1 -1
  82. package/dist/lib/net-errors.test.js +19 -1
  83. package/dist/lib/net-errors.test.js.map +1 -1
  84. package/dist/machine-auth.test.js +6 -4
  85. package/dist/machine-auth.test.js.map +1 -1
  86. package/dist/machine-token-cache-isolation.test.d.ts +24 -0
  87. package/dist/machine-token-cache-isolation.test.d.ts.map +1 -0
  88. package/dist/machine-token-cache-isolation.test.js +241 -0
  89. package/dist/machine-token-cache-isolation.test.js.map +1 -0
  90. package/dist/manifest/build-manifest-resident-parity.test.js +3 -3
  91. package/dist/manifest/build-manifest-resident-parity.test.js.map +1 -1
  92. package/dist/personal-vault.d.ts.map +1 -1
  93. package/dist/personal-vault.js +3 -10
  94. package/dist/personal-vault.js.map +1 -1
  95. package/dist/personal-vault.test.js +0 -35
  96. package/dist/personal-vault.test.js.map +1 -1
  97. package/dist/remote-pull.d.ts.map +1 -1
  98. package/dist/remote-pull.js +2 -9
  99. package/dist/remote-pull.js.map +1 -1
  100. package/dist/remote-pull.test.js +2 -59
  101. package/dist/remote-pull.test.js.map +1 -1
  102. package/dist/s3.d.ts +0 -1
  103. package/dist/s3.d.ts.map +1 -1
  104. package/dist/s3.js +2 -20
  105. package/dist/s3.js.map +1 -1
  106. package/dist/sync/area-ledger.d.ts +3 -27
  107. package/dist/sync/area-ledger.d.ts.map +1 -1
  108. package/dist/sync/area-ledger.js +39 -149
  109. package/dist/sync/area-ledger.js.map +1 -1
  110. package/dist/sync/area-ledger.test.js +5 -192
  111. package/dist/sync/area-ledger.test.js.map +1 -1
  112. package/dist/sync/parallel-sync-coordinator.test.js +0 -29
  113. package/dist/sync/parallel-sync-coordinator.test.js.map +1 -1
  114. package/dist/sync/realtime-telemetry.d.ts +1 -1
  115. package/dist/sync/realtime-telemetry.d.ts.map +1 -1
  116. package/dist/sync/realtime-telemetry.js +6 -14
  117. package/dist/sync/realtime-telemetry.js.map +1 -1
  118. package/dist/sync/realtime-telemetry.test.js +16 -136
  119. package/dist/sync/realtime-telemetry.test.js.map +1 -1
  120. package/dist/sync/state-store.d.ts +0 -6
  121. package/dist/sync/state-store.d.ts.map +1 -1
  122. package/dist/sync/state-store.js +2 -14
  123. package/dist/sync/state-store.js.map +1 -1
  124. package/dist/watcher.d.ts +0 -12
  125. package/dist/watcher.d.ts.map +1 -1
  126. package/dist/watcher.js +0 -131
  127. package/dist/watcher.js.map +1 -1
  128. package/dist/watcher.test.js +0 -63
  129. package/dist/watcher.test.js.map +1 -1
  130. package/package.json +1 -2
  131. package/dist/cli/onepass-pull-planner-regressions.test.d.ts +0 -2
  132. package/dist/cli/onepass-pull-planner-regressions.test.d.ts.map +0 -1
  133. package/dist/cli/onepass-pull-planner-regressions.test.js +0 -300
  134. package/dist/cli/onepass-pull-planner-regressions.test.js.map +0 -1
  135. package/dist/cli/onepass-pull-planner.test.d.ts +0 -2
  136. package/dist/cli/onepass-pull-planner.test.d.ts.map +0 -1
  137. package/dist/cli/onepass-pull-planner.test.js +0 -266
  138. package/dist/cli/onepass-pull-planner.test.js.map +0 -1
  139. package/dist/cli/streaming-pull-planner.bench.test.d.ts +0 -15
  140. package/dist/cli/streaming-pull-planner.bench.test.d.ts.map +0 -1
  141. package/dist/cli/streaming-pull-planner.bench.test.js +0 -273
  142. package/dist/cli/streaming-pull-planner.bench.test.js.map +0 -1
  143. package/dist/s3.list-cost.test.d.ts +0 -2
  144. package/dist/s3.list-cost.test.d.ts.map +0 -1
  145. package/dist/s3.list-cost.test.js +0 -51
  146. package/dist/s3.list-cost.test.js.map +0 -1
  147. package/dist/sync/remote-file-spool.d.ts +0 -16
  148. package/dist/sync/remote-file-spool.d.ts.map +0 -1
  149. package/dist/sync/remote-file-spool.js +0 -91
  150. package/dist/sync/remote-file-spool.js.map +0 -1
  151. package/dist/sync/remote-file-spool.test.d.ts +0 -2
  152. package/dist/sync/remote-file-spool.test.d.ts.map +0 -1
  153. package/dist/sync/remote-file-spool.test.js +0 -77
  154. package/dist/sync/remote-file-spool.test.js.map +0 -1
@@ -27,9 +27,10 @@ vi.mock("../s3.js", async (importOriginal) => {
27
27
  };
28
28
  });
29
29
  import { executeCompanyFanout } from "./sync-runner-company.js";
30
+ import { clearContextCache, resolveEntityContext, setEntityFetchSleepForTest, } from "../context.js";
30
31
  import { CognitoAuthError, CognitoRefreshError } from "../cognito-auth.js";
31
32
  import { _testing as shareTesting, share, parseRegisteredCompanySkillUid, } from "../cli/share.js";
32
- import { readJournal } from "../journal.js";
33
+ import { PERSONAL_VAULT_JOURNAL_SLUG, readJournal } from "../journal.js";
33
34
  import { VaultConflictError } from "../vault-client.js";
34
35
  import { headRemoteFile, uploadFile } from "../s3.js";
35
36
  import { setBandwidthGovernorForTesting } from "../bandwidth.js";
@@ -68,61 +69,6 @@ describe("executeCompanyFanout realtime inventory gate", () => {
68
69
  expect(emit).toHaveBeenCalledWith(expect.objectContaining({ type: "error", company: "personal", path: "(scope)" }));
69
70
  });
70
71
  });
71
- describe("executeCompanyFanout cooperative checkpoints", () => {
72
- it("hands off only after each target leg has settled and retains the remaining plan", async () => {
73
- const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-fanout-checkpoint-"));
74
- const order = [];
75
- const shareResult = {
76
- filesUploaded: 0,
77
- bytesUploaded: 0,
78
- filesSkipped: 0,
79
- filesDeleted: 0,
80
- filesTombstoned: 0,
81
- filesRefusedStale: 0,
82
- filesRefusedStalePaths: [],
83
- filesSuppressedByTombstone: 0,
84
- filesExcludedByPolicy: 0,
85
- filesExcludedByScope: 0,
86
- filesExcludedByIgnore: 0,
87
- unreachablePaths: [],
88
- linkedSubtreesNotShipped: [],
89
- conflictPaths: [],
90
- aborted: false,
91
- };
92
- try {
93
- await executeCompanyFanout({
94
- plan: [
95
- { uid: "prs_1", slug: "personal", personalMode: true },
96
- { uid: "prs_2", slug: "personal-next", personalMode: true },
97
- ],
98
- direction: "push",
99
- hqRoot,
100
- onConflict: "abort",
101
- client: {},
102
- vaultConfig: { apiUrl: "https://api.example", authToken: "access-token" },
103
- syncFn: vi.fn(),
104
- shareFn: vi.fn(async (options) => {
105
- order.push(`target:${options.company}`);
106
- return shareResult;
107
- }),
108
- resolveDeletePolicy: () => "all",
109
- emit: vi.fn(),
110
- ensureCompanyDir: vi.fn(),
111
- cooperativeCheckpoint: async () => { order.push("checkpoint"); },
112
- telemetryClient: null,
113
- });
114
- expect(order).toEqual([
115
- "target:prs_1",
116
- "checkpoint",
117
- "target:prs_2",
118
- "checkpoint",
119
- ]);
120
- }
121
- finally {
122
- fs.rmSync(hqRoot, { recursive: true, force: true });
123
- }
124
- });
125
- });
126
72
  describe("executeCompanyFanout — not-shipped event forwarding", () => {
127
73
  it("preserves a company skill validation message", async () => {
128
74
  const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-fanout-skill-validation-"));
@@ -609,6 +555,71 @@ describe("executeCompanyFanout — archived session-log skip", () => {
609
555
  }
610
556
  });
611
557
  });
558
+ describe("executeCompanyFanout — diagnostic tombstone HEAD deferral", () => {
559
+ it("does not error the company when the only events are diagnostic tombstone deferrals", async () => {
560
+ // A vault 100/hr throttle during planned-deletion HEAD verify is
561
+ // emitted as `{ type: "error", diagnostic: true }`. The fanout must
562
+ // forward that marker and leave errors[] empty so the company
563
+ // settles `complete` and the runner keeps exit 0 (never 2).
564
+ const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-fanout-tombstone-"));
565
+ try {
566
+ const events = [];
567
+ const pullResult = {
568
+ filesDownloaded: 0,
569
+ bytesDownloaded: 0,
570
+ filesSkipped: 0,
571
+ conflicts: 0,
572
+ conflictPaths: [],
573
+ aborted: false,
574
+ newFiles: [],
575
+ newFilesCount: 0,
576
+ filesExcludedByPolicy: 0,
577
+ filesTombstoned: 0,
578
+ filesOutOfScope: 0,
579
+ scopeOrphansRemoved: 0,
580
+ scopeOrphansBlocked: 0,
581
+ };
582
+ const syncFn = vi.fn(async (opts) => {
583
+ opts.onEvent?.({
584
+ type: "error",
585
+ diagnostic: true,
586
+ path: "skills/wbr-finalize/SKILL.md",
587
+ message: "tombstone HEAD verify failed (deferring): RateLimited rate limited (100/hr) — get skills/wbr-finalize/SKILL.md deferred to next sync",
588
+ });
589
+ return pullResult;
590
+ });
591
+ const result = await executeCompanyFanout({
592
+ plan: [{ uid: "prs_1", slug: "personal", personalMode: true }],
593
+ direction: "pull",
594
+ hqRoot,
595
+ onConflict: "keep",
596
+ client: {},
597
+ vaultConfig: { apiUrl: "https://api.example", authToken: "access-token" },
598
+ syncFn,
599
+ shareFn: vi.fn(),
600
+ resolveDeletePolicy: () => "all",
601
+ emit: (e) => events.push(e),
602
+ ensureCompanyDir: vi.fn(),
603
+ telemetryClient: null,
604
+ });
605
+ expect(events).toContainEqual({
606
+ type: "error",
607
+ diagnostic: true,
608
+ company: "personal",
609
+ path: "skills/wbr-finalize/SKILL.md",
610
+ message: "tombstone HEAD verify failed (deferring): RateLimited rate limited (100/hr) — get skills/wbr-finalize/SKILL.md deferred to next sync",
611
+ });
612
+ expect(result.errors).toEqual([]);
613
+ expect(result.companyFailures).toEqual([]);
614
+ expect(result.transientCompanies).toEqual([]);
615
+ expect(events.some((e) => e.type === "error" && e.diagnostic !== true)).toBe(false);
616
+ expect(result.stateByCompany.get("personal")?.status).toBe("complete");
617
+ }
618
+ finally {
619
+ fs.rmSync(hqRoot, { recursive: true, force: true });
620
+ }
621
+ });
622
+ });
612
623
  describe("executeCompanyFanout — Windows rename-blocked skip (HQ-DESKTOP-54, second path)", () => {
613
624
  it("re-emits the skip with the company label, never errors the company, and keeps it complete", async () => {
614
625
  // A transient Windows rename refusal surfaces from the pull leg as the
@@ -1163,6 +1174,104 @@ describe("executeCompanyFanout — scoped push walk roots", () => {
1163
1174
  }
1164
1175
  });
1165
1176
  });
1177
+ describe("executeCompanyFanout — entity-fetch 429 (2026-09-18 Odin)", () => {
1178
+ const vaultConfig = {
1179
+ apiUrl: "https://vault-api.test",
1180
+ authToken: "access-token",
1181
+ };
1182
+ const entity = {
1183
+ uid: "cmp_01KQ2RYAHXHDPCTY9GPQPTH3DG",
1184
+ slug: "indigo",
1185
+ bucketName: "hq-vault-indigo",
1186
+ status: "active",
1187
+ };
1188
+ const vend = {
1189
+ credentials: {
1190
+ accessKeyId: "ASIA_TEST",
1191
+ secretAccessKey: "secret",
1192
+ sessionToken: "session",
1193
+ expiration: new Date(Date.now() + 15 * 60 * 1000).toISOString(),
1194
+ },
1195
+ expiresAt: new Date(Date.now() + 15 * 60 * 1000).toISOString(),
1196
+ };
1197
+ const runIndigoPull = async (syncFn) => {
1198
+ const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-fanout-429-"));
1199
+ const events = [];
1200
+ try {
1201
+ const result = await executeCompanyFanout({
1202
+ plan: [{ uid: entity.uid, slug: "indigo" }],
1203
+ direction: "pull",
1204
+ hqRoot,
1205
+ onConflict: "keep",
1206
+ client: {},
1207
+ vaultConfig,
1208
+ syncFn,
1209
+ shareFn: vi.fn(),
1210
+ resolveDeletePolicy: () => "all",
1211
+ emit: (e) => events.push(e),
1212
+ ensureCompanyDir: vi.fn(),
1213
+ telemetryClient: null,
1214
+ });
1215
+ return { result, events };
1216
+ }
1217
+ finally {
1218
+ fs.rmSync(hqRoot, { recursive: true, force: true });
1219
+ }
1220
+ };
1221
+ beforeEach(() => {
1222
+ clearContextCache();
1223
+ setEntityFetchSleepForTest(async () => undefined);
1224
+ });
1225
+ afterEach(() => {
1226
+ setEntityFetchSleepForTest(undefined);
1227
+ vi.unstubAllGlobals();
1228
+ });
1229
+ it("a 429 then success on retry yields a complete company sync", async () => {
1230
+ let entityCalls = 0;
1231
+ vi.stubGlobal("fetch", vi.fn(async (url) => {
1232
+ const u = String(url);
1233
+ if (u.includes(`/entity/${entity.uid}`)) {
1234
+ entityCalls += 1;
1235
+ if (entityCalls === 1) {
1236
+ return new Response(JSON.stringify({ message: "Too Many Requests" }), {
1237
+ status: 429,
1238
+ });
1239
+ }
1240
+ return new Response(JSON.stringify({ entity }), { status: 200 });
1241
+ }
1242
+ if (u.includes("/sts/vend")) {
1243
+ return new Response(JSON.stringify(vend), { status: 200 });
1244
+ }
1245
+ return new Response("Not found", { status: 404 });
1246
+ }));
1247
+ const emptyPull = {
1248
+ filesDownloaded: 0,
1249
+ bytesDownloaded: 0,
1250
+ filesSkipped: 0,
1251
+ conflicts: 0,
1252
+ conflictPaths: [],
1253
+ aborted: false,
1254
+ newFiles: [],
1255
+ newFilesCount: 0,
1256
+ filesExcludedByPolicy: 0,
1257
+ filesTombstoned: 0,
1258
+ filesOutOfScope: 0,
1259
+ scopeOrphansRemoved: 0,
1260
+ scopeOrphansBlocked: 0,
1261
+ changedPaths: [],
1262
+ };
1263
+ const { result, events } = await runIndigoPull(async (opts) => {
1264
+ await resolveEntityContext(opts.company ?? entity.uid, opts.vaultConfig);
1265
+ return emptyPull;
1266
+ });
1267
+ expect(entityCalls).toBe(2);
1268
+ expect(result.errors).toEqual([]);
1269
+ expect(result.transientCompanies).toEqual([]);
1270
+ expect(result.stateByCompany.get("indigo")?.status).toBe("complete");
1271
+ expect(events.some((e) => e.type === "error")).toBe(false);
1272
+ expect(events.some((e) => e.type === "transient-network")).toBe(false);
1273
+ });
1274
+ });
1166
1275
  describe("executeCompanyFanout — mid-pass identity refusal (HQ-DESKTOP-5S)", () => {
1167
1276
  // A mid-pass Cognito refusal surfaces from the leg (share.ts/sync.ts now
1168
1277
  // rethrow it instead of recording a per-file error). personalMode avoids the
@@ -1361,4 +1470,152 @@ describe("executeCompanyFanout — a company-skill 409 never aborts the leg (HQ-
1361
1470
  expect(parseRegisteredCompanySkillUid(fs.readFileSync(skillFile, "utf8"))).toBe("skl_SOURCE1");
1362
1471
  });
1363
1472
  });
1473
+ describe("executeCompanyFanout — personal vault skips journaled company dirs", () => {
1474
+ const shareResult = {
1475
+ filesUploaded: 0,
1476
+ bytesUploaded: 0,
1477
+ filesSkipped: 0,
1478
+ filesDeleted: 0,
1479
+ filesTombstoned: 0,
1480
+ filesRefusedStale: 0,
1481
+ filesRefusedStalePaths: [],
1482
+ filesSuppressedByTombstone: 0,
1483
+ filesExcludedByPolicy: 0,
1484
+ filesExcludedByScope: 0,
1485
+ filesExcludedByIgnore: 0,
1486
+ unreachablePaths: [],
1487
+ linkedSubtreesNotShipped: [],
1488
+ conflictPaths: [],
1489
+ aborted: false,
1490
+ };
1491
+ const syncResult = {
1492
+ filesDownloaded: 0,
1493
+ bytesDownloaded: 0,
1494
+ filesSkipped: 0,
1495
+ conflicts: 0,
1496
+ conflictPaths: [],
1497
+ aborted: false,
1498
+ newFiles: [],
1499
+ newFilesCount: 0,
1500
+ filesExcludedByPolicy: 0,
1501
+ filesTombstoned: 0,
1502
+ filesOutOfScope: 0,
1503
+ scopeOrphansRemoved: 0,
1504
+ scopeOrphansBlocked: 0,
1505
+ changedPaths: [],
1506
+ };
1507
+ let hqRoot;
1508
+ let stateDir;
1509
+ let previousStateDir;
1510
+ beforeEach(() => {
1511
+ hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-fanout-journal-slugs-"));
1512
+ stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "hq-fanout-journal-state-"));
1513
+ previousStateDir = process.env.HQ_STATE_DIR;
1514
+ process.env.HQ_STATE_DIR = stateDir;
1515
+ fs.mkdirSync(path.join(hqRoot, "personal"), { recursive: true });
1516
+ });
1517
+ afterEach(() => {
1518
+ fs.rmSync(hqRoot, { recursive: true, force: true });
1519
+ fs.rmSync(stateDir, { recursive: true, force: true });
1520
+ if (previousStateDir === undefined)
1521
+ delete process.env.HQ_STATE_DIR;
1522
+ else
1523
+ process.env.HQ_STATE_DIR = previousStateDir;
1524
+ });
1525
+ function writeCompanyDir(slug) {
1526
+ fs.mkdirSync(path.join(hqRoot, "companies", slug), { recursive: true });
1527
+ }
1528
+ function writeJournalLocator(slug) {
1529
+ fs.writeFileSync(path.join(stateDir, `sync-journal.${slug}.json`), JSON.stringify({ schema: "hq-sync-journal-locator@1" }));
1530
+ }
1531
+ function relSharePaths(shareFn) {
1532
+ const call = shareFn.mock.calls[0];
1533
+ expect(call).toBeDefined();
1534
+ return call[0].paths.map((p) => path.relative(hqRoot, p)).sort();
1535
+ }
1536
+ async function runPersonalBoth(shareFn = vi.fn(async (_options) => shareResult), syncFn = vi.fn(async (_options) => syncResult)) {
1537
+ const events = [];
1538
+ await executeCompanyFanout({
1539
+ plan: [{ uid: "prs_1", slug: "personal", personalMode: true }],
1540
+ direction: "both",
1541
+ hqRoot,
1542
+ onConflict: "abort",
1543
+ client: {},
1544
+ vaultConfig: { apiUrl: "https://api.example", authToken: "access-token" },
1545
+ fetchFileTombstones: async () => new Map(),
1546
+ syncFn,
1547
+ shareFn,
1548
+ resolveDeletePolicy: () => "all",
1549
+ emit: (event) => events.push(event),
1550
+ ensureCompanyDir: vi.fn(),
1551
+ telemetryClient: null,
1552
+ });
1553
+ return { events, shareFn, syncFn };
1554
+ }
1555
+ it("excludes companies/dandy from the personal push when its sync journal exists and there is no manifest", async () => {
1556
+ writeCompanyDir("dandy");
1557
+ writeJournalLocator("dandy");
1558
+ const { events, shareFn, syncFn } = await runPersonalBoth();
1559
+ const rel = relSharePaths(shareFn);
1560
+ expect(rel).not.toContain(path.join("companies", "dandy"));
1561
+ expect(rel).toContain("personal");
1562
+ const shareArgs = shareFn.mock.calls[0][0];
1563
+ expect(shareArgs.decommissionPrefixes).toEqual(["companies/dandy"]);
1564
+ const pullArgs = syncFn.mock.calls[0][0];
1565
+ expect(Array.from(pullArgs.teamSyncedSlugs ?? [])).toEqual(["dandy"]);
1566
+ expect(events).toContainEqual({
1567
+ type: "error",
1568
+ diagnostic: true,
1569
+ company: "personal",
1570
+ path: "(personal-vault)",
1571
+ message: "excluding journaled companies from personal vault: dandy",
1572
+ });
1573
+ expect(events.some((e) => e.type === "error" && e.diagnostic !== true)).toBe(false);
1574
+ });
1575
+ it("still includes a local-only company that has no journal when there is no manifest", async () => {
1576
+ writeCompanyDir("local-only");
1577
+ const { events, shareFn, syncFn } = await runPersonalBoth();
1578
+ const rel = relSharePaths(shareFn);
1579
+ expect(rel).toContain(path.join("companies", "local-only"));
1580
+ expect(shareFn.mock.calls[0][0]).not.toHaveProperty("decommissionPrefixes");
1581
+ expect(Array.from(syncFn.mock.calls[0][0].teamSyncedSlugs ?? [])).toEqual([]);
1582
+ expect(events.some((e) => e.type === "error")).toBe(false);
1583
+ });
1584
+ it("does not treat the personal-vault sentinel journal as a company slug", async () => {
1585
+ writeCompanyDir("local-only");
1586
+ writeJournalLocator(PERSONAL_VAULT_JOURNAL_SLUG);
1587
+ const { events, shareFn, syncFn } = await runPersonalBoth();
1588
+ const rel = relSharePaths(shareFn);
1589
+ expect(rel).toContain(path.join("companies", "local-only"));
1590
+ expect(shareFn.mock.calls[0][0]).not.toHaveProperty("decommissionPrefixes");
1591
+ expect(Array.from(syncFn.mock.calls[0][0].teamSyncedSlugs ?? [])).toEqual([]);
1592
+ expect(events.some((e) => e.type === "error")).toBe(false);
1593
+ });
1594
+ it("does not pass journal-derived teamSyncedSlugs to a company target", async () => {
1595
+ writeCompanyDir("dandy");
1596
+ writeJournalLocator("dandy");
1597
+ const shareFn = vi.fn(async (_options) => shareResult);
1598
+ const syncFn = vi.fn(async (_options) => syncResult);
1599
+ await executeCompanyFanout({
1600
+ plan: [{ uid: "cmp_dandy", slug: "dandy" }],
1601
+ direction: "both",
1602
+ hqRoot,
1603
+ onConflict: "abort",
1604
+ client: {},
1605
+ vaultConfig: { apiUrl: "https://api.example", authToken: "access-token" },
1606
+ fetchFileTombstones: async () => new Map(),
1607
+ syncFn,
1608
+ shareFn,
1609
+ resolveDeletePolicy: () => "all",
1610
+ emit: vi.fn(),
1611
+ ensureCompanyDir: vi.fn(),
1612
+ telemetryClient: null,
1613
+ });
1614
+ expect(shareFn.mock.calls[0][0].paths).toEqual([
1615
+ path.join(hqRoot, "companies", "dandy"),
1616
+ ]);
1617
+ expect(shareFn.mock.calls[0][0]).not.toHaveProperty("decommissionPrefixes");
1618
+ expect(Object.keys(syncFn.mock.calls[0][0])).not.toContain("teamSyncedSlugs");
1619
+ });
1620
+ });
1364
1621
  //# sourceMappingURL=sync-runner-company.test.js.map