@indigoai-us/hq-cloud 6.15.20 → 6.15.21

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 (38) hide show
  1. package/dist/bin/sync-runner.d.ts +1 -1
  2. package/dist/bin/sync-runner.d.ts.map +1 -1
  3. package/dist/bin/sync-runner.js +37 -5
  4. package/dist/bin/sync-runner.js.map +1 -1
  5. package/dist/bin/sync-runner.test.js +77 -4
  6. package/dist/bin/sync-runner.test.js.map +1 -1
  7. package/dist/cli/share.test.js +140 -108
  8. package/dist/cli/share.test.js.map +1 -1
  9. package/dist/cli/sync.test.js +59 -47
  10. package/dist/cli/sync.test.js.map +1 -1
  11. package/dist/journal-delta-writes.test.d.ts +2 -0
  12. package/dist/journal-delta-writes.test.d.ts.map +1 -0
  13. package/dist/journal-delta-writes.test.js +140 -0
  14. package/dist/journal-delta-writes.test.js.map +1 -0
  15. package/dist/journal.d.ts.map +1 -1
  16. package/dist/journal.js +151 -24
  17. package/dist/journal.js.map +1 -1
  18. package/dist/journal.test.js +29 -11
  19. package/dist/journal.test.js.map +1 -1
  20. package/dist/outcome-telemetry.d.ts +7 -1
  21. package/dist/outcome-telemetry.d.ts.map +1 -1
  22. package/dist/outcome-telemetry.js +15 -9
  23. package/dist/outcome-telemetry.js.map +1 -1
  24. package/dist/outcome-telemetry.test.js +15 -0
  25. package/dist/outcome-telemetry.test.js.map +1 -1
  26. package/dist/skill-telemetry.d.ts +6 -1
  27. package/dist/skill-telemetry.d.ts.map +1 -1
  28. package/dist/skill-telemetry.js +14 -8
  29. package/dist/skill-telemetry.js.map +1 -1
  30. package/dist/skill-telemetry.test.js +43 -0
  31. package/dist/skill-telemetry.test.js.map +1 -1
  32. package/dist/telemetry.d.ts +7 -1
  33. package/dist/telemetry.d.ts.map +1 -1
  34. package/dist/telemetry.js +67 -60
  35. package/dist/telemetry.js.map +1 -1
  36. package/dist/telemetry.test.js +12 -0
  37. package/dist/telemetry.test.js.map +1 -1
  38. package/package.json +1 -1
@@ -55,7 +55,39 @@ import * as readline from "readline";
55
55
  import { share, isForbiddenCompanyVaultKey, UnreachablePushPathsError, currentUploadBytes, _testing as shareTesting } from "./share.js";
56
56
  import { deleteRemoteFile, downloadFile, headRemoteFile, uploadFile, uploadSymlink, WindowsSymlinkPrivilegeError } from "../s3.js";
57
57
  import { VaultAuthError } from "../vault-client.js";
58
- import { hashFile } from "../journal.js";
58
+ import { hashFile, readJournal, writeJournal } from "../journal.js";
59
+ /**
60
+ * Read a journal the way production does.
61
+ *
62
+ * These tests used to `JSON.parse` `sync-journal.<slug>.json` directly, back
63
+ * when that file held a full copy of the journal. It is now a small locator —
64
+ * the authority is the v3 state store — so the assertions below go through
65
+ * `readJournal`, which is what the sync engine itself uses. The slug is
66
+ * recovered from the path so each call site keeps naming the journal it means.
67
+ */
68
+ /**
69
+ * Seed a journal the way production does.
70
+ *
71
+ * These fixtures used to write `sync-journal.<slug>.json` by hand, which worked
72
+ * only while that file WAS the journal. It is a locator now, so a hand-written
73
+ * copy is simply ignored and the test silently runs against an empty journal.
74
+ * Going through `writeJournal` puts the state where the engine actually reads
75
+ * it, and migrates v1 fixtures on the way in exactly as a real upgrade would.
76
+ */
77
+ function seedJournalAt(journalPath, journal) {
78
+ const slug = path
79
+ .basename(journalPath)
80
+ .replace(/^sync-journal\./, "")
81
+ .replace(/\.json$/, "");
82
+ writeJournal(slug, journal);
83
+ }
84
+ function journalAt(journalPath) {
85
+ const slug = path
86
+ .basename(journalPath)
87
+ .replace(/^sync-journal\./, "")
88
+ .replace(/\.json$/, "");
89
+ return readJournal(slug);
90
+ }
59
91
  const mockConfig = {
60
92
  apiUrl: "https://vault-api.test",
61
93
  authToken: "test-jwt-token",
@@ -528,7 +560,7 @@ describe("share", () => {
528
560
  const { hashFile } = await import("../journal.js");
529
561
  const hash = hashFile(testFile);
530
562
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
531
- fs.writeFileSync(journalPath, JSON.stringify({
563
+ seedJournalAt(journalPath, ({
532
564
  version: "1",
533
565
  lastSync: new Date().toISOString(),
534
566
  files: {
@@ -559,7 +591,7 @@ describe("share", () => {
559
591
  // Journal has a stale hash for this path — simulating "local has been
560
592
  // edited since the last push".
561
593
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
562
- fs.writeFileSync(journalPath, JSON.stringify({
594
+ seedJournalAt(journalPath, ({
563
595
  version: "1",
564
596
  lastSync: new Date().toISOString(),
565
597
  files: {
@@ -593,7 +625,7 @@ describe("share", () => {
593
625
  // share.ts makes the first test fail (uploadFile IS called → resurrection).
594
626
  describe("push-side tombstone consult", () => {
595
627
  function seedJournal(key, hash, size) {
596
- fs.writeFileSync(path.join(stateDir, "sync-journal.acme.json"), JSON.stringify({
628
+ seedJournalAt(path.join(stateDir, "sync-journal.acme.json"), ({
597
629
  version: "1",
598
630
  lastSync: new Date().toISOString(),
599
631
  files: {
@@ -737,7 +769,7 @@ describe("share", () => {
737
769
  size: 99,
738
770
  });
739
771
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
740
- fs.writeFileSync(journalPath, JSON.stringify({
772
+ seedJournalAt(journalPath, ({
741
773
  version: "1",
742
774
  lastSync: new Date().toISOString(),
743
775
  files: {
@@ -789,7 +821,7 @@ describe("share", () => {
789
821
  // assert it is never consulted below — the skip is cheap, and a leftover
790
822
  // mockResolvedValueOnce would otherwise leak into a later test's HEAD queue.
791
823
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
792
- fs.writeFileSync(journalPath, JSON.stringify({
824
+ seedJournalAt(journalPath, ({
793
825
  version: "1",
794
826
  lastSync: new Date().toISOString(),
795
827
  files: {
@@ -969,7 +1001,7 @@ describe("share", () => {
969
1001
  direction: "push",
970
1002
  });
971
1003
  expect(fs.readdirSync(companyRoot).filter((name) => name.includes(".conflict-"))).toEqual([]);
972
- const journal = JSON.parse(fs.readFileSync(path.join(stateDir, "sync-journal.acme.json"), "utf-8"));
1004
+ const journal = readJournal("acme");
973
1005
  expect(journal.files["kms-scaffold.md"]).toMatchObject({
974
1006
  direction: "up",
975
1007
  remoteEtag: "288bfa91684ae21f9ae27c061254d412",
@@ -1085,7 +1117,7 @@ describe("share", () => {
1085
1117
  size: 5,
1086
1118
  });
1087
1119
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
1088
- fs.writeFileSync(journalPath, JSON.stringify({
1120
+ seedJournalAt(journalPath, ({
1089
1121
  version: "1",
1090
1122
  lastSync: syncedAt,
1091
1123
  files: {
@@ -1158,7 +1190,7 @@ describe("share", () => {
1158
1190
  // No journal stamp for the failed PUT — next pass re-evaluates fresh.
1159
1191
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
1160
1192
  if (fs.existsSync(journalPath)) {
1161
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
1193
+ const journal = journalAt(journalPath);
1162
1194
  expect(journal.files?.["raced.md"]).toBeUndefined();
1163
1195
  }
1164
1196
  });
@@ -1240,7 +1272,7 @@ describe("share", () => {
1240
1272
  return { etag: '"should-not-land"' };
1241
1273
  });
1242
1274
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
1243
- fs.writeFileSync(journalPath, JSON.stringify({
1275
+ seedJournalAt(journalPath, ({
1244
1276
  version: "1",
1245
1277
  lastSync: new Date(Date.now() - 60_000).toISOString(),
1246
1278
  files: {
@@ -1300,7 +1332,7 @@ describe("share", () => {
1300
1332
  });
1301
1333
  vi.mocked(uploadFile).mockResolvedValueOnce({ etag: '"new-upload-etag"' });
1302
1334
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
1303
- fs.writeFileSync(journalPath, JSON.stringify({
1335
+ seedJournalAt(journalPath, ({
1304
1336
  version: "1",
1305
1337
  lastSync: new Date(Date.now() - 60_000).toISOString(),
1306
1338
  files: {
@@ -1335,7 +1367,7 @@ describe("share", () => {
1335
1367
  size: 20,
1336
1368
  });
1337
1369
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
1338
- fs.writeFileSync(journalPath, JSON.stringify({
1370
+ seedJournalAt(journalPath, ({
1339
1371
  version: "1",
1340
1372
  lastSync: new Date().toISOString(),
1341
1373
  files: {
@@ -1377,7 +1409,7 @@ describe("share", () => {
1377
1409
  size: 40,
1378
1410
  });
1379
1411
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
1380
- fs.writeFileSync(journalPath, JSON.stringify({
1412
+ seedJournalAt(journalPath, ({
1381
1413
  version: "1",
1382
1414
  lastSync: new Date().toISOString(),
1383
1415
  files: {
@@ -1423,7 +1455,7 @@ describe("share", () => {
1423
1455
  });
1424
1456
  vi.mocked(uploadFile).mockResolvedValueOnce({ etag: '"forced-etag"' });
1425
1457
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
1426
- fs.writeFileSync(journalPath, JSON.stringify({
1458
+ seedJournalAt(journalPath, ({
1427
1459
  version: "1",
1428
1460
  lastSync: new Date().toISOString(),
1429
1461
  files: {
@@ -1506,7 +1538,7 @@ describe("share", () => {
1506
1538
  expect(vi.mocked(uploadFile).mock.calls[0][4]).toEqual({ ifMatch: REMOTE });
1507
1539
  // The entry is rewritten as an ordinary upload: local is now authority,
1508
1540
  // so the journal-honesty flag must be gone rather than re-armed.
1509
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
1541
+ const journal = journalAt(journalPath);
1510
1542
  expect(journal.files["publish-divergent.md"].direction).toBe("up");
1511
1543
  expect(journal.files["publish-divergent.md"].localDiverges).toBeFalsy();
1512
1544
  expect(journal.files["publish-divergent.md"].remoteEtag).toBe("published-etag");
@@ -1559,7 +1591,7 @@ describe("share", () => {
1559
1591
  expect(result.conflictPaths).toContain("publish-raced.md");
1560
1592
  // Consent was for a remote that no longer exists, so the flag stays put
1561
1593
  // and the operator is asked again rather than silently overruled.
1562
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
1594
+ const journal = journalAt(journalPath);
1563
1595
  expect(journal.files["publish-raced.md"].localDiverges).toBe(true);
1564
1596
  expect(fs.readFileSync(testFile, "utf-8")).toBe(localBytes);
1565
1597
  });
@@ -1614,7 +1646,7 @@ describe("share", () => {
1614
1646
  });
1615
1647
  expect(result.filesUploaded).toBe(1);
1616
1648
  expect(vi.mocked(uploadFile).mock.calls[0][4]).toEqual({ ifMatch: REMOTE });
1617
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
1649
+ const journal = journalAt(journalPath);
1618
1650
  expect(journal.files["stuck-under-skip-unchanged.md"].direction).toBe("up");
1619
1651
  expect(journal.files["stuck-under-skip-unchanged.md"].localDiverges).toBeFalsy();
1620
1652
  });
@@ -1704,7 +1736,7 @@ describe("share", () => {
1704
1736
  expect(uploadFile).not.toHaveBeenCalled();
1705
1737
  expect(result.filesUploaded).toBe(0);
1706
1738
  expect(result.conflictPaths).toContain("peer-deleted.md");
1707
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
1739
+ const journal = journalAt(journalPath);
1708
1740
  expect(journal.files["peer-deleted.md"].localDiverges).toBe(true);
1709
1741
  expect(fs.readFileSync(testFile, "utf-8")).toBe(localBytes);
1710
1742
  });
@@ -1765,7 +1797,7 @@ describe("share", () => {
1765
1797
  vi.mocked(headRemoteFile).mockResolvedValueOnce(null);
1766
1798
  vi.mocked(uploadFile).mockResolvedValueOnce({ etag: '"recreated-etag"' });
1767
1799
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
1768
- fs.writeFileSync(journalPath, JSON.stringify({
1800
+ seedJournalAt(journalPath, ({
1769
1801
  version: "1",
1770
1802
  lastSync: new Date(Date.now() - 60_000).toISOString(),
1771
1803
  files: {
@@ -1795,7 +1827,7 @@ describe("share", () => {
1795
1827
  const pc = vi.mocked(uploadFile).mock.calls[0][4];
1796
1828
  expect(pc?.ifMatch).toBeUndefined();
1797
1829
  expect(pc?.ifNoneMatch).toBe("*");
1798
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
1830
+ const journal = journalAt(journalPath);
1799
1831
  expect(journal.files["recreate-deleted.md"]).toMatchObject({
1800
1832
  hash: localHash,
1801
1833
  remoteEtag: "recreated-etag",
@@ -1816,7 +1848,7 @@ describe("share", () => {
1816
1848
  return undefined;
1817
1849
  });
1818
1850
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
1819
- fs.writeFileSync(journalPath, JSON.stringify({
1851
+ seedJournalAt(journalPath, ({
1820
1852
  version: "1",
1821
1853
  lastSync: new Date(Date.now() - 60_000).toISOString(),
1822
1854
  files: {
@@ -1848,7 +1880,7 @@ describe("share", () => {
1848
1880
  expect(vi.mocked(uploadFile).mock.calls[0][4]).toEqual({ ifNoneMatch: "*" });
1849
1881
  // Journal not advanced to a successful upload etag (entry may still be
1850
1882
  // the prior baseline — next pass re-evaluates).
1851
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
1883
+ const journal = journalAt(journalPath);
1852
1884
  expect(journal.files["race-recreate.md"]?.remoteEtag).toBe("etag-before-delete");
1853
1885
  expect(journal.files["race-recreate.md"]?.hash).toBe("old-hash");
1854
1886
  expect(fs.readFileSync(testFile, "utf-8")).toBe("my recreated local");
@@ -2172,7 +2204,7 @@ describe("share", () => {
2172
2204
  size: 5,
2173
2205
  });
2174
2206
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
2175
- fs.writeFileSync(journalPath, JSON.stringify({
2207
+ seedJournalAt(journalPath, ({
2176
2208
  version: "1",
2177
2209
  lastSync: syncedAt,
2178
2210
  files: {
@@ -2212,7 +2244,7 @@ describe("share", () => {
2212
2244
  size: 5,
2213
2245
  });
2214
2246
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
2215
- fs.writeFileSync(journalPath, JSON.stringify({
2247
+ seedJournalAt(journalPath, ({
2216
2248
  version: "1",
2217
2249
  lastSync: syncedAt,
2218
2250
  files: {
@@ -2248,7 +2280,7 @@ describe("share", () => {
2248
2280
  hqRoot: tmpDir,
2249
2281
  });
2250
2282
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
2251
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
2283
+ const journal = journalAt(journalPath);
2252
2284
  expect(journal.files["fresh.md"].remoteEtag).toBe("new-upload-etag");
2253
2285
  });
2254
2286
  it("forwards UploadAuthor to uploadFile when present (created-by metadata)", async () => {
@@ -2293,7 +2325,7 @@ describe("share", () => {
2293
2325
  const { hashFile } = await import("../journal.js");
2294
2326
  const hash = hashFile(testFile);
2295
2327
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
2296
- fs.writeFileSync(journalPath, JSON.stringify({
2328
+ seedJournalAt(journalPath, ({
2297
2329
  version: "1",
2298
2330
  lastSync: new Date().toISOString(),
2299
2331
  files: {
@@ -2513,7 +2545,7 @@ describe("share", () => {
2513
2545
  .update("stable content")
2514
2546
  .digest("hex");
2515
2547
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
2516
- fs.writeFileSync(journalPath, JSON.stringify({
2548
+ seedJournalAt(journalPath, ({
2517
2549
  version: "1",
2518
2550
  lastSync: new Date().toISOString(),
2519
2551
  files: {
@@ -2574,7 +2606,7 @@ describe("share", () => {
2574
2606
  // not be queued for delete.
2575
2607
  fs.writeFileSync(path.join(tmpDir, ".hqinclude"), "companies/*/knowledge/\n");
2576
2608
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
2577
- fs.writeFileSync(journalPath, JSON.stringify({
2609
+ seedJournalAt(journalPath, ({
2578
2610
  version: "1",
2579
2611
  lastSync: new Date().toISOString(),
2580
2612
  files: {
@@ -2611,7 +2643,7 @@ describe("share", () => {
2611
2643
  operation: "delete",
2612
2644
  });
2613
2645
  expect(deleteRemoteFile).toHaveBeenCalledWith(expect.anything(), "knowledge");
2614
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
2646
+ const journal = journalAt(journalPath);
2615
2647
  expect(journal.files["knowledge"]).toBeUndefined();
2616
2648
  });
2617
2649
  it("propagateDeletes: a dangling local symlink is NOT classified as gone (lstat, not existsSync)", async () => {
@@ -2628,7 +2660,7 @@ describe("share", () => {
2628
2660
  const danglingLink = path.join(companyRoot, "dangling-link.md");
2629
2661
  fs.symlinkSync("./missing-target.md", danglingLink);
2630
2662
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
2631
- fs.writeFileSync(journalPath, JSON.stringify({
2663
+ seedJournalAt(journalPath, ({
2632
2664
  version: "1",
2633
2665
  lastSync: new Date().toISOString(),
2634
2666
  files: {
@@ -2658,7 +2690,7 @@ describe("share", () => {
2658
2690
  expect(result.filesDeleted).toBe(0);
2659
2691
  expect(deleteRemoteFile).not.toHaveBeenCalled();
2660
2692
  // Journal entry must survive the run.
2661
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
2693
+ const journal = journalAt(journalPath);
2662
2694
  expect(journal.files["dangling-link.md"]).toBeDefined();
2663
2695
  });
2664
2696
  it("vault-litter drain: a `.drift-*` marker with direction:'down' and NO delete intent tombstones " +
@@ -2723,7 +2755,7 @@ describe("share", () => {
2723
2755
  });
2724
2756
  expect(result.filesDeleted).toBe(1);
2725
2757
  expect(deleteRemoteFile).toHaveBeenCalledWith(expect.anything(), "personal/scripts/run-project.sh.drift-1779863862-42519");
2726
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
2758
+ const journal = journalAt(journalPath);
2727
2759
  expect(journal.files["personal/scripts/run-project.sh.drift-1779863862-42519"]).toBeUndefined();
2728
2760
  }
2729
2761
  finally {
@@ -2796,7 +2828,7 @@ describe("share", () => {
2796
2828
  fs.mkdirSync(companyRoot, { recursive: true });
2797
2829
  // No file on disk under companies/acme/peer-uploaded.md → local missing.
2798
2830
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
2799
- fs.writeFileSync(journalPath, JSON.stringify({
2831
+ seedJournalAt(journalPath, ({
2800
2832
  version: "1",
2801
2833
  lastSync: new Date().toISOString(),
2802
2834
  files: {
@@ -2821,7 +2853,7 @@ describe("share", () => {
2821
2853
  });
2822
2854
  expect(result.filesDeleted).toBe(0);
2823
2855
  expect(deleteRemoteFile).not.toHaveBeenCalled();
2824
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
2856
+ const journal = journalAt(journalPath);
2825
2857
  expect(journal.files["peer-uploaded.md"]).toBeDefined();
2826
2858
  });
2827
2859
  it("ROLLBACK KNOB: owned-only is honored on a prs_ personal vault — an intent-less entry with a " +
@@ -2912,7 +2944,7 @@ describe("share", () => {
2912
2944
  ]);
2913
2945
  // Journal entry survives, so a later run (or a watcher-minted intent)
2914
2946
  // can still authorize the delete properly.
2915
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
2947
+ const journal = journalAt(journalPath);
2916
2948
  expect(journal.files["personal/knowledge/gone.md"]).toBeDefined();
2917
2949
  }
2918
2950
  finally {
@@ -2929,7 +2961,7 @@ describe("share", () => {
2929
2961
  // deleted by the user.
2930
2962
  fs.writeFileSync(path.join(companyRoot, "kept.md"), "still here");
2931
2963
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
2932
- fs.writeFileSync(journalPath, JSON.stringify({
2964
+ seedJournalAt(journalPath, ({
2933
2965
  version: "1",
2934
2966
  lastSync: new Date().toISOString(),
2935
2967
  files: {
@@ -2965,7 +2997,7 @@ describe("share", () => {
2965
2997
  expect(deleteRemoteFile).toHaveBeenCalledTimes(1);
2966
2998
  expect(deleteRemoteFile).toHaveBeenCalledWith(expect.anything(), "gone.md");
2967
2999
  // Journal entry for the gone file is removed; the kept entry stays.
2968
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
3000
+ const journal = journalAt(journalPath);
2969
3001
  expect(journal.files["gone.md"]).toBeUndefined();
2970
3002
  expect(journal.files["kept.md"]).toBeDefined();
2971
3003
  });
@@ -2973,7 +3005,7 @@ describe("share", () => {
2973
3005
  const companyRoot = path.join(tmpDir, "companies", "acme");
2974
3006
  fs.mkdirSync(companyRoot, { recursive: true });
2975
3007
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
2976
- fs.writeFileSync(journalPath, JSON.stringify({
3008
+ seedJournalAt(journalPath, ({
2977
3009
  version: "1",
2978
3010
  lastSync: new Date().toISOString(),
2979
3011
  files: {
@@ -3015,7 +3047,7 @@ describe("share", () => {
3015
3047
  const companyRoot = path.join(tmpDir, "companies", "acme");
3016
3048
  fs.mkdirSync(companyRoot, { recursive: true });
3017
3049
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3018
- fs.writeFileSync(journalPath, JSON.stringify({
3050
+ seedJournalAt(journalPath, ({
3019
3051
  version: "1",
3020
3052
  lastSync: new Date().toISOString(),
3021
3053
  files: {
@@ -3038,7 +3070,7 @@ describe("share", () => {
3038
3070
  expect(result.filesDeleted).toBe(0);
3039
3071
  expect(deleteRemoteFile).not.toHaveBeenCalled();
3040
3072
  // Journal entry survives so the next opt-in run can still propagate.
3041
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
3073
+ const journal = journalAt(journalPath);
3042
3074
  expect(journal.files["gone.md"]).toBeDefined();
3043
3075
  });
3044
3076
  it("propagateDeletes: scope is constrained to the supplied paths — sibling deletes are not swept", async () => {
@@ -3046,7 +3078,7 @@ describe("share", () => {
3046
3078
  fs.mkdirSync(path.join(companyRoot, "in-scope"), { recursive: true });
3047
3079
  fs.mkdirSync(path.join(companyRoot, "other"), { recursive: true });
3048
3080
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3049
- fs.writeFileSync(journalPath, JSON.stringify({
3081
+ seedJournalAt(journalPath, ({
3050
3082
  version: "1",
3051
3083
  lastSync: new Date().toISOString(),
3052
3084
  files: {
@@ -3081,7 +3113,7 @@ describe("share", () => {
3081
3113
  expect(result.filesDeleted).toBe(1);
3082
3114
  expect(deleteRemoteFile).toHaveBeenCalledTimes(1);
3083
3115
  expect(deleteRemoteFile).toHaveBeenCalledWith(expect.anything(), "in-scope/gone.md");
3084
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
3116
+ const journal = journalAt(journalPath);
3085
3117
  expect(journal.files["in-scope/gone.md"]).toBeUndefined();
3086
3118
  // Sibling tree's journal entry is untouched — `hq share <subtree>` must
3087
3119
  // not act on files outside the named scope.
@@ -3097,7 +3129,7 @@ describe("share", () => {
3097
3129
  fs.mkdirSync(path.join(companyRoot, "in-scope"), { recursive: true });
3098
3130
  fs.mkdirSync(path.join(companyRoot, "other"), { recursive: true });
3099
3131
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3100
- fs.writeFileSync(journalPath, JSON.stringify({
3132
+ seedJournalAt(journalPath, ({
3101
3133
  version: "1",
3102
3134
  lastSync: new Date().toISOString(),
3103
3135
  files: {
@@ -3132,7 +3164,7 @@ describe("share", () => {
3132
3164
  });
3133
3165
  expect(result.filesDeleted).toBe(1);
3134
3166
  expect(deleteRemoteFile).toHaveBeenCalledWith(expect.anything(), "in-scope/gone.md");
3135
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
3167
+ const journal = journalAt(journalPath);
3136
3168
  expect(journal.files["in-scope/gone.md"]).toBeUndefined();
3137
3169
  // Scope stays exactly as narrow as the absolute spelling's — no wider.
3138
3170
  expect(journal.files["other/also-gone.md"]).toBeDefined();
@@ -3149,7 +3181,7 @@ describe("share", () => {
3149
3181
  fs.mkdirSync(acmeRepo, { recursive: true });
3150
3182
  fs.symlinkSync(acmeRepo, path.join(companyRoot, "knowledge"));
3151
3183
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3152
- fs.writeFileSync(journalPath, JSON.stringify({
3184
+ seedJournalAt(journalPath, ({
3153
3185
  version: "1",
3154
3186
  lastSync: new Date().toISOString(),
3155
3187
  files: {
@@ -3179,14 +3211,14 @@ describe("share", () => {
3179
3211
  });
3180
3212
  expect(result.filesDeleted).toBe(0);
3181
3213
  expect(deleteRemoteFile).not.toHaveBeenCalled();
3182
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
3214
+ const journal = journalAt(journalPath);
3183
3215
  expect(journal.files["knowledge/agents/brief.md"]).toBeDefined();
3184
3216
  });
3185
3217
  it("propagateDeletes: a failed DeleteObject leaves the journal entry intact for retry", async () => {
3186
3218
  const companyRoot = path.join(tmpDir, "companies", "acme");
3187
3219
  fs.mkdirSync(companyRoot, { recursive: true });
3188
3220
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3189
- fs.writeFileSync(journalPath, JSON.stringify({
3221
+ seedJournalAt(journalPath, ({
3190
3222
  version: "1",
3191
3223
  lastSync: new Date().toISOString(),
3192
3224
  files: {
@@ -3219,7 +3251,7 @@ describe("share", () => {
3219
3251
  const errorEvent = events.find((e) => e.type === "error");
3220
3252
  expect(errorEvent).toMatchObject({ path: "flaky.md", message: expect.stringContaining("S3 down") });
3221
3253
  // Entry survives — next run will retry the delete.
3222
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
3254
+ const journal = journalAt(journalPath);
3223
3255
  expect(journal.files["flaky.md"]).toBeDefined();
3224
3256
  });
3225
3257
  // ── Delete propagation safety: direction + filter guards (Bug B + C) ──────
@@ -3253,7 +3285,7 @@ describe("share", () => {
3253
3285
  // and one this machine uploaded (direction:'up'). Only the 'up' one should
3254
3286
  // be eligible for delete-propagation.
3255
3287
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3256
- fs.writeFileSync(journalPath, JSON.stringify({
3288
+ seedJournalAt(journalPath, ({
3257
3289
  version: "1",
3258
3290
  lastSync: new Date().toISOString(),
3259
3291
  files: {
@@ -3290,7 +3322,7 @@ describe("share", () => {
3290
3322
  const companyRoot = path.join(tmpDir, "companies", "acme");
3291
3323
  fs.mkdirSync(companyRoot, { recursive: true });
3292
3324
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3293
- fs.writeFileSync(journalPath, JSON.stringify({
3325
+ seedJournalAt(journalPath, ({
3294
3326
  version: "1",
3295
3327
  lastSync: new Date().toISOString(),
3296
3328
  files: {
@@ -3323,7 +3355,7 @@ describe("share", () => {
3323
3355
  // absence is a filter outcome, not a user-intended delete.
3324
3356
  fs.writeFileSync(path.join(tmpDir, ".hqignore"), "companies/acme/legacy/**\n");
3325
3357
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3326
- fs.writeFileSync(journalPath, JSON.stringify({
3358
+ seedJournalAt(journalPath, ({
3327
3359
  version: "1",
3328
3360
  lastSync: new Date().toISOString(),
3329
3361
  files: {
@@ -3382,7 +3414,7 @@ describe("share", () => {
3382
3414
  // Under owned-only this would be stuck on remote forever; currency-gated
3383
3415
  // checks the ETag and propagates the delete cleanly when match holds.
3384
3416
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3385
- fs.writeFileSync(journalPath, JSON.stringify({
3417
+ seedJournalAt(journalPath, ({
3386
3418
  version: "1",
3387
3419
  lastSync: new Date().toISOString(),
3388
3420
  files: {
@@ -3412,7 +3444,7 @@ describe("share", () => {
3412
3444
  });
3413
3445
  expect(result.filesDeleted).toBe(1);
3414
3446
  expect(deleteRemoteFile).toHaveBeenCalledWith(expect.anything(), "core/policies/old.md");
3415
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
3447
+ const journal = journalAt(journalPath);
3416
3448
  expect(journal.files["core/policies/old.md"]).toBeUndefined();
3417
3449
  });
3418
3450
  it("currency-gated: propagates the S3 delete for a local-delete tombstone (pull-leg producer hand-off)", async () => {
@@ -3423,7 +3455,7 @@ describe("share", () => {
3423
3455
  const companyRoot = path.join(tmpDir, "companies", "acme");
3424
3456
  fs.mkdirSync(companyRoot, { recursive: true });
3425
3457
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3426
- fs.writeFileSync(journalPath, JSON.stringify({
3458
+ seedJournalAt(journalPath, ({
3427
3459
  version: "2",
3428
3460
  lastSync: new Date().toISOString(),
3429
3461
  files: {
@@ -3459,7 +3491,7 @@ describe("share", () => {
3459
3491
  });
3460
3492
  expect(result.filesDeleted).toBe(1);
3461
3493
  expect(deleteRemoteFile).toHaveBeenCalledWith(expect.anything(), "docs/deleted.md");
3462
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
3494
+ const journal = journalAt(journalPath);
3463
3495
  expect(journal.files["docs/deleted.md"]).toBeUndefined();
3464
3496
  });
3465
3497
  it("currency-gated: refuses delete + emits stale-etag event when remote moved since last sync", async () => {
@@ -3468,7 +3500,7 @@ describe("share", () => {
3468
3500
  // Another device modified `shared.md` after this device's last sync.
3469
3501
  // The journal still records the old etag; HEAD returns the new one.
3470
3502
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3471
- fs.writeFileSync(journalPath, JSON.stringify({
3503
+ seedJournalAt(journalPath, ({
3472
3504
  version: "1",
3473
3505
  lastSync: new Date().toISOString(),
3474
3506
  files: {
@@ -3500,7 +3532,7 @@ describe("share", () => {
3500
3532
  // No remote DELETE issued, journal entry preserved (pull will re-pull).
3501
3533
  expect(result.filesDeleted).toBe(0);
3502
3534
  expect(deleteRemoteFile).not.toHaveBeenCalled();
3503
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
3535
+ const journal = journalAt(journalPath);
3504
3536
  expect(journal.files["shared.md"]).toBeDefined();
3505
3537
  // Dedicated event fired so UIs surface the refusal. `reason` field
3506
3538
  // discriminates "stale-etag" (real drift) from "legacy-no-etag" so
@@ -3522,7 +3554,7 @@ describe("share", () => {
3522
3554
  fs.mkdirSync(companyRoot, { recursive: true });
3523
3555
  const recreatedPath = path.join(companyRoot, "racy.md");
3524
3556
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3525
- fs.writeFileSync(journalPath, JSON.stringify({
3557
+ seedJournalAt(journalPath, ({
3526
3558
  version: "2",
3527
3559
  lastSync: new Date().toISOString(),
3528
3560
  files: {
@@ -3558,7 +3590,7 @@ describe("share", () => {
3558
3590
  expect(result.filesDeleted).toBe(0);
3559
3591
  expect(deleteRemoteFile).not.toHaveBeenCalled();
3560
3592
  expect(fs.readFileSync(recreatedPath, "utf-8")).toBe("recreated after planning");
3561
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
3593
+ const journal = journalAt(journalPath);
3562
3594
  expect(journal.files["racy.md"]).toBeDefined();
3563
3595
  expect(events).toContainEqual(expect.objectContaining({
3564
3596
  type: "delete-refused-stale-etag",
@@ -3580,7 +3612,7 @@ describe("share", () => {
3580
3612
  // genuine local==remote sync, so propagating the delete would destroy the
3581
3613
  // (different) remote version. Must be REFUSED, not propagated.
3582
3614
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3583
- fs.writeFileSync(journalPath, JSON.stringify({
3615
+ seedJournalAt(journalPath, ({
3584
3616
  version: "2",
3585
3617
  lastSync: new Date().toISOString(),
3586
3618
  files: {
@@ -3615,7 +3647,7 @@ describe("share", () => {
3615
3647
  // Refusal short-circuits before the currency HEAD — no network needed.
3616
3648
  expect(headRemoteFile).not.toHaveBeenCalled();
3617
3649
  // Journal entry preserved, not silently dropped.
3618
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
3650
+ const journal = journalAt(journalPath);
3619
3651
  expect(journal.files["core/policies/kept.md"]).toBeDefined();
3620
3652
  // Surfaced as a refusal with the divergent-local reason.
3621
3653
  const refused = events.find((e) => e.type === "delete-refused-stale-etag");
@@ -3632,7 +3664,7 @@ describe("share", () => {
3632
3664
  // S3 console). Local copy also missing. Currency-gated should drop the
3633
3665
  // journal entry without issuing a DELETE — the remote is already gone.
3634
3666
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3635
- fs.writeFileSync(journalPath, JSON.stringify({
3667
+ seedJournalAt(journalPath, ({
3636
3668
  version: "1",
3637
3669
  lastSync: new Date().toISOString(),
3638
3670
  files: {
@@ -3665,7 +3697,7 @@ describe("share", () => {
3665
3697
  // from real deletes without re-counting events.
3666
3698
  expect(result.filesTombstoned).toBe(1);
3667
3699
  expect(result.filesRefusedStale).toBe(0);
3668
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
3700
+ const journal = journalAt(journalPath);
3669
3701
  expect(journal.files["removed-out-of-band.md"]).toBeUndefined();
3670
3702
  // Synthetic progress event carries the tombstone marker. Without this,
3671
3703
  // the tty stream renders tombstones identically to real deletes — operator
@@ -3686,7 +3718,7 @@ describe("share", () => {
3686
3718
  // against. Refuse the delete in the safe direction; a future sync with
3687
3719
  // a recorded etag can re-evaluate.
3688
3720
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3689
- fs.writeFileSync(journalPath, JSON.stringify({
3721
+ seedJournalAt(journalPath, ({
3690
3722
  version: "1",
3691
3723
  lastSync: new Date().toISOString(),
3692
3724
  files: {
@@ -3740,7 +3772,7 @@ describe("share", () => {
3740
3772
  const companyRoot = path.join(tmpDir, "companies", "acme");
3741
3773
  fs.mkdirSync(companyRoot, { recursive: true });
3742
3774
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3743
- fs.writeFileSync(journalPath, JSON.stringify({
3775
+ seedJournalAt(journalPath, ({
3744
3776
  version: "1",
3745
3777
  lastSync: new Date().toISOString(),
3746
3778
  files: {
@@ -3792,7 +3824,7 @@ describe("share", () => {
3792
3824
  const companyRoot = path.join(tmpDir, "companies", "acme");
3793
3825
  fs.mkdirSync(companyRoot, { recursive: true });
3794
3826
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3795
- fs.writeFileSync(journalPath, JSON.stringify({
3827
+ seedJournalAt(journalPath, ({
3796
3828
  version: "1",
3797
3829
  lastSync: new Date().toISOString(),
3798
3830
  files: {
@@ -3826,7 +3858,7 @@ describe("share", () => {
3826
3858
  const companyRoot = path.join(tmpDir, "companies", "acme");
3827
3859
  fs.mkdirSync(companyRoot, { recursive: true });
3828
3860
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3829
- fs.writeFileSync(journalPath, JSON.stringify({
3861
+ seedJournalAt(journalPath, ({
3830
3862
  version: "1",
3831
3863
  lastSync: new Date().toISOString(),
3832
3864
  files: {
@@ -3876,7 +3908,7 @@ describe("share", () => {
3876
3908
  fs.mkdirSync(companyRoot, { recursive: true });
3877
3909
  const recreatedPath = path.join(companyRoot, "notes", "gone.md");
3878
3910
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3879
- fs.writeFileSync(journalPath, JSON.stringify({
3911
+ seedJournalAt(journalPath, ({
3880
3912
  version: "1",
3881
3913
  lastSync: new Date().toISOString(),
3882
3914
  files: {
@@ -3924,7 +3956,7 @@ describe("share", () => {
3924
3956
  const companyRoot = path.join(tmpDir, "companies", "acme");
3925
3957
  fs.mkdirSync(companyRoot, { recursive: true });
3926
3958
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3927
- fs.writeFileSync(journalPath, JSON.stringify({
3959
+ seedJournalAt(journalPath, ({
3928
3960
  version: "1",
3929
3961
  lastSync: new Date().toISOString(),
3930
3962
  files: {
@@ -3954,14 +3986,14 @@ describe("share", () => {
3954
3986
  type: "delete-refused-stale-etag",
3955
3987
  reason: "legacy-no-etag",
3956
3988
  }));
3957
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
3989
+ const journal = journalAt(journalPath);
3958
3990
  expect(journal.files["notes/legacy.md"]).toBeDefined();
3959
3991
  });
3960
3992
  it("owned-only still requires an intent — it is the documented rollback knob", async () => {
3961
3993
  const companyRoot = path.join(tmpDir, "companies", "acme");
3962
3994
  fs.mkdirSync(companyRoot, { recursive: true });
3963
3995
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3964
- fs.writeFileSync(journalPath, JSON.stringify({
3996
+ seedJournalAt(journalPath, ({
3965
3997
  version: "1",
3966
3998
  lastSync: new Date().toISOString(),
3967
3999
  files: {
@@ -4148,7 +4180,7 @@ describe("share", () => {
4148
4180
  const companyRoot = path.join(tmpDir, "companies", "acme");
4149
4181
  fs.mkdirSync(companyRoot, { recursive: true });
4150
4182
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
4151
- fs.writeFileSync(journalPath, JSON.stringify({
4183
+ seedJournalAt(journalPath, ({
4152
4184
  version: "1",
4153
4185
  lastSync: new Date().toISOString(),
4154
4186
  files: {
@@ -4189,7 +4221,7 @@ describe("share", () => {
4189
4221
  expect(deleteRemoteFile).toHaveBeenCalledWith(expect.anything(), "regular.md");
4190
4222
  expect(deleteRemoteFile).toHaveBeenCalledWith(expect.anything(), "CLAUDE.md.conflict-2026-05-13T19-40-40Z-e5797a.md");
4191
4223
  // Both journal entries are removed by the delete loop's removeEntry call.
4192
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
4224
+ const journal = journalAt(journalPath);
4193
4225
  expect(journal.files["CLAUDE.md.conflict-2026-05-13T19-40-40Z-e5797a.md"]).toBeUndefined();
4194
4226
  expect(journal.files["regular.md"]).toBeUndefined();
4195
4227
  });
@@ -4204,7 +4236,7 @@ describe("share", () => {
4204
4236
  process.env.HQ_STATE_DIR = stateDirPersonal;
4205
4237
  try {
4206
4238
  const journalPath = path.join(stateDirPersonal, "sync-journal.__hq_personal_vault__.json");
4207
- fs.writeFileSync(journalPath, JSON.stringify({
4239
+ seedJournalAt(journalPath, ({
4208
4240
  version: "1",
4209
4241
  lastSync: new Date().toISOString(),
4210
4242
  files: {
@@ -4230,7 +4262,7 @@ describe("share", () => {
4230
4262
  });
4231
4263
  expect(result.filesDeleted).toBe(1);
4232
4264
  expect(deleteRemoteFile).toHaveBeenCalledWith(expect.anything(), "personal/.obsidian/graph.json.drift-1779863862-42519");
4233
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
4265
+ const journal = journalAt(journalPath);
4234
4266
  expect(journal.files["personal/.obsidian/graph.json.drift-1779863862-42519"]).toBeUndefined();
4235
4267
  }
4236
4268
  finally {
@@ -4253,7 +4285,7 @@ describe("share", () => {
4253
4285
  hash: "h", size: 1, syncedAt: new Date().toISOString(),
4254
4286
  direction: "up",
4255
4287
  });
4256
- fs.writeFileSync(journalPath, JSON.stringify({
4288
+ seedJournalAt(journalPath, ({
4257
4289
  version: "1",
4258
4290
  lastSync: new Date().toISOString(),
4259
4291
  files: {
@@ -4301,7 +4333,7 @@ describe("share", () => {
4301
4333
  expect(pruned[0].byReason["ignore-filtered"]).toBe(3);
4302
4334
  expect(pruned[0].byReason["excluded-top-level"]).toBe(1);
4303
4335
  // The persisted journal converged: excluded rows gone, legit row kept.
4304
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
4336
+ const journal = journalAt(journalPath);
4305
4337
  expect(Object.keys(journal.files)).toContain("personal/keep.md");
4306
4338
  expect(journal.files[".claude/worktrees/wt/a.ts"]).toBeUndefined();
4307
4339
  expect(journal.files["personal/node_modules/pkg/index.js"]).toBeUndefined();
@@ -4333,7 +4365,7 @@ describe("share", () => {
4333
4365
  const key = "deleted-top-level.md";
4334
4366
  const journalSlug = "personal-scoped-delete";
4335
4367
  const journalPath = path.join(stateDir, `sync-journal.${journalSlug}.json`);
4336
- fs.writeFileSync(journalPath, JSON.stringify({
4368
+ seedJournalAt(journalPath, ({
4337
4369
  version: "2",
4338
4370
  lastSync: new Date().toISOString(),
4339
4371
  pulls: [],
@@ -4365,7 +4397,7 @@ describe("share", () => {
4365
4397
  });
4366
4398
  expect(result.filesDeleted).toBe(1);
4367
4399
  expect(deleteRemoteFile).toHaveBeenCalledWith(expect.anything(), key);
4368
- expect(JSON.parse(fs.readFileSync(journalPath, "utf8")).files[key]).toBeUndefined();
4400
+ expect(journalAt(journalPath).files[key]).toBeUndefined();
4369
4401
  });
4370
4402
  // ── Decommission prefixes ──────────────────────────────────────────────────
4371
4403
  //
@@ -4420,7 +4452,7 @@ describe("share", () => {
4420
4452
  // Local file remains on disk — decommission is a remote-only sweep.
4421
4453
  expect(fs.existsSync(path.join(tmpDir, "companies", "foo", "notes.md"))).toBe(true);
4422
4454
  // Journal entry pruned in lockstep with the remote delete.
4423
- const journalAfter = JSON.parse(fs.readFileSync(personalJournalPath, "utf-8"));
4455
+ const journalAfter = journalAt(personalJournalPath);
4424
4456
  expect(journalAfter.files["companies/foo/notes.md"]).toBeUndefined();
4425
4457
  expect(journalAfter.files["knowledge/x.md"]).toBeDefined();
4426
4458
  });
@@ -4456,7 +4488,7 @@ describe("share", () => {
4456
4488
  expect(deleteRemoteFile).not.toHaveBeenCalled();
4457
4489
  // Journal entry preserved — the pulled-from-peer record stays so a
4458
4490
  // future pull can still reconcile it correctly.
4459
- const journalAfter = JSON.parse(fs.readFileSync(personalJournalPath, "utf-8"));
4491
+ const journalAfter = journalAt(personalJournalPath);
4460
4492
  expect(journalAfter.files["companies/foo/from-peer.md"]).toBeDefined();
4461
4493
  });
4462
4494
  it("decommissionPrefixes: dedupes with propagateDeletes plan — a key in both still emits one DeleteObject", async () => {
@@ -4576,7 +4608,7 @@ describe("share", () => {
4576
4608
  expect(result.filesTombstoned).toBe(1);
4577
4609
  expect(result.filesDeleted).toBe(0);
4578
4610
  // Journal entry is gone.
4579
- const journalAfter = JSON.parse(fs.readFileSync(personalJournalPath, "utf-8"));
4611
+ const journalAfter = journalAt(personalJournalPath);
4580
4612
  expect(journalAfter.files["companies/foo/gone.md"]).toBeUndefined();
4581
4613
  });
4582
4614
  describe("personalMode", () => {
@@ -4617,7 +4649,7 @@ describe("share", () => {
4617
4649
  const acmeJournalPath = path.join(stateDir, "sync-journal.acme.json");
4618
4650
  expect(fs.existsSync(personalJournalPath)).toBe(true);
4619
4651
  expect(fs.existsSync(acmeJournalPath)).toBe(false);
4620
- const journal = JSON.parse(fs.readFileSync(personalJournalPath, "utf-8"));
4652
+ const journal = journalAt(personalJournalPath);
4621
4653
  expect(journal.files["knowledge/notes.md"]).toBeDefined();
4622
4654
  });
4623
4655
  it("personalMode=true accepts files outside companies/<slug>/ (companion to the company-folder rejection)", async () => {
@@ -4905,7 +4937,7 @@ describe("share", () => {
4905
4937
  .createHash("sha256")
4906
4938
  .update(target)
4907
4939
  .digest("hex");
4908
- fs.writeFileSync(journalPath, JSON.stringify({
4940
+ seedJournalAt(journalPath, ({
4909
4941
  version: "1",
4910
4942
  lastSync: new Date().toISOString(),
4911
4943
  files: {
@@ -5308,7 +5340,7 @@ describe("share", () => {
5308
5340
  });
5309
5341
  expect(pushed.filesUploaded).toBe(1);
5310
5342
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
5311
- const afterPush = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
5343
+ const afterPush = journalAt(journalPath);
5312
5344
  expect(Object.keys(afterPush.files)).toContain("knowledge/agents/brief.md");
5313
5345
  vi.mocked(deleteRemoteFile).mockClear();
5314
5346
  const walked = await share({
@@ -5322,7 +5354,7 @@ describe("share", () => {
5322
5354
  expect(walked.filesDeleted).toBe(0);
5323
5355
  const deletedKeys = vi.mocked(deleteRemoteFile).mock.calls.map((c) => c[1]);
5324
5356
  expect(deletedKeys).not.toContain("knowledge/agents/brief.md");
5325
- const afterWalk = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
5357
+ const afterWalk = journalAt(journalPath);
5326
5358
  expect(Object.keys(afterWalk.files)).toContain("knowledge/agents/brief.md");
5327
5359
  });
5328
5360
  });
@@ -5366,7 +5398,7 @@ describe("share", () => {
5366
5398
  }
5367
5399
  }
5368
5400
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
5369
- fs.writeFileSync(journalPath, JSON.stringify({
5401
+ seedJournalAt(journalPath, ({
5370
5402
  version: "1",
5371
5403
  lastSync: new Date().toISOString(),
5372
5404
  files,
@@ -5424,7 +5456,7 @@ describe("share", () => {
5424
5456
  expect(summary.samplePaths.length).toBeGreaterThan(0);
5425
5457
  expect(summary.samplePaths.length).toBeLessThanOrEqual(10);
5426
5458
  // Journal entries for the missing files SURVIVE — guard never mutates.
5427
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
5459
+ const journal = journalAt(journalPath);
5428
5460
  expect(Object.keys(journal.files).length).toBe(100);
5429
5461
  // Per-key delete-refused-stale-etag events for each candidate.
5430
5462
  const perKeyRefusals = events.filter((e) => e.type === "delete-refused-stale-etag" && e.reason === "bulk-asymmetry");
@@ -5474,7 +5506,7 @@ describe("share", () => {
5474
5506
  expect(headedKeys).not.toContain(`f-${i.toString().padStart(4, "0")}.md`);
5475
5507
  }
5476
5508
  // Journal untouched — the entries stay recoverable.
5477
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
5509
+ const journal = journalAt(journalPath);
5478
5510
  expect(Object.keys(journal.files).length).toBe(100);
5479
5511
  });
5480
5512
  // The core of the #227 regression, isolated: pre-#227 a trip emptied
@@ -5505,7 +5537,7 @@ describe("share", () => {
5505
5537
  const perKeyRefusals = events.filter((e) => e.type === "delete-refused-stale-etag" &&
5506
5538
  e.reason === "bulk-asymmetry");
5507
5539
  expect(perKeyRefusals.length).toBe(11);
5508
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
5540
+ const journal = journalAt(journalPath);
5509
5541
  expect(Object.keys(journal.files).length).toBe(100);
5510
5542
  });
5511
5543
  it("vault litter still drains when the breaker trips", async () => {
@@ -5519,7 +5551,7 @@ describe("share", () => {
5519
5551
  withIntents: false,
5520
5552
  });
5521
5553
  const litterKey = "CLAUDE.md.conflict-2026-05-13T19-40-40Z-e5797a.md";
5522
- const seeded = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
5554
+ const seeded = journalAt(journalPath);
5523
5555
  seeded.files[litterKey] = {
5524
5556
  hash: "h",
5525
5557
  size: 5,
@@ -5528,7 +5560,7 @@ describe("share", () => {
5528
5560
  remoteEtag: "litter-etag",
5529
5561
  kind: "file",
5530
5562
  };
5531
- fs.writeFileSync(journalPath, JSON.stringify(seeded));
5563
+ seedJournalAt(journalPath, seeded);
5532
5564
  vi.mocked(headRemoteFile).mockImplementation(async (_ctx, key) => ({
5533
5565
  etag: `etag-${parseInt(key.replace(/[^0-9]/g, ""), 10)}`,
5534
5566
  lastModified: new Date(),
@@ -5668,7 +5700,7 @@ describe("share", () => {
5668
5700
  expect(summary).toBeDefined();
5669
5701
  expect(summary.ratio).toBeCloseTo(1.0, 5);
5670
5702
  // Journal untouched.
5671
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
5703
+ const journal = journalAt(journalPath);
5672
5704
  expect(Object.keys(journal.files).length).toBe(50);
5673
5705
  });
5674
5706
  it("small absolute count (9/9 = 100% ratio but < MIN_ABS=10) does NOT trip: all 9 delete normally", async () => {
@@ -5712,7 +5744,7 @@ describe("share", () => {
5712
5744
  expect(deleteRemoteFile).not.toHaveBeenCalled();
5713
5745
  expect(events.find((e) => e.type === "delete-refused-bulk-asymmetry")).toBeDefined();
5714
5746
  // Journal entries survive.
5715
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
5747
+ const journal = journalAt(journalPath);
5716
5748
  expect(Object.keys(journal.files).length).toBe(100);
5717
5749
  });
5718
5750
  it("direction:'down' entries don't count toward the bulk numerator (owned-only silently skips them)", async () => {
@@ -5740,7 +5772,7 @@ describe("share", () => {
5740
5772
  localDeleteIntent: deleteIntent("up-etag", "h"),
5741
5773
  };
5742
5774
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
5743
- fs.writeFileSync(journalPath, JSON.stringify({ version: "1", lastSync: now, files }));
5775
+ seedJournalAt(journalPath, { version: "1", lastSync: now, files });
5744
5776
  const result = await share({
5745
5777
  paths: [companyRoot],
5746
5778
  company: "acme",
@@ -5863,7 +5895,7 @@ describe("currency-gated: journal version 2 fixtures", () => {
5863
5895
  fs.mkdirSync(companyRoot, { recursive: true });
5864
5896
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
5865
5897
  // v2-shaped fixture — same field set the production journal uses.
5866
- fs.writeFileSync(journalPath, JSON.stringify({
5898
+ seedJournalAt(journalPath, ({
5867
5899
  version: "2",
5868
5900
  lastSync: new Date().toISOString(),
5869
5901
  files: {
@@ -5909,7 +5941,7 @@ describe("currency-gated: journal version 2 fixtures", () => {
5909
5941
  const { hashFile } = await import("../journal.js");
5910
5942
  const baselineHash = hashFile(testFile);
5911
5943
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
5912
- fs.writeFileSync(journalPath, JSON.stringify({
5944
+ seedJournalAt(journalPath, ({
5913
5945
  version: "2",
5914
5946
  lastSync: new Date().toISOString(),
5915
5947
  files: {
@@ -5950,7 +5982,7 @@ describe("currency-gated: journal version 2 fixtures", () => {
5950
5982
  const originalLstat = fs.lstatSync(testFile);
5951
5983
  // The recorded hash intentionally differs from the current bytes.
5952
5984
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
5953
- fs.writeFileSync(journalPath, JSON.stringify({
5985
+ seedJournalAt(journalPath, ({
5954
5986
  version: "2",
5955
5987
  lastSync: new Date().toISOString(),
5956
5988
  files: {
@@ -5997,7 +6029,7 @@ describe("currency-gated: journal version 2 fixtures", () => {
5997
6029
  const { hashFile } = await import("../journal.js");
5998
6030
  const realHash = hashFile(testFile);
5999
6031
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
6000
- fs.writeFileSync(journalPath, JSON.stringify({
6032
+ seedJournalAt(journalPath, ({
6001
6033
  version: "2",
6002
6034
  lastSync: new Date().toISOString(),
6003
6035
  files: {
@@ -6047,7 +6079,7 @@ describe("currency-gated: journal version 2 fixtures", () => {
6047
6079
  const { hashFile } = await import("../journal.js");
6048
6080
  const realHash = hashFile(testFile);
6049
6081
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
6050
- fs.writeFileSync(journalPath, JSON.stringify({
6082
+ seedJournalAt(journalPath, ({
6051
6083
  version: "2",
6052
6084
  lastSync: new Date().toISOString(),
6053
6085
  files: {
@@ -6079,7 +6111,7 @@ describe("currency-gated: journal version 2 fixtures", () => {
6079
6111
  expect(uploadFile).not.toHaveBeenCalled();
6080
6112
  // Journal mtimeMs is now the bumped value, while content hashing remains
6081
6113
  // authoritative on the next sync.
6082
- const journalAfter = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
6114
+ const journalAfter = journalAt(journalPath);
6083
6115
  expect(journalAfter.files["touched-then-fast.md"].mtimeMs).toBe(bumpedLstat.mtimeMs);
6084
6116
  expect(journalAfter.files["touched-then-fast.md"].size).toBe(bumpedLstat.size);
6085
6117
  // Content hash untouched — the file genuinely didn't change.
@@ -6099,7 +6131,7 @@ describe("currency-gated: journal version 2 fixtures", () => {
6099
6131
  });
6100
6132
  expect(result.filesUploaded).toBe(1);
6101
6133
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
6102
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
6134
+ const journal = journalAt(journalPath);
6103
6135
  expect(journal.files["fresh.md"]).toBeDefined();
6104
6136
  expect(journal.files["fresh.md"].mtimeMs).toBe(lstat.mtimeMs);
6105
6137
  expect(journal.files["fresh.md"].size).toBe(lstat.size);
@@ -6116,7 +6148,7 @@ describe("currency-gated: journal version 2 fixtures", () => {
6116
6148
  const realSize = fs.statSync(testFile).size;
6117
6149
  // Real hash but NO mtimeMs. Hash matches → skip upload.
6118
6150
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
6119
- fs.writeFileSync(journalPath, JSON.stringify({
6151
+ seedJournalAt(journalPath, ({
6120
6152
  version: "1",
6121
6153
  lastSync: new Date().toISOString(),
6122
6154
  files: {
@@ -6675,7 +6707,7 @@ describe("currency-gated: journal version 2 fixtures", () => {
6675
6707
  // AND lost their etag/content-hash/stat journal metadata.
6676
6708
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
6677
6709
  expect(fs.existsSync(journalPath)).toBe(true);
6678
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
6710
+ const journal = journalAt(journalPath);
6679
6711
  const stampedKeys = Object.keys(journal.files);
6680
6712
  expect(stampedKeys.length).toBe(FILE_COUNT - 1);
6681
6713
  // The failed file is NOT in the journal (no successful upload).
@@ -6823,7 +6855,7 @@ describe("scope-invalid key hardening on push (doubled companies/ tree — incid
6823
6855
  const companyRoot = path.join(tmpDir, "companies", "acme");
6824
6856
  fs.mkdirSync(companyRoot, { recursive: true });
6825
6857
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
6826
- fs.writeFileSync(journalPath, JSON.stringify({
6858
+ seedJournalAt(journalPath, ({
6827
6859
  version: "1",
6828
6860
  lastSync: new Date().toISOString(),
6829
6861
  files: {
@@ -6852,7 +6884,7 @@ describe("scope-invalid key hardening on push (doubled companies/ tree — incid
6852
6884
  expect(headRemoteFile).not.toHaveBeenCalled();
6853
6885
  expect(deleteRemoteFile).not.toHaveBeenCalled();
6854
6886
  expect(uploadFile).not.toHaveBeenCalled();
6855
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
6887
+ const journal = journalAt(journalPath);
6856
6888
  expect(journal.files["companies/acme/knowledge/poison.md"]).toBeUndefined();
6857
6889
  });
6858
6890
  it("defers an unreadable scope-invalid symlink tombstone without a fatal error", async () => {
@@ -6864,7 +6896,7 @@ describe("scope-invalid key hardening on push (doubled companies/ tree — incid
6864
6896
  fs.mkdirSync(targetDir, { recursive: true });
6865
6897
  fs.symlinkSync(targetDir, linkPath, "dir");
6866
6898
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
6867
- fs.writeFileSync(journalPath, JSON.stringify({
6899
+ seedJournalAt(journalPath, ({
6868
6900
  version: "1",
6869
6901
  lastSync: new Date().toISOString(),
6870
6902
  files: {
@@ -6909,7 +6941,7 @@ describe("scope-invalid key hardening on push (doubled companies/ tree — incid
6909
6941
  samplePaths: [linkKey],
6910
6942
  });
6911
6943
  expect(events.some((event) => event.type === "error")).toBe(false);
6912
- const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
6944
+ const journal = journalAt(journalPath);
6913
6945
  expect(journal.files[linkKey]).toBeDefined();
6914
6946
  }
6915
6947
  finally {