@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.
- package/dist/cli/share.d.ts.map +1 -1
- package/dist/cli/share.js +89 -14
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +105 -3
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/sync.d.ts +1 -1
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +128 -41
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +241 -25
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/journal.d.ts +7 -1
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +22 -1
- package/dist/journal.js.map +1 -1
- package/dist/personal-vault.d.ts +6 -0
- package/dist/personal-vault.d.ts.map +1 -1
- package/dist/personal-vault.js +12 -0
- package/dist/personal-vault.js.map +1 -1
- package/dist/s3.d.ts +3 -1
- package/dist/s3.d.ts.map +1 -1
- package/dist/s3.js +3 -1
- package/dist/s3.js.map +1 -1
- package/dist/types.d.ts +13 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/share.test.ts +129 -3
- package/src/cli/share.ts +114 -15
- package/src/cli/sync.test.ts +293 -24
- package/src/cli/sync.ts +173 -50
- package/src/journal.ts +28 -0
- package/src/personal-vault.ts +15 -0
- package/src/s3.ts +3 -0
- package/src/types.ts +13 -0
package/dist/cli/share.test.js
CHANGED
|
@@ -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 —
|
|
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: "<
|
|
2404
|
-
reason: "
|
|
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: [],
|