@indigoai-us/hq-cloud 6.15.35 → 6.15.36

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.
@@ -52,7 +52,7 @@ vi.mock("readline", () => ({
52
52
  })),
53
53
  }));
54
54
  import * as readline from "readline";
55
- import { share, isForbiddenCompanyVaultKey, ServerOwnedPushPathsError, UnreachablePushPathsError, currentUploadBytes, _testing as shareTesting } from "./share.js";
55
+ import { share, isForbiddenCompanyVaultKey, 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";
@@ -956,8 +956,8 @@ describe("share", () => {
956
956
  // no-journal) still uploads. Differential proof: reverting the consult in
957
957
  // share.ts makes the first test fail (uploadFile IS called → resurrection).
958
958
  describe("push-side tombstone consult", () => {
959
- function seedJournal(key, hash, size, slug = "acme") {
960
- seedJournalAt(path.join(stateDir, `sync-journal.${slug}.json`), ({
959
+ function seedJournal(key, hash, size) {
960
+ seedJournalAt(path.join(stateDir, "sync-journal.acme.json"), ({
961
961
  version: "1",
962
962
  lastSync: new Date().toISOString(),
963
963
  files: {
@@ -1088,71 +1088,6 @@ describe("share", () => {
1088
1088
  expect(uploadFile).not.toHaveBeenCalled();
1089
1089
  expect(result.filesSuppressedByTombstone).toBe(1);
1090
1090
  });
1091
- it("personal push uses personal=1 to suppress a stale copy, then uploads a real edit", async () => {
1092
- const f = path.join(tmpDir, "docs", "shared.md");
1093
- fs.mkdirSync(path.dirname(f), { recursive: true });
1094
- fs.writeFileSync(f, "shared content");
1095
- seedJournal("docs/shared.md", hashFile(f), 14, "personal");
1096
- const deletedAt = new Date().toISOString();
1097
- const fetchMock = vi.fn().mockImplementation(async (url) => {
1098
- const u = String(url);
1099
- if (u.includes("/entity/check-slug/me")) {
1100
- return {
1101
- ok: true,
1102
- status: 200,
1103
- json: async () => ({ available: false, conflictingCompanyUid: mockEntity.uid }),
1104
- text: async () => "",
1105
- };
1106
- }
1107
- if (u.includes("/entity/by-slug/") || /\/entity\/cmp_/.test(u)) {
1108
- return { ok: true, status: 200, json: async () => ({ entity: mockEntity }), text: async () => "" };
1109
- }
1110
- if (u.includes("/sts/vend")) {
1111
- return { ok: true, status: 200, json: async () => mockVendResponse, text: async () => "" };
1112
- }
1113
- if (u.includes("/v1/files/tombstones")) {
1114
- return {
1115
- ok: true,
1116
- status: 200,
1117
- json: async () => ({
1118
- tombstones: [{ key: "docs/shared.md", deletedAt }],
1119
- }),
1120
- text: async () => "",
1121
- };
1122
- }
1123
- return { ok: false, status: 404, text: async () => "Not found" };
1124
- });
1125
- vi.stubGlobal("fetch", fetchMock);
1126
- const stale = await share({
1127
- paths: [path.dirname(f)],
1128
- company: "acme",
1129
- vaultConfig: mockConfig,
1130
- hqRoot: tmpDir,
1131
- personalMode: true,
1132
- journalSlug: "personal",
1133
- });
1134
- expect(stale.filesSuppressedByTombstone).toBe(1);
1135
- expect(uploadFile).not.toHaveBeenCalled();
1136
- const tombstoneUrls = fetchMock.mock.calls
1137
- .map((call) => String(call[0]))
1138
- .filter((url) => url.includes("/v1/files/tombstones"));
1139
- expect(tombstoneUrls).toEqual([
1140
- "https://vault-api.test/v1/files/tombstones?personal=1",
1141
- ]);
1142
- fs.writeFileSync(f, "edited after deletion");
1143
- vi.mocked(uploadFile).mockClear();
1144
- const recreated = await share({
1145
- paths: [path.dirname(f)],
1146
- company: "acme",
1147
- vaultConfig: mockConfig,
1148
- hqRoot: tmpDir,
1149
- personalMode: true,
1150
- journalSlug: "personal",
1151
- });
1152
- expect(recreated.filesSuppressedByTombstone).toBe(0);
1153
- expect(recreated.filesUploaded).toBe(1);
1154
- expect(uploadFile).toHaveBeenCalledWith(expect.anything(), f, "docs/shared.md", undefined, expect.anything());
1155
- });
1156
1091
  });
1157
1092
  it("populates conflictPaths and emits a conflict event when both local and remote drifted from journal", async () => {
1158
1093
  const companyRoot = path.join(tmpDir, "companies", "acme");
@@ -1222,110 +1157,6 @@ describe("share", () => {
1222
1157
  expect(fs.readFileSync(path.join(tmpDir, index.conflicts[0].conflictPath), "utf-8"))
1223
1158
  .toBe("local edit");
1224
1159
  });
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
- });
1329
1160
  it("cloud-authoritative: never pushes a stale local ontology/.last-run over the server value (one-sided-change clobber guard)", async () => {
1330
1161
  // Regression for the gardener watermark clobber. A second HQ root sharing this
1331
1162
  // machine's sync journal can leave the local mirror holding a STALE value A while
@@ -1359,14 +1190,11 @@ describe("share", () => {
1359
1190
  },
1360
1191
  },
1361
1192
  }));
1362
- const events = [];
1363
1193
  const result = await share({
1364
1194
  paths: [testFile],
1365
1195
  company: "acme",
1366
1196
  vaultConfig: mockConfig,
1367
1197
  hqRoot: tmpDir,
1368
- serverOwnedPathPolicy: "skip",
1369
- onEvent: (event) => events.push(event),
1370
1198
  });
1371
1199
  // Server-owned file: push is skipped BEFORE HEAD, the stale local value is NEVER
1372
1200
  // uploaded (the pull leg refreshes local from cloud instead), and it is not a conflict.
@@ -1375,12 +1203,6 @@ describe("share", () => {
1375
1203
  expect(result.filesUploaded).toBe(0);
1376
1204
  expect(result.filesSkipped).toBeGreaterThanOrEqual(1);
1377
1205
  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);
1384
1206
  });
1385
1207
  it("first-time-upload-with-cloud-collision: emits conflict + writes mirror under --on-conflict keep (Bug #7)", async () => {
1386
1208
  // Bug #7 (data-loss class) from the 5.33.0 deep test: when a file has