@indigoai-us/hq-cloud 6.15.24 → 6.15.26

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.
@@ -88,6 +88,14 @@ function journalAt(journalPath) {
88
88
  .replace(/\.json$/, "");
89
89
  return readJournal(slug);
90
90
  }
91
+ function sidecarIn(dir) {
92
+ const found = fs.readdirSync(dir).filter((name) => name.includes(".conflict-"));
93
+ expect(found).toHaveLength(1);
94
+ return path.join(dir, found[0]);
95
+ }
96
+ function conflictIndexAt(hqRoot) {
97
+ return JSON.parse(fs.readFileSync(path.join(hqRoot, ".hq-conflicts", "index.json"), "utf-8"));
98
+ }
91
99
  const mockConfig = {
92
100
  apiUrl: "https://vault-api.test",
93
101
  authToken: "test-jwt-token",
@@ -743,7 +751,7 @@ describe("share", () => {
743
751
  expect(entry.ctimeMs).toBe(stat.ctimeMs);
744
752
  });
745
753
  it("never lets the fast path skip a localDiverges entry", async () => {
746
- // A pull-kept divergent entry stores the LOCAL hash and local stat, so
754
+ // A pull-held divergent entry stores the LOCAL hash and local stat, so
747
755
  // every field matches by construction. Skipping on that asserts "local
748
756
  // matches remote", which is exactly what the flag exists to deny.
749
757
  const abs = writeFile("divergent.md", CONTENT_A);
@@ -988,6 +996,10 @@ describe("share", () => {
988
996
  etag: '"remote-new-etag"',
989
997
  size: 99,
990
998
  });
999
+ vi.mocked(downloadFile).mockImplementationOnce(async (_ctx, _key, dest) => {
1000
+ fs.writeFileSync(dest, "remote edit");
1001
+ return undefined;
1002
+ });
991
1003
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
992
1004
  seedJournalAt(journalPath, ({
993
1005
  version: "1",
@@ -1020,6 +1032,24 @@ describe("share", () => {
1020
1032
  direction: "push",
1021
1033
  resolution: "keep",
1022
1034
  });
1035
+ // `keep` is cloud-wins on the push leg too: remote takes the working
1036
+ // name, while the displaced local body remains recoverable beside it.
1037
+ expect(fs.readFileSync(testFile, "utf-8")).toBe("remote edit");
1038
+ expect(fs.readFileSync(sidecarIn(companyRoot), "utf-8")).toBe("local edit");
1039
+ const journal = journalAt(journalPath);
1040
+ expect(journal.files["drifted.md"]).toMatchObject({
1041
+ hash: hashFile(testFile),
1042
+ remoteEtag: "remote-new-etag",
1043
+ direction: "down",
1044
+ });
1045
+ const index = conflictIndexAt(tmpDir);
1046
+ expect(index.conflicts).toHaveLength(1);
1047
+ expect(index.conflicts[0]).toMatchObject({
1048
+ originalPath: "companies/acme/drifted.md",
1049
+ preserved: "local",
1050
+ });
1051
+ expect(fs.readFileSync(path.join(tmpDir, index.conflicts[0].conflictPath), "utf-8"))
1052
+ .toBe("local edit");
1023
1053
  });
1024
1054
  it("cloud-authoritative: never pushes a stale local ontology/.last-run over the server value (one-sided-change clobber guard)", async () => {
1025
1055
  // Regression for the gardener watermark clobber. A second HQ root sharing this
@@ -1087,9 +1117,9 @@ describe("share", () => {
1087
1117
  //
1088
1118
  // Fix: even on first-time upload, if HEAD shows remote exists and
1089
1119
  // its content (etag) differs from what we'd put, emit a conflict.
1090
- // Under keep, the existing pull-side mirror routine downloads the
1091
- // remote bytes to \`<orig>.conflict-<ts>-<short>\` so both versions
1092
- // survive on disk; under abort, the company aborts before PUT.
1120
+ // Under keep, cloud takes the original name and local moves to
1121
+ // \`<orig>.conflict-<ts>-<short>\` so both versions survive on disk;
1122
+ // under abort, the company aborts before PUT.
1093
1123
  const companyRoot = path.join(tmpDir, "companies", "acme");
1094
1124
  fs.mkdirSync(companyRoot, { recursive: true });
1095
1125
  const testFile = path.join(companyRoot, "v7b-fresh.txt");
@@ -1101,6 +1131,10 @@ describe("share", () => {
1101
1131
  etag: '"remote-version-etag"',
1102
1132
  size: 40,
1103
1133
  });
1134
+ vi.mocked(downloadFile).mockImplementation(async (_ctx, _key, dest) => {
1135
+ fs.writeFileSync(dest, "peer remote content");
1136
+ return undefined;
1137
+ });
1104
1138
  // No journal file written — first-time upload contract.
1105
1139
  const events = [];
1106
1140
  const result = await share({
@@ -1112,14 +1146,19 @@ describe("share", () => {
1112
1146
  onEvent: (e) => events.push(e),
1113
1147
  });
1114
1148
  expect(result.conflictPaths).toEqual(["v7b-fresh.txt"]);
1115
- // Under keep, local stays as-is (no upload) and the mirror records
1116
- // the remote bytes so neither version is lost.
1149
+ // Under keep, no upload occurs: the remote bytes take the working name
1150
+ // and the local body is preserved beside them.
1117
1151
  expect(result.filesUploaded).toBe(0);
1118
1152
  const conflicts = events.filter((e) => typeof e === "object" && e !== null && e.type === "conflict");
1119
1153
  expect(conflicts).toHaveLength(1);
1120
1154
  expect(conflicts[0].path).toBe("v7b-fresh.txt");
1121
- // Local file unmodified — it must still contain the local content.
1122
- expect(fs.readFileSync(testFile, "utf-8")).toBe("macos-fresh-collision-content");
1155
+ expect(fs.readFileSync(testFile, "utf-8")).toBe("peer remote content");
1156
+ expect(fs.readFileSync(sidecarIn(companyRoot), "utf-8")).toBe("macos-fresh-collision-content");
1157
+ const index = conflictIndexAt(tmpDir);
1158
+ expect(index.conflicts[0]).toMatchObject({
1159
+ originalPath: "companies/acme/v7b-fresh.txt",
1160
+ preserved: "local",
1161
+ });
1123
1162
  });
1124
1163
  it("first-time-upload-with-cloud-collision: aborts company under --on-conflict abort (Bug #7)", async () => {
1125
1164
  // Symmetric to the keep case: when --on-conflict abort is passed, a
@@ -1147,11 +1186,11 @@ describe("share", () => {
1147
1186
  expect(result.filesUploaded).toBe(0);
1148
1187
  });
1149
1188
  it("HQ-DESKTOP-54: a privilege refusal while writing the push conflict mirror is a skip, not an error", async () => {
1150
- // The keep-mirror also materializes the remote object (to a .conflict-*
1151
- // file). On an unprivileged Windows host that throws the typed privilege
1189
+ // Push keep also materializes the remote object (at the original name).
1190
+ // On an unprivileged Windows host that throws the typed privilege
1152
1191
  // error; it must be downgraded to the deliberate skip so the push stays
1153
1192
  // complete and the runner exits 0 — never type:error. The conflict itself
1154
- // is still recorded and the local file is untouched.
1193
+ // is still surfaced and the local rename is rolled back.
1155
1194
  const companyRoot = path.join(tmpDir, "companies", "acme");
1156
1195
  fs.mkdirSync(companyRoot, { recursive: true });
1157
1196
  const testFile = path.join(companyRoot, "v7b-priv.txt");
@@ -1162,7 +1201,7 @@ describe("share", () => {
1162
1201
  size: 40,
1163
1202
  });
1164
1203
  // On a real unprivileged host BOTH the fresh-collision reconciled probe and
1165
- // the keep-mirror materialize the remote symlink, so both downloadFile calls
1204
+ // the keep adoption materialize the remote symlink, so both downloadFile calls
1166
1205
  // refuse. The probe's catch returns "differs" (conflict) without erroring;
1167
1206
  // the mirror must downgrade the refusal to the deliberate skip.
1168
1207
  vi.mocked(downloadFile)
@@ -1183,7 +1222,7 @@ describe("share", () => {
1183
1222
  target: "../target.md",
1184
1223
  }));
1185
1224
  expect(events.some((e) => e.type === "error")).toBe(false);
1186
- // The conflict is still recorded; the local file is preserved.
1225
+ // The conflict is still surfaced; the failed adoption restores local.
1187
1226
  expect(result.conflictPaths).toEqual(["v7b-priv.txt"]);
1188
1227
  expect(fs.readFileSync(testFile, "utf-8")).toBe("local-collision-content");
1189
1228
  });
@@ -1295,7 +1334,7 @@ describe("share", () => {
1295
1334
  etag: '"0123456789abcdef0123456789abcdef-3"',
1296
1335
  size: 32,
1297
1336
  });
1298
- // The probe (and the subsequent keep-mirror download) return DIFFERENT
1337
+ // The probe (and the subsequent keep adoption download) return DIFFERENT
1299
1338
  // remote bytes.
1300
1339
  vi.mocked(downloadFile).mockImplementation(async (_ctx, _key, dest) => {
1301
1340
  fs.writeFileSync(dest, "a-peers-different-version");
@@ -1314,8 +1353,8 @@ describe("share", () => {
1314
1353
  expect(result.filesUploaded).toBe(0);
1315
1354
  const conflicts = events.filter((e) => typeof e === "object" && e !== null && e.type === "conflict");
1316
1355
  expect(conflicts).toHaveLength(1);
1317
- // Local file untouched under keep.
1318
- expect(fs.readFileSync(testFile, "utf-8")).toBe("my-local-version");
1356
+ expect(fs.readFileSync(testFile, "utf-8")).toBe("a-peers-different-version");
1357
+ expect(fs.readFileSync(sidecarIn(companyRoot), "utf-8")).toBe("my-local-version");
1319
1358
  });
1320
1359
  describe("conditional-write fence (If-Match / If-None-Match / 412)", () => {
1321
1360
  const fence412 = () => Object.assign(new Error("Precondition failed"), {
@@ -1375,12 +1414,18 @@ describe("share", () => {
1375
1414
  });
1376
1415
  expect(uploadFile).toHaveBeenCalledWith(expect.anything(), testFile, "brand-new.md", undefined, { ifNoneMatch: "*" });
1377
1416
  });
1378
- it("412 under keep: conflict event + remote mirrored + ledger entry + NO journal stamp", async () => {
1417
+ it("412 under keep: adopts the racing remote, preserves local, and converges the journal", async () => {
1379
1418
  const companyRoot = path.join(tmpDir, "companies", "acme");
1380
1419
  fs.mkdirSync(companyRoot, { recursive: true });
1381
1420
  const testFile = path.join(companyRoot, "raced.md");
1382
1421
  fs.writeFileSync(testFile, "my local bytes");
1383
- vi.mocked(headRemoteFile).mockResolvedValueOnce(null);
1422
+ vi.mocked(headRemoteFile)
1423
+ .mockResolvedValueOnce(null)
1424
+ .mockResolvedValueOnce({
1425
+ lastModified: new Date(),
1426
+ etag: '"racing-peer-etag"',
1427
+ size: 23,
1428
+ });
1384
1429
  vi.mocked(uploadFile).mockRejectedValueOnce(fence412());
1385
1430
  vi.mocked(downloadFile).mockImplementationOnce(async (_ctx, _key, dest) => {
1386
1431
  fs.writeFileSync(dest, "the racing peer version");
@@ -1400,19 +1445,22 @@ describe("share", () => {
1400
1445
  expect(result.filesUploaded).toBe(0);
1401
1446
  expect(result.aborted).toBe(false);
1402
1447
  expect(events.some((e) => e.type === "conflict" && e.path === "raced.md")).toBe(true);
1403
- // The racing remote bytes were preserved next to the local copy.
1404
- const mirrors = fs
1405
- .readdirSync(companyRoot)
1406
- .filter((f) => f.includes(".conflict-"));
1407
- expect(mirrors).toHaveLength(1);
1408
- // Local copy untouched.
1409
- expect(fs.readFileSync(testFile, "utf-8")).toBe("my local bytes");
1410
- // No journal stamp for the failed PUT — next pass re-evaluates fresh.
1448
+ // The racing remote wins the working name; local remains recoverable.
1449
+ expect(fs.readFileSync(testFile, "utf-8")).toBe("the racing peer version");
1450
+ expect(fs.readFileSync(sidecarIn(companyRoot), "utf-8")).toBe("my local bytes");
1451
+ // The pull-shaped result is journalled as the remote body, so the next
1452
+ // pass does not revive the same resolved conflict.
1411
1453
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
1412
- if (fs.existsSync(journalPath)) {
1413
- const journal = journalAt(journalPath);
1414
- expect(journal.files?.["raced.md"]).toBeUndefined();
1415
- }
1454
+ const journal = journalAt(journalPath);
1455
+ expect(journal.files["raced.md"]).toMatchObject({
1456
+ hash: hashFile(testFile),
1457
+ remoteEtag: "racing-peer-etag",
1458
+ direction: "down",
1459
+ });
1460
+ expect(conflictIndexAt(tmpDir).conflicts[0]).toMatchObject({
1461
+ originalPath: "companies/acme/raced.md",
1462
+ preserved: "local",
1463
+ });
1416
1464
  });
1417
1465
  it("412 under overwrite: retries exactly once WITHOUT the fence (explicit consent)", async () => {
1418
1466
  const companyRoot = path.join(tmpDir, "companies", "acme");
@@ -1491,6 +1539,10 @@ describe("share", () => {
1491
1539
  }
1492
1540
  return { etag: '"should-not-land"' };
1493
1541
  });
1542
+ vi.mocked(downloadFile).mockImplementationOnce(async (_ctx, _key, dest) => {
1543
+ fs.writeFileSync(dest, "reviewed peer body");
1544
+ return undefined;
1545
+ });
1494
1546
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
1495
1547
  seedJournalAt(journalPath, ({
1496
1548
  version: "1",
@@ -1535,8 +1587,9 @@ describe("share", () => {
1535
1587
  }
1536
1588
  // No unfenced retry under keep.
1537
1589
  expect(vi.mocked(uploadFile).mock.calls.some((c) => c[4] === undefined)).toBe(false);
1538
- // Local stale bytes preserved on disk (keep does not pull).
1539
- expect(fs.readFileSync(testFile, "utf-8")).toBe(staleBytes);
1590
+ // Keep adopts the peer body but retains the stale local body beside it.
1591
+ expect(fs.readFileSync(testFile, "utf-8")).toBe("reviewed peer body");
1592
+ expect(fs.readFileSync(sidecarIn(path.dirname(testFile)), "utf-8")).toBe(staleBytes);
1540
1593
  });
1541
1594
  it("legitimate local-ahead still uploads when remote remains at journal baseline", async () => {
1542
1595
  const companyRoot = path.join(tmpDir, "companies", "acme");
@@ -1586,6 +1639,10 @@ describe("share", () => {
1586
1639
  etag: '"remote-concurrent"',
1587
1640
  size: 20,
1588
1641
  });
1642
+ vi.mocked(downloadFile).mockImplementationOnce(async (_ctx, _key, dest) => {
1643
+ fs.writeFileSync(dest, "remote concurrent");
1644
+ return undefined;
1645
+ });
1589
1646
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
1590
1647
  seedJournalAt(journalPath, ({
1591
1648
  version: "1",
@@ -1610,12 +1667,14 @@ describe("share", () => {
1610
1667
  expect(result.conflictPaths).toEqual(["both-changed.md"]);
1611
1668
  expect(result.filesUploaded).toBe(0);
1612
1669
  expect(uploadFile).not.toHaveBeenCalled();
1613
- });
1614
- it("localDiverges under keep: pull-kept divergent local cannot silently become write authority", async () => {
1615
- // After pull --on-conflict keep, journal stamps remoteEtag of the peer
1616
- // version AND localDiverges=true (local never matched that etag). A later
1617
- // push must not treat "remote still at stamped etag" as license to PUT
1618
- // the divergent local body (journal-baseline If-Match would succeed).
1670
+ expect(fs.readFileSync(testFile, "utf-8")).toBe("remote concurrent");
1671
+ expect(fs.readFileSync(sidecarIn(companyRoot), "utf-8")).toBe("local concurrent");
1672
+ });
1673
+ it("localDiverges under keep: a pull-held divergent local yields to cloud without becoming write authority", async () => {
1674
+ // After a pull-side hold (`skip` or pull-leg `publish-local`), the journal
1675
+ // stamps the peer remoteEtag and localDiverges=true because local never
1676
+ // matched that etag. A later push must not treat "remote still at stamped
1677
+ // etag" as license to PUT the divergent local body.
1619
1678
  const companyRoot = path.join(tmpDir, "companies", "acme");
1620
1679
  fs.mkdirSync(companyRoot, { recursive: true });
1621
1680
  const testFile = path.join(companyRoot, "kept-divergent.md");
@@ -1628,6 +1687,10 @@ describe("share", () => {
1628
1687
  etag: `"${REMOTE}"`,
1629
1688
  size: 40,
1630
1689
  });
1690
+ vi.mocked(downloadFile).mockImplementationOnce(async (_ctx, _key, dest) => {
1691
+ fs.writeFileSync(dest, "peer remote body");
1692
+ return undefined;
1693
+ });
1631
1694
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
1632
1695
  seedJournalAt(journalPath, ({
1633
1696
  version: "1",
@@ -1659,7 +1722,10 @@ describe("share", () => {
1659
1722
  expect(events.some((e) => e.type === "conflict" &&
1660
1723
  e.path === "kept-divergent.md" &&
1661
1724
  e.resolution === "keep")).toBe(true);
1662
- expect(fs.readFileSync(testFile, "utf-8")).toBe(localBytes);
1725
+ expect(fs.readFileSync(testFile, "utf-8")).toBe("peer remote body");
1726
+ expect(fs.readFileSync(sidecarIn(companyRoot), "utf-8")).toBe(localBytes);
1727
+ expect(journalAt(journalPath).files["kept-divergent.md"]?.localDiverges)
1728
+ .toBeFalsy();
1663
1729
  });
1664
1730
  it("localDiverges under overwrite: explicit intent may force-write", async () => {
1665
1731
  const companyRoot = path.join(tmpDir, "companies", "acme");
@@ -1816,7 +1882,7 @@ describe("share", () => {
1816
1882
  expect(fs.readFileSync(testFile, "utf-8")).toBe(localBytes);
1817
1883
  });
1818
1884
  // Codex review (PR #356, P1). The sync runner's push leg always calls
1819
- // share() with `skipUnchanged: true`, and a pull-kept entry stores the
1885
+ // share() with `skipUnchanged: true`, and a pull-held entry stores the
1820
1886
  // CURRENT local hash — that is exactly what the journal-honesty stamp
1821
1887
  // records. So `existing.hash === localHash` is true for every stuck file,
1822
1888
  // classifyPushCandidates() returns `skip-unchanged`, and executeUploads()
@@ -1829,7 +1895,7 @@ describe("share", () => {
1829
1895
  const companyRoot = path.join(tmpDir, "companies", "acme");
1830
1896
  fs.mkdirSync(companyRoot, { recursive: true });
1831
1897
  const testFile = path.join(companyRoot, "stuck-under-skip-unchanged.md");
1832
- const localBytes = "the body a pull-keep stamped and then refused to publish";
1898
+ const localBytes = "the body a pull hold stamped and then refused to publish";
1833
1899
  fs.writeFileSync(testFile, localBytes);
1834
1900
  const localHash = hashFile(testFile);
1835
1901
  const REMOTE = "etag-at-keep-time";
@@ -1845,7 +1911,7 @@ describe("share", () => {
1845
1911
  lastSync: new Date().toISOString(),
1846
1912
  files: {
1847
1913
  "stuck-under-skip-unchanged.md": {
1848
- // Deliberately the CURRENT local hash: this is what a pull-keep
1914
+ // Deliberately the CURRENT local hash: this is what a pull hold
1849
1915
  // writes, and it is what makes the fast path swallow the file.
1850
1916
  hash: localHash,
1851
1917
  size: localBytes.length,
@@ -2054,14 +2120,20 @@ describe("share", () => {
2054
2120
  direction: "up",
2055
2121
  });
2056
2122
  });
2057
- it("deleted journaled remote + concurrent create 412 under keep: existing conflict semantics", async () => {
2123
+ it("deleted journaled remote + concurrent create 412 under keep: adopts the winner and preserves local", async () => {
2058
2124
  // Same setup as recreation, but a peer creates the key between HEAD and
2059
- // PUT → If-None-Match:* 412s → keep surfaces conflict, no journal stamp.
2125
+ // PUT → If-None-Match:* 412s → keep adopts the peer's winning create.
2060
2126
  const companyRoot = path.join(tmpDir, "companies", "acme");
2061
2127
  fs.mkdirSync(companyRoot, { recursive: true });
2062
2128
  const testFile = path.join(companyRoot, "race-recreate.md");
2063
2129
  fs.writeFileSync(testFile, "my recreated local");
2064
- vi.mocked(headRemoteFile).mockResolvedValueOnce(null);
2130
+ vi.mocked(headRemoteFile)
2131
+ .mockResolvedValueOnce(null)
2132
+ .mockResolvedValueOnce({
2133
+ lastModified: new Date(),
2134
+ etag: '"peer-created-etag"',
2135
+ size: 18,
2136
+ });
2065
2137
  vi.mocked(uploadFile).mockRejectedValueOnce(fence412());
2066
2138
  vi.mocked(downloadFile).mockImplementationOnce(async (_ctx, _key, dest) => {
2067
2139
  fs.writeFileSync(dest, "peer created first");
@@ -2098,12 +2170,15 @@ describe("share", () => {
2098
2170
  // First (only) attempt must be create-fenced; no unfenced retry under keep.
2099
2171
  expect(uploadFile).toHaveBeenCalledTimes(1);
2100
2172
  expect(vi.mocked(uploadFile).mock.calls[0][4]).toEqual({ ifNoneMatch: "*" });
2101
- // Journal not advanced to a successful upload etag (entry may still be
2102
- // the prior baseline — next pass re-evaluates).
2173
+ // Journal now describes the peer body at the original name.
2103
2174
  const journal = journalAt(journalPath);
2104
- expect(journal.files["race-recreate.md"]?.remoteEtag).toBe("etag-before-delete");
2105
- expect(journal.files["race-recreate.md"]?.hash).toBe("old-hash");
2106
- expect(fs.readFileSync(testFile, "utf-8")).toBe("my recreated local");
2175
+ expect(journal.files["race-recreate.md"]).toMatchObject({
2176
+ remoteEtag: "peer-created-etag",
2177
+ hash: hashFile(testFile),
2178
+ direction: "down",
2179
+ });
2180
+ expect(fs.readFileSync(testFile, "utf-8")).toBe("peer created first");
2181
+ expect(fs.readFileSync(sidecarIn(companyRoot), "utf-8")).toBe("my recreated local");
2107
2182
  });
2108
2183
  });
2109
2184
  it("scoped push (plan-exceeds-grant): syncs the in-scope subset, skips out-of-scope paths, never aborts (feedback_ded09d56)", async () => {