@indigoai-us/hq-cloud 6.14.9 → 6.14.10

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.
@@ -62,6 +62,14 @@ function makeEntityContext(overrides = {}) {
62
62
  ...overrides,
63
63
  };
64
64
  }
65
+ function deleteIntent(remoteEtag, localHash, localKind = "file") {
66
+ return {
67
+ version: 1,
68
+ remoteEtag,
69
+ localHash,
70
+ localKind,
71
+ };
72
+ }
65
73
  describe("wrapFilterWithIgnoreVisibility", () => {
66
74
  it("records noteworthy base-ignore exclusions while counting all rejections", () => {
67
75
  const hqRoot = path.join("/tmp", "hqroot");
@@ -1613,6 +1621,8 @@ describe("share", () => {
1613
1621
  syncedAt: new Date().toISOString(),
1614
1622
  direction: "up",
1615
1623
  remoteEtag: "knowledge-etag",
1624
+ kind: "symlink",
1625
+ localDeleteIntent: deleteIntent("knowledge-etag", "irrelevant-not-checked", "symlink"),
1616
1626
  },
1617
1627
  },
1618
1628
  }));
@@ -1808,6 +1818,8 @@ describe("share", () => {
1808
1818
  syncedAt: new Date().toISOString(),
1809
1819
  direction: "up",
1810
1820
  remoteEtag: "gone-etag",
1821
+ kind: "file",
1822
+ localDeleteIntent: deleteIntent("gone-etag", "irrelevant-not-checked-here"),
1811
1823
  },
1812
1824
  },
1813
1825
  }));
@@ -1842,6 +1854,9 @@ describe("share", () => {
1842
1854
  size: 42,
1843
1855
  syncedAt: new Date().toISOString(),
1844
1856
  direction: "up",
1857
+ remoteEtag: "removed-etag",
1858
+ kind: "file",
1859
+ localDeleteIntent: deleteIntent("removed-etag", "h"),
1845
1860
  },
1846
1861
  },
1847
1862
  }));
@@ -1912,6 +1927,9 @@ describe("share", () => {
1912
1927
  size: 1,
1913
1928
  syncedAt: new Date().toISOString(),
1914
1929
  direction: "up",
1930
+ remoteEtag: "in-scope-etag",
1931
+ kind: "file",
1932
+ localDeleteIntent: deleteIntent("in-scope-etag", "h"),
1915
1933
  },
1916
1934
  "other/also-gone.md": {
1917
1935
  hash: "h",
@@ -1954,6 +1972,9 @@ describe("share", () => {
1954
1972
  size: 5,
1955
1973
  syncedAt: new Date().toISOString(),
1956
1974
  direction: "up",
1975
+ remoteEtag: "flaky-etag",
1976
+ kind: "file",
1977
+ localDeleteIntent: deleteIntent("flaky-etag", "h"),
1957
1978
  },
1958
1979
  },
1959
1980
  }));
@@ -2020,6 +2041,9 @@ describe("share", () => {
2020
2041
  "i-uploaded.md": {
2021
2042
  hash: "h", size: 1, syncedAt: new Date().toISOString(),
2022
2043
  direction: "up",
2044
+ remoteEtag: "uploaded-etag",
2045
+ kind: "file",
2046
+ localDeleteIntent: deleteIntent("uploaded-etag", "h"),
2023
2047
  },
2024
2048
  },
2025
2049
  }));
@@ -2050,6 +2074,9 @@ describe("share", () => {
2050
2074
  "pulled.md": {
2051
2075
  hash: "h", size: 1, syncedAt: new Date().toISOString(),
2052
2076
  direction: "down",
2077
+ remoteEtag: "pulled-etag",
2078
+ kind: "file",
2079
+ localDeleteIntent: deleteIntent("pulled-etag", "h"),
2053
2080
  },
2054
2081
  },
2055
2082
  }));
@@ -2084,6 +2111,9 @@ describe("share", () => {
2084
2111
  "active/current.md": {
2085
2112
  hash: "h", size: 1, syncedAt: new Date().toISOString(),
2086
2113
  direction: "up",
2114
+ remoteEtag: "active-etag",
2115
+ kind: "file",
2116
+ localDeleteIntent: deleteIntent("active-etag", "h"),
2087
2117
  },
2088
2118
  },
2089
2119
  }));
@@ -2137,6 +2167,8 @@ describe("share", () => {
2137
2167
  hash: "h", size: 100, syncedAt: new Date().toISOString(),
2138
2168
  direction: "down",
2139
2169
  remoteEtag: "abc123",
2170
+ kind: "file",
2171
+ localDeleteIntent: deleteIntent("abc123", "h"),
2140
2172
  },
2141
2173
  },
2142
2174
  }));
@@ -2178,6 +2210,8 @@ describe("share", () => {
2178
2210
  syncedAt: new Date().toISOString(),
2179
2211
  direction: "down",
2180
2212
  remoteEtag: "abc123",
2213
+ kind: "file",
2214
+ localDeleteIntent: deleteIntent("abc123", "h"),
2181
2215
  removedAt: new Date().toISOString(),
2182
2216
  removedReason: "local-delete",
2183
2217
  },
@@ -2219,6 +2253,8 @@ describe("share", () => {
2219
2253
  hash: "h", size: 50, syncedAt: new Date().toISOString(),
2220
2254
  direction: "down",
2221
2255
  remoteEtag: "stale-etag",
2256
+ kind: "file",
2257
+ localDeleteIntent: deleteIntent("stale-etag", "h"),
2222
2258
  },
2223
2259
  },
2224
2260
  }));
@@ -2258,6 +2294,55 @@ describe("share", () => {
2258
2294
  expect(result.filesRefusedStale).toBe(1);
2259
2295
  expect(result.filesTombstoned).toBe(0);
2260
2296
  });
2297
+ it("currency-gated: preserves a file recreated after the delete plan", async () => {
2298
+ const companyRoot = path.join(tmpDir, "companies", "acme");
2299
+ fs.mkdirSync(companyRoot, { recursive: true });
2300
+ const recreatedPath = path.join(companyRoot, "racy.md");
2301
+ const journalPath = path.join(stateDir, "sync-journal.acme.json");
2302
+ fs.writeFileSync(journalPath, JSON.stringify({
2303
+ version: "2",
2304
+ lastSync: new Date().toISOString(),
2305
+ files: {
2306
+ "racy.md": {
2307
+ hash: "synced-hash",
2308
+ size: 10,
2309
+ syncedAt: new Date().toISOString(),
2310
+ direction: "up",
2311
+ remoteEtag: "racy-etag",
2312
+ kind: "file",
2313
+ localDeleteIntent: deleteIntent("racy-etag", "synced-hash"),
2314
+ },
2315
+ },
2316
+ pulls: [],
2317
+ }));
2318
+ // The currency HEAD establishes the remote revision, then a local writer
2319
+ // recreates the file before the planned DeleteObject can run.
2320
+ vi.mocked(headRemoteFile).mockImplementationOnce(async () => {
2321
+ fs.writeFileSync(recreatedPath, "recreated after planning");
2322
+ return { lastModified: new Date(), etag: '"racy-etag"', size: 10 };
2323
+ });
2324
+ const events = [];
2325
+ const result = await share({
2326
+ paths: [companyRoot],
2327
+ company: "acme",
2328
+ vaultConfig: mockConfig,
2329
+ hqRoot: tmpDir,
2330
+ skipUnchanged: true,
2331
+ propagateDeletes: true,
2332
+ propagateDeletePolicy: "currency-gated",
2333
+ onEvent: (e) => events.push(e),
2334
+ });
2335
+ expect(result.filesDeleted).toBe(0);
2336
+ expect(deleteRemoteFile).not.toHaveBeenCalled();
2337
+ expect(fs.readFileSync(recreatedPath, "utf-8")).toBe("recreated after planning");
2338
+ const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
2339
+ expect(journal.files["racy.md"]).toBeDefined();
2340
+ expect(events).toContainEqual(expect.objectContaining({
2341
+ type: "delete-refused-stale-etag",
2342
+ path: "racy.md",
2343
+ reason: "intent-changed",
2344
+ }));
2345
+ });
2261
2346
  it("currency-gated: refuses delete for a divergent-local entry (journal-honesty — never delete a remote the local never genuinely matched)", async () => {
2262
2347
  const companyRoot = path.join(tmpDir, "companies", "acme");
2263
2348
  fs.mkdirSync(companyRoot, { recursive: true });
@@ -2328,6 +2413,8 @@ describe("share", () => {
2328
2413
  hash: "h", size: 25, syncedAt: new Date().toISOString(),
2329
2414
  direction: "down",
2330
2415
  remoteEtag: "doesnt-matter",
2416
+ kind: "file",
2417
+ localDeleteIntent: deleteIntent("doesnt-matter", "h"),
2331
2418
  },
2332
2419
  },
2333
2420
  }));
@@ -2396,12 +2483,13 @@ describe("share", () => {
2396
2483
  });
2397
2484
  expect(result.filesDeleted).toBe(0);
2398
2485
  expect(deleteRemoteFile).not.toHaveBeenCalled();
2399
- // HEAD must NOT be called — we short-circuit on the missing journal etag.
2486
+ // HEAD must NOT be called — the explicit-intent gate rejects legacy
2487
+ // entries before any remote operation.
2400
2488
  expect(headRemoteFile).not.toHaveBeenCalled();
2401
2489
  const refused = events.find((e) => e.type === "delete-refused-stale-etag");
2402
2490
  expect(refused).toMatchObject({
2403
- journalEtag: "<legacy-no-etag>",
2404
- reason: "legacy-no-etag",
2491
+ journalEtag: "<missing-delete-intent>",
2492
+ reason: "missing-delete-intent",
2405
2493
  });
2406
2494
  expect(result.filesRefusedStale).toBe(1);
2407
2495
  });
@@ -2424,6 +2512,8 @@ describe("share", () => {
2424
2512
  hash: "h", size: 200, syncedAt: new Date().toISOString(),
2425
2513
  direction: "down",
2426
2514
  remoteEtag: "upstream-etag",
2515
+ kind: "file",
2516
+ localDeleteIntent: deleteIntent("upstream-etag", "h"),
2427
2517
  },
2428
2518
  },
2429
2519
  }));
@@ -2525,6 +2615,8 @@ describe("share", () => {
2525
2615
  hash: "h", size: 5, syncedAt: new Date().toISOString(),
2526
2616
  direction: "up",
2527
2617
  remoteEtag: "regular-etag",
2618
+ kind: "file",
2619
+ localDeleteIntent: deleteIntent("regular-etag", "h"),
2528
2620
  },
2529
2621
  },
2530
2622
  }));
@@ -2796,6 +2888,8 @@ describe("share", () => {
2796
2888
  hash: "h", size: 1, syncedAt: new Date().toISOString(),
2797
2889
  direction: "up",
2798
2890
  remoteEtag: "abc",
2891
+ kind: "file",
2892
+ localDeleteIntent: deleteIntent("abc", "h"),
2799
2893
  },
2800
2894
  },
2801
2895
  }));
@@ -3190,6 +3284,8 @@ describe("share", () => {
3190
3284
  syncedAt: new Date().toISOString(),
3191
3285
  direction,
3192
3286
  remoteEtag: `etag-${i}`,
3287
+ kind: "file",
3288
+ localDeleteIntent: deleteIntent(`etag-${i}`, "h"),
3193
3289
  };
3194
3290
  if (i >= missing) {
3195
3291
  fs.writeFileSync(path.join(companyRoot, key), "hi");
@@ -3394,11 +3490,15 @@ describe("share", () => {
3394
3490
  files[`d-${i.toString().padStart(4, "0")}.md`] = {
3395
3491
  hash: "h", size: 5, syncedAt: now,
3396
3492
  direction: "down", remoteEtag: `down-etag-${i}`,
3493
+ kind: "file",
3494
+ localDeleteIntent: deleteIntent(`down-etag-${i}`, "h"),
3397
3495
  };
3398
3496
  }
3399
3497
  files["only-up.md"] = {
3400
3498
  hash: "h", size: 5, syncedAt: now,
3401
3499
  direction: "up", remoteEtag: "up-etag",
3500
+ kind: "file",
3501
+ localDeleteIntent: deleteIntent("up-etag", "h"),
3402
3502
  };
3403
3503
  const journalPath = path.join(stateDir, "sync-journal.acme.json");
3404
3504
  fs.writeFileSync(journalPath, JSON.stringify({ version: "1", lastSync: now, files }));
@@ -3534,6 +3634,8 @@ describe("currency-gated: journal version 2 fixtures", () => {
3534
3634
  syncedAt: new Date().toISOString(),
3535
3635
  direction: "down",
3536
3636
  remoteEtag: "v2-etag",
3637
+ kind: "file",
3638
+ localDeleteIntent: deleteIntent("v2-etag", "h"),
3537
3639
  },
3538
3640
  },
3539
3641
  pulls: [],