@indigoai-us/hq-cloud 6.15.34 → 6.15.35
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/bin/sync-runner-company.d.ts.map +1 -1
- package/dist/bin/sync-runner-company.js +12 -0
- package/dist/bin/sync-runner-company.js.map +1 -1
- package/dist/bin/sync-runner.d.ts +5 -0
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +25 -0
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/share.d.ts +18 -0
- package/dist/cli/share.d.ts.map +1 -1
- package/dist/cli/share.js +52 -2
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +114 -1
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/sync.d.ts +11 -0
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +6 -0
- package/dist/cli/sync.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/share.test.js
CHANGED
|
@@ -52,7 +52,7 @@ vi.mock("readline", () => ({
|
|
|
52
52
|
})),
|
|
53
53
|
}));
|
|
54
54
|
import * as readline from "readline";
|
|
55
|
-
import { share, isForbiddenCompanyVaultKey, UnreachablePushPathsError, currentUploadBytes, _testing as shareTesting } from "./share.js";
|
|
55
|
+
import { share, isForbiddenCompanyVaultKey, ServerOwnedPushPathsError, UnreachablePushPathsError, currentUploadBytes, _testing as shareTesting } from "./share.js";
|
|
56
56
|
import { deleteRemoteFile, downloadFile, headRemoteFile, primeObjectTransport, primeUploads, uploadFile, uploadSymlink, WindowsSymlinkPrivilegeError, } from "../s3.js";
|
|
57
57
|
import { VaultAuthError } from "../vault-client.js";
|
|
58
58
|
import { hashFile, readJournal, writeJournal } from "../journal.js";
|
|
@@ -1222,6 +1222,110 @@ describe("share", () => {
|
|
|
1222
1222
|
expect(fs.readFileSync(path.join(tmpDir, index.conflicts[0].conflictPath), "utf-8"))
|
|
1223
1223
|
.toBe("local edit");
|
|
1224
1224
|
});
|
|
1225
|
+
it.each([
|
|
1226
|
+
["local-only", false],
|
|
1227
|
+
["divergent-remote", true],
|
|
1228
|
+
])("direct source push (%s): emits a machine-readable refusal and fails when it is the only requested path", async (_state, divergentRemote) => {
|
|
1229
|
+
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
1230
|
+
const testFile = path.join(companyRoot, "sources", "meetings", "m-1.md");
|
|
1231
|
+
fs.mkdirSync(path.dirname(testFile), { recursive: true });
|
|
1232
|
+
fs.writeFileSync(testFile, "local meeting source");
|
|
1233
|
+
if (divergentRemote) {
|
|
1234
|
+
seedJournalAt(path.join(stateDir, "sync-journal.acme.json"), {
|
|
1235
|
+
version: "1",
|
|
1236
|
+
lastSync: new Date().toISOString(),
|
|
1237
|
+
files: {
|
|
1238
|
+
"sources/meetings/m-1.md": {
|
|
1239
|
+
hash: "remote-body-hash",
|
|
1240
|
+
size: 21,
|
|
1241
|
+
syncedAt: new Date().toISOString(),
|
|
1242
|
+
direction: "down",
|
|
1243
|
+
remoteEtag: "remote-etag",
|
|
1244
|
+
localDiverges: true,
|
|
1245
|
+
},
|
|
1246
|
+
},
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
const events = [];
|
|
1250
|
+
await expect(share({
|
|
1251
|
+
paths: [testFile],
|
|
1252
|
+
company: "acme",
|
|
1253
|
+
vaultConfig: mockConfig,
|
|
1254
|
+
hqRoot: tmpDir,
|
|
1255
|
+
skipUnchanged: divergentRemote,
|
|
1256
|
+
onEvent: (event) => events.push(event),
|
|
1257
|
+
})).rejects.toMatchObject({
|
|
1258
|
+
name: "ServerOwnedPushPathsError",
|
|
1259
|
+
paths: ["sources/meetings/m-1.md"],
|
|
1260
|
+
});
|
|
1261
|
+
expect(events).toContainEqual({
|
|
1262
|
+
type: "push-refused-server-owned",
|
|
1263
|
+
path: "sources/meetings/m-1.md",
|
|
1264
|
+
nextStep: "use-source-ingestion",
|
|
1265
|
+
});
|
|
1266
|
+
expect(events.some((event) => event.type === "reconciled")).toBe(false);
|
|
1267
|
+
expect(uploadFile).not.toHaveBeenCalled();
|
|
1268
|
+
expect(headRemoteFile).not.toHaveBeenCalled();
|
|
1269
|
+
});
|
|
1270
|
+
it.each([
|
|
1271
|
+
["local-only", false],
|
|
1272
|
+
["divergent-remote", true],
|
|
1273
|
+
])("watch-driven source push (%s): explicit skip mode refuses without failing the pass", async (_state, divergentRemote) => {
|
|
1274
|
+
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
1275
|
+
const testFile = path.join(companyRoot, "sources", "meetings", "m-2.md");
|
|
1276
|
+
fs.mkdirSync(path.dirname(testFile), { recursive: true });
|
|
1277
|
+
fs.writeFileSync(testFile, "local meeting source");
|
|
1278
|
+
if (divergentRemote) {
|
|
1279
|
+
seedJournalAt(path.join(stateDir, "sync-journal.acme.json"), {
|
|
1280
|
+
version: "1",
|
|
1281
|
+
lastSync: new Date().toISOString(),
|
|
1282
|
+
files: {
|
|
1283
|
+
"sources/meetings/m-2.md": {
|
|
1284
|
+
hash: "remote-body-hash",
|
|
1285
|
+
size: 21,
|
|
1286
|
+
syncedAt: new Date().toISOString(),
|
|
1287
|
+
direction: "down",
|
|
1288
|
+
remoteEtag: "remote-etag",
|
|
1289
|
+
localDiverges: true,
|
|
1290
|
+
},
|
|
1291
|
+
},
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
const events = [];
|
|
1295
|
+
const result = await share({
|
|
1296
|
+
paths: [testFile],
|
|
1297
|
+
company: "acme",
|
|
1298
|
+
vaultConfig: mockConfig,
|
|
1299
|
+
hqRoot: tmpDir,
|
|
1300
|
+
skipUnchanged: true,
|
|
1301
|
+
serverOwnedPathPolicy: "skip",
|
|
1302
|
+
onEvent: (event) => events.push(event),
|
|
1303
|
+
});
|
|
1304
|
+
expect(result).toMatchObject({ filesUploaded: 0, filesSkipped: 1 });
|
|
1305
|
+
expect(events).toContainEqual({
|
|
1306
|
+
type: "push-refused-server-owned",
|
|
1307
|
+
path: "sources/meetings/m-2.md",
|
|
1308
|
+
nextStep: "use-source-ingestion",
|
|
1309
|
+
});
|
|
1310
|
+
expect(events.some((event) => event.type === "reconciled")).toBe(false);
|
|
1311
|
+
expect(uploadFile).not.toHaveBeenCalled();
|
|
1312
|
+
expect(headRemoteFile).not.toHaveBeenCalled();
|
|
1313
|
+
});
|
|
1314
|
+
it("direct source push explains the server-owned refusal and supported next step", async () => {
|
|
1315
|
+
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
1316
|
+
const testFile = path.join(companyRoot, "sources", "meetings", "m-3.md");
|
|
1317
|
+
fs.mkdirSync(path.dirname(testFile), { recursive: true });
|
|
1318
|
+
fs.writeFileSync(testFile, "local meeting source");
|
|
1319
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
1320
|
+
await expect(share({
|
|
1321
|
+
paths: [testFile],
|
|
1322
|
+
company: "acme",
|
|
1323
|
+
vaultConfig: mockConfig,
|
|
1324
|
+
hqRoot: tmpDir,
|
|
1325
|
+
})).rejects.toThrow(ServerOwnedPushPathsError);
|
|
1326
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining("is server-owned and was not uploaded"));
|
|
1327
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining("supported integration or ingestion flow"));
|
|
1328
|
+
});
|
|
1225
1329
|
it("cloud-authoritative: never pushes a stale local ontology/.last-run over the server value (one-sided-change clobber guard)", async () => {
|
|
1226
1330
|
// Regression for the gardener watermark clobber. A second HQ root sharing this
|
|
1227
1331
|
// machine's sync journal can leave the local mirror holding a STALE value A while
|
|
@@ -1255,11 +1359,14 @@ describe("share", () => {
|
|
|
1255
1359
|
},
|
|
1256
1360
|
},
|
|
1257
1361
|
}));
|
|
1362
|
+
const events = [];
|
|
1258
1363
|
const result = await share({
|
|
1259
1364
|
paths: [testFile],
|
|
1260
1365
|
company: "acme",
|
|
1261
1366
|
vaultConfig: mockConfig,
|
|
1262
1367
|
hqRoot: tmpDir,
|
|
1368
|
+
serverOwnedPathPolicy: "skip",
|
|
1369
|
+
onEvent: (event) => events.push(event),
|
|
1263
1370
|
});
|
|
1264
1371
|
// Server-owned file: push is skipped BEFORE HEAD, the stale local value is NEVER
|
|
1265
1372
|
// uploaded (the pull leg refreshes local from cloud instead), and it is not a conflict.
|
|
@@ -1268,6 +1375,12 @@ describe("share", () => {
|
|
|
1268
1375
|
expect(result.filesUploaded).toBe(0);
|
|
1269
1376
|
expect(result.filesSkipped).toBeGreaterThanOrEqual(1);
|
|
1270
1377
|
expect(result.conflictPaths).toEqual([]);
|
|
1378
|
+
expect(events).toContainEqual({
|
|
1379
|
+
type: "push-refused-server-owned",
|
|
1380
|
+
path: "ontology/.last-run",
|
|
1381
|
+
nextStep: "change-upstream-and-pull",
|
|
1382
|
+
});
|
|
1383
|
+
expect(events.some((event) => event.type === "reconciled")).toBe(false);
|
|
1271
1384
|
});
|
|
1272
1385
|
it("first-time-upload-with-cloud-collision: emits conflict + writes mirror under --on-conflict keep (Bug #7)", async () => {
|
|
1273
1386
|
// Bug #7 (data-loss class) from the 5.33.0 deep test: when a file has
|