@indigoai-us/hq-cloud 5.4.5 → 5.7.0

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/s3.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"s3.js","sourceRoot":"","sources":["../src/s3.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"s3.js","sourceRoot":"","sources":["../src/s3.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -23,6 +23,14 @@ export interface JournalEntry {
23
23
  size: number;
24
24
  syncedAt: string;
25
25
  direction: "up" | "down";
26
+ /**
27
+ * S3 ETag of the remote object as of last successful sync, normalized (no
28
+ * surrounding quotes). Optional for backwards compatibility: entries
29
+ * written before this field existed won't have it, in which case
30
+ * conflict detection falls back to comparing remote `lastModified`
31
+ * against `syncedAt`.
32
+ */
33
+ remoteEtag?: string;
26
34
  }
27
35
  export interface SyncJournal {
28
36
  version: "1";
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,GAAG,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,GAAG,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,yBAAyB;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,6BAA6B;IAC7B,WAAW,EAAE,gBAAgB,CAAC;IAC9B,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,GAAG,MAAM,CAAC;IACzB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,GAAG,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,yBAAyB;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,6BAA6B;IAC7B,WAAW,EAAE,gBAAgB,CAAC;IAC9B,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
package/dist/watcher.js CHANGED
@@ -91,8 +91,8 @@ export class SyncWatcher {
91
91
  const existing = journal.files[relativePath];
92
92
  if (existing && existing.hash === hash)
93
93
  continue;
94
- await uploadFile(this.ctx, change.absolutePath, relativePath);
95
- updateEntry(journal, relativePath, hash, stat.size, "up");
94
+ const { etag } = await uploadFile(this.ctx, change.absolutePath, relativePath);
95
+ updateEntry(journal, relativePath, hash, stat.size, "up", etag);
96
96
  }
97
97
  }
98
98
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../src/watcher.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": "5.4.5",
3
+ "version": "5.7.0",
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",
@@ -575,6 +575,49 @@ describe("per-company fanout", () => {
575
575
  ]);
576
576
  });
577
577
 
578
+ it("tags Stage-1 plan events with the company slug", async () => {
579
+ const deps = makeDeps({
580
+ createVaultClient: () =>
581
+ makeVaultStub({
582
+ memberships: [{ companyUid: "cmp_a" }],
583
+ entityGet: (uid: string) =>
584
+ Promise.resolve({ uid, slug: "acme" } as unknown as EntityInfo),
585
+ }),
586
+ sync: vi.fn().mockImplementation(async (opts: SyncOptions) => {
587
+ opts.onEvent?.({
588
+ type: "plan",
589
+ filesToDownload: 7,
590
+ bytesToDownload: 4096,
591
+ filesToUpload: 0,
592
+ bytesToUpload: 0,
593
+ filesToSkip: 3,
594
+ filesToConflict: 1,
595
+ });
596
+ return defaultSyncResult({ filesDownloaded: 7, bytesDownloaded: 4096 });
597
+ }),
598
+ });
599
+
600
+ const code = await runRunner(["--companies"], deps);
601
+ expect(code).toBe(0);
602
+ const planEvents = deps.stdout
603
+ .events()
604
+ .filter((e): e is Extract<RunnerEvent, { type: "plan" }> =>
605
+ e.type === "plan",
606
+ );
607
+ expect(planEvents).toEqual([
608
+ {
609
+ type: "plan",
610
+ company: "acme",
611
+ filesToDownload: 7,
612
+ bytesToDownload: 4096,
613
+ filesToUpload: 0,
614
+ bytesToUpload: 0,
615
+ filesToSkip: 3,
616
+ filesToConflict: 1,
617
+ },
618
+ ]);
619
+ });
620
+
578
621
  it("tags per-file error events with the company slug", async () => {
579
622
  const deps = makeDeps({
580
623
  createVaultClient: () =>
@@ -125,6 +125,18 @@ export type RunnerEvent =
125
125
  type: "fanout-plan";
126
126
  companies: Array<{ uid: string; slug: string; name?: string }>;
127
127
  }
128
+ | ({
129
+ /**
130
+ * Stage-1 results for a single company's sync/share pass. Emitted once
131
+ * before any `progress` events for that company arrive — once for the
132
+ * pull phase (download counts) and once for the push phase (upload
133
+ * counts) when `--direction both`. Consumers (the menubar) sum the
134
+ * non-zero fields across all `plan` events seen for a fanout to render
135
+ * an accurate "X of Y files" denominator before transfers begin.
136
+ */
137
+ type: "plan";
138
+ company: string;
139
+ } & Omit<Extract<SyncProgressEvent, { type: "plan" }>, "type">)
128
140
  | ({ type: "progress"; company: string } & Omit<Extract<SyncProgressEvent, { type: "progress" }>, "type">)
129
141
  | ({ type: "error"; company?: string } & Omit<Extract<SyncProgressEvent, { type: "error" }>, "type">)
130
142
  | ({ type: "conflict"; company: string } & Omit<Extract<SyncProgressEvent, { type: "conflict" }>, "type">)
@@ -534,7 +546,18 @@ export async function runRunner(
534
546
  // Per-company event tagger — shared by push and pull phases so progress
535
547
  // rows land on the right company regardless of which phase emitted them.
536
548
  const tagAndEmit = (event: SyncProgressEvent): void => {
537
- if (event.type === "progress") {
549
+ if (event.type === "plan") {
550
+ emit({
551
+ type: "plan",
552
+ company: companyLabel,
553
+ filesToDownload: event.filesToDownload,
554
+ bytesToDownload: event.bytesToDownload,
555
+ filesToUpload: event.filesToUpload,
556
+ bytesToUpload: event.bytesToUpload,
557
+ filesToSkip: event.filesToSkip,
558
+ filesToConflict: event.filesToConflict,
559
+ });
560
+ } else if (event.type === "progress") {
538
561
  emit({
539
562
  type: "progress",
540
563
  company: companyLabel,
@@ -550,7 +573,7 @@ export async function runRunner(
550
573
  direction: event.direction,
551
574
  resolution: event.resolution,
552
575
  });
553
- } else {
576
+ } else if (event.type === "error") {
554
577
  emit({
555
578
  type: "error",
556
579
  company: companyLabel,
@@ -9,9 +9,11 @@ import * as os from "os";
9
9
  import { clearContextCache } from "../context.js";
10
10
  import type { VaultServiceConfig } from "../types.js";
11
11
 
12
- // Mock s3 module at the top level
12
+ // Mock s3 module at the top level. uploadFile resolves to a synthetic ETag
13
+ // so share() can record it on the journal entry — the real PutObject
14
+ // response shape is `{ ETag: '"<hex>"' }`.
13
15
  vi.mock("../s3.js", () => ({
14
- uploadFile: vi.fn().mockResolvedValue(undefined),
16
+ uploadFile: vi.fn().mockResolvedValue({ etag: '"upload-etag"' }),
15
17
  downloadFile: vi.fn().mockResolvedValue(undefined),
16
18
  listRemoteFiles: vi.fn().mockResolvedValue([]),
17
19
  deleteRemoteFile: vi.fn().mockResolvedValue(undefined),
@@ -77,6 +79,10 @@ describe("share", () => {
77
79
  afterEach(() => {
78
80
  vi.unstubAllGlobals();
79
81
  vi.clearAllMocks();
82
+ // clearAllMocks wipes the default ETag impl set in vi.mock(), so
83
+ // re-prime it for the next test.
84
+ vi.mocked(uploadFile).mockResolvedValue({ etag: '"upload-etag"' });
85
+ vi.mocked(headRemoteFile).mockResolvedValue(null);
80
86
  fs.rmSync(tmpDir, { recursive: true, force: true });
81
87
  fs.rmSync(stateDir, { recursive: true, force: true });
82
88
  delete process.env.HQ_STATE_DIR;
@@ -276,18 +282,18 @@ describe("share", () => {
276
282
  expect(uploadFile).toHaveBeenCalledWith(expect.anything(), testFile, "changed.md");
277
283
  });
278
284
 
279
- it("populates conflictPaths and emits a conflict event when remote drifted from journal", async () => {
285
+ it("populates conflictPaths and emits a conflict event when both local and remote drifted from journal", async () => {
280
286
  const companyRoot = path.join(tmpDir, "companies", "acme");
281
287
  fs.mkdirSync(companyRoot, { recursive: true });
282
288
  const testFile = path.join(companyRoot, "drifted.md");
283
289
  fs.writeFileSync(testFile, "local edit");
284
290
 
285
- // Journal has a stale hash → local diverged. headRemoteFile returning
286
- // non-null tells share() the remote also exists; combined with the
287
- // hash mismatch this trips the conflict branch.
291
+ // Stale hash → local diverged. Remote ETag in head response differs
292
+ // from the one stored in the journal → remote also moved. Both sides
293
+ // changed since last sync = real conflict.
288
294
  vi.mocked(headRemoteFile).mockResolvedValueOnce({
289
295
  lastModified: new Date(),
290
- etag: '"remote-changed"',
296
+ etag: '"remote-new-etag"',
291
297
  size: 99,
292
298
  });
293
299
 
@@ -303,6 +309,7 @@ describe("share", () => {
303
309
  size: 10,
304
310
  syncedAt: new Date().toISOString(),
305
311
  direction: "up",
312
+ remoteEtag: "remote-old-etag",
306
313
  },
307
314
  },
308
315
  }),
@@ -332,6 +339,124 @@ describe("share", () => {
332
339
  });
333
340
  });
334
341
 
342
+ it("uploads (no conflict) when only the local side changed since last sync", async () => {
343
+ // Regression for hq-cloud#<conflict-detection>: a local edit to a file
344
+ // that exists on S3 used to trigger a push conflict because the
345
+ // detector compared `journalEntry.hash !== localHash` without checking
346
+ // the remote. Combined with `--on-conflict keep`, this silently dropped
347
+ // every edit to any pre-existing file.
348
+ const companyRoot = path.join(tmpDir, "companies", "acme");
349
+ fs.mkdirSync(companyRoot, { recursive: true });
350
+ const testFile = path.join(companyRoot, "edited.md");
351
+ fs.writeFileSync(testFile, "edited locally");
352
+
353
+ const syncedAt = new Date(Date.now() - 60_000).toISOString();
354
+ vi.mocked(headRemoteFile).mockResolvedValueOnce({
355
+ lastModified: new Date(Date.parse(syncedAt) - 30_000),
356
+ etag: '"unchanged-remote"',
357
+ size: 5,
358
+ });
359
+
360
+ const journalPath = path.join(stateDir, "sync-journal.acme.json");
361
+ fs.writeFileSync(
362
+ journalPath,
363
+ JSON.stringify({
364
+ version: "1",
365
+ lastSync: syncedAt,
366
+ files: {
367
+ "edited.md": {
368
+ hash: "stale-hash-for-old-content",
369
+ size: 5,
370
+ syncedAt,
371
+ direction: "down",
372
+ remoteEtag: "unchanged-remote",
373
+ },
374
+ },
375
+ }),
376
+ );
377
+
378
+ const events: unknown[] = [];
379
+ const result = await share({
380
+ paths: [testFile],
381
+ company: "acme",
382
+ vaultConfig: mockConfig,
383
+ hqRoot: tmpDir,
384
+ onConflict: "keep",
385
+ onEvent: (e) => events.push(e),
386
+ });
387
+
388
+ expect(result.conflictPaths).toEqual([]);
389
+ expect(result.filesUploaded).toBe(1);
390
+ expect(events.some((e): e is { type: string } =>
391
+ typeof e === "object" && e !== null && (e as { type?: string }).type === "conflict",
392
+ )).toBe(false);
393
+ });
394
+
395
+ it("falls back to lastModified vs syncedAt when journal entry has no remoteEtag (legacy)", async () => {
396
+ // Legacy entries from before the remoteEtag field existed should be
397
+ // treated as "remote unchanged" iff lastModified <= syncedAt.
398
+ const companyRoot = path.join(tmpDir, "companies", "acme");
399
+ fs.mkdirSync(companyRoot, { recursive: true });
400
+ const testFile = path.join(companyRoot, "legacy.md");
401
+ fs.writeFileSync(testFile, "edited locally");
402
+
403
+ const syncedAt = new Date().toISOString();
404
+ vi.mocked(headRemoteFile).mockResolvedValueOnce({
405
+ lastModified: new Date(Date.parse(syncedAt) - 5_000),
406
+ etag: '"some-etag"',
407
+ size: 5,
408
+ });
409
+
410
+ const journalPath = path.join(stateDir, "sync-journal.acme.json");
411
+ fs.writeFileSync(
412
+ journalPath,
413
+ JSON.stringify({
414
+ version: "1",
415
+ lastSync: syncedAt,
416
+ files: {
417
+ "legacy.md": {
418
+ hash: "stale-hash",
419
+ size: 5,
420
+ syncedAt,
421
+ direction: "down",
422
+ // no remoteEtag — pre-fix journal
423
+ },
424
+ },
425
+ }),
426
+ );
427
+
428
+ const result = await share({
429
+ paths: [testFile],
430
+ company: "acme",
431
+ vaultConfig: mockConfig,
432
+ hqRoot: tmpDir,
433
+ onConflict: "keep",
434
+ });
435
+
436
+ expect(result.conflictPaths).toEqual([]);
437
+ expect(result.filesUploaded).toBe(1);
438
+ });
439
+
440
+ it("records the upload's ETag on the journal entry", async () => {
441
+ const companyRoot = path.join(tmpDir, "companies", "acme");
442
+ fs.mkdirSync(companyRoot, { recursive: true });
443
+ const testFile = path.join(companyRoot, "fresh.md");
444
+ fs.writeFileSync(testFile, "new file");
445
+
446
+ vi.mocked(uploadFile).mockResolvedValueOnce({ etag: '"new-upload-etag"' });
447
+
448
+ await share({
449
+ paths: [testFile],
450
+ company: "acme",
451
+ vaultConfig: mockConfig,
452
+ hqRoot: tmpDir,
453
+ });
454
+
455
+ const journalPath = path.join(stateDir, "sync-journal.acme.json");
456
+ const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
457
+ expect(journal.files["fresh.md"].remoteEtag).toBe("new-upload-etag");
458
+ });
459
+
335
460
  it("skipUnchanged=false (default) uploads even when hash matches", async () => {
336
461
  const companyRoot = path.join(tmpDir, "companies", "acme");
337
462
  fs.mkdirSync(companyRoot, { recursive: true });
@@ -383,6 +508,9 @@ describe("share", () => {
383
508
  vaultConfig: mockConfig,
384
509
  hqRoot: tmpDir,
385
510
  onEvent: (e) => {
511
+ // Only file-level events carry `.path`. The Stage-1 `plan` event is
512
+ // surfaced separately and tested in its own block.
513
+ if (e.type === "plan") return;
386
514
  events.push({
387
515
  type: e.type,
388
516
  path: e.path,
@@ -396,4 +524,126 @@ describe("share", () => {
396
524
  expect(events.every((e) => e.type === "progress")).toBe(true);
397
525
  expect(events.map((e) => e.path).sort()).toEqual(["a.md", "b.md"]);
398
526
  });
527
+
528
+ // ── Stage-1 plan event ─────────────────────────────────────────────────
529
+
530
+ it("emits a plan event before any progress events", async () => {
531
+ const companyRoot = path.join(tmpDir, "companies", "acme");
532
+ fs.mkdirSync(companyRoot, { recursive: true });
533
+ fs.writeFileSync(path.join(companyRoot, "a.md"), "alpha");
534
+ fs.writeFileSync(path.join(companyRoot, "b.md"), "beta");
535
+
536
+ const events: { type: string }[] = [];
537
+ await share({
538
+ paths: [companyRoot],
539
+ company: "acme",
540
+ vaultConfig: mockConfig,
541
+ hqRoot: tmpDir,
542
+ onEvent: (e) => events.push({ type: e.type }),
543
+ });
544
+
545
+ expect(events.length).toBeGreaterThan(0);
546
+ expect(events[0].type).toBe("plan");
547
+ const planIndex = events.findIndex((e) => e.type === "plan");
548
+ const firstProgressIndex = events.findIndex((e) => e.type === "progress");
549
+ expect(firstProgressIndex).toBeGreaterThan(planIndex);
550
+ });
551
+
552
+ it("plan event reports filesToUpload = candidates and bytesToUpload = sum of file sizes", async () => {
553
+ const companyRoot = path.join(tmpDir, "companies", "acme");
554
+ fs.mkdirSync(companyRoot, { recursive: true });
555
+ fs.writeFileSync(path.join(companyRoot, "a.md"), "alpha"); // 5 bytes
556
+ fs.writeFileSync(path.join(companyRoot, "b.md"), "beta!"); // 5 bytes
557
+
558
+ const planEvents: Array<{
559
+ type: string;
560
+ filesToUpload?: number;
561
+ bytesToUpload?: number;
562
+ filesToDownload?: number;
563
+ bytesToDownload?: number;
564
+ filesToSkip?: number;
565
+ filesToConflict?: number;
566
+ }> = [];
567
+ await share({
568
+ paths: [companyRoot],
569
+ company: "acme",
570
+ vaultConfig: mockConfig,
571
+ hqRoot: tmpDir,
572
+ onEvent: (e) => {
573
+ if (e.type === "plan") planEvents.push(e);
574
+ },
575
+ });
576
+
577
+ expect(planEvents).toHaveLength(1);
578
+ expect(planEvents[0]).toMatchObject({
579
+ type: "plan",
580
+ filesToUpload: 2,
581
+ bytesToUpload: 10,
582
+ filesToDownload: 0, // share() is push-only
583
+ bytesToDownload: 0,
584
+ filesToSkip: 0,
585
+ // Push conflicts can't be classified pre-HEAD (V1 limitation);
586
+ // the complete event reports the authoritative count.
587
+ filesToConflict: 0,
588
+ });
589
+ });
590
+
591
+ it("plan event filesToSkip reflects skip-unchanged hits when journal hash matches", async () => {
592
+ const companyRoot = path.join(tmpDir, "companies", "acme");
593
+ fs.mkdirSync(companyRoot, { recursive: true });
594
+ fs.writeFileSync(path.join(companyRoot, "unchanged.md"), "stable content");
595
+ fs.writeFileSync(path.join(companyRoot, "changed.md"), "newer content");
596
+
597
+ // Pre-seed the journal so unchanged.md matches its hash but
598
+ // changed.md does not.
599
+ const crypto = await import("crypto");
600
+ const unchangedHash = crypto
601
+ .createHash("sha256")
602
+ .update("stable content")
603
+ .digest("hex");
604
+ const journalPath = path.join(stateDir, "sync-journal.acme.json");
605
+ fs.writeFileSync(
606
+ journalPath,
607
+ JSON.stringify({
608
+ version: "1",
609
+ lastSync: new Date().toISOString(),
610
+ files: {
611
+ "unchanged.md": {
612
+ hash: unchangedHash,
613
+ size: 14,
614
+ syncedAt: new Date().toISOString(),
615
+ direction: "up",
616
+ },
617
+ "changed.md": {
618
+ hash: "stale-hash",
619
+ size: 13,
620
+ syncedAt: new Date().toISOString(),
621
+ direction: "up",
622
+ },
623
+ },
624
+ }),
625
+ );
626
+
627
+ const planEvents: Array<{
628
+ type: string;
629
+ filesToUpload?: number;
630
+ filesToSkip?: number;
631
+ }> = [];
632
+ await share({
633
+ paths: [companyRoot],
634
+ company: "acme",
635
+ vaultConfig: mockConfig,
636
+ hqRoot: tmpDir,
637
+ skipUnchanged: true,
638
+ onEvent: (e) => {
639
+ if (e.type === "plan") planEvents.push(e);
640
+ },
641
+ });
642
+
643
+ expect(planEvents).toHaveLength(1);
644
+ expect(planEvents[0]).toMatchObject({
645
+ filesToUpload: 1,
646
+ filesToSkip: 1,
647
+ });
648
+ });
399
649
  });