@indigoai-us/hq-cloud 6.15.38 → 6.15.40

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.
@@ -1303,9 +1303,9 @@ describe("share", () => {
1303
1303
  });
1304
1304
  expect(result).toMatchObject({ filesUploaded: 0, filesSkipped: 1 });
1305
1305
  expect(events).toContainEqual({
1306
- type: "push-refused-server-owned",
1307
- path: "sources/meetings/m-2.md",
1308
- nextStep: "use-source-ingestion",
1306
+ type: "push-refused-server-owned-summary",
1307
+ count: 1,
1308
+ samplePaths: ["sources/meetings/m-2.md"],
1309
1309
  });
1310
1310
  expect(events.some((event) => event.type === "reconciled")).toBe(false);
1311
1311
  expect(uploadFile).not.toHaveBeenCalled();
@@ -1376,12 +1376,42 @@ describe("share", () => {
1376
1376
  expect(result.filesSkipped).toBeGreaterThanOrEqual(1);
1377
1377
  expect(result.conflictPaths).toEqual([]);
1378
1378
  expect(events).toContainEqual({
1379
- type: "push-refused-server-owned",
1380
- path: "ontology/.last-run",
1381
- nextStep: "change-upstream-and-pull",
1379
+ type: "push-refused-server-owned-summary",
1380
+ count: 1,
1381
+ samplePaths: ["ontology/.last-run"],
1382
1382
  });
1383
1383
  expect(events.some((event) => event.type === "reconciled")).toBe(false);
1384
1384
  });
1385
+ it("watch push collapses 32k server-owned refusals to one exact-count event while board.json still uploads", async () => {
1386
+ const companyRoot = path.join(tmpDir, "companies", "acme");
1387
+ const sourceRoot = path.join(companyRoot, "sources", "meetings");
1388
+ const serverOwnedPathCount = 32_540;
1389
+ fs.mkdirSync(sourceRoot, { recursive: true });
1390
+ for (let index = 0; index < serverOwnedPathCount; index++) {
1391
+ fs.writeFileSync(path.join(sourceRoot, `meeting-${index}.md`), "source");
1392
+ }
1393
+ fs.writeFileSync(path.join(companyRoot, "board.json"), "{\"cards\":[]}");
1394
+ const events = [];
1395
+ const result = await share({
1396
+ paths: [companyRoot],
1397
+ company: "acme",
1398
+ vaultConfig: mockConfig,
1399
+ hqRoot: tmpDir,
1400
+ serverOwnedPathPolicy: "skip",
1401
+ onEvent: (event) => events.push(event),
1402
+ });
1403
+ const summaries = events.filter((event) => event.type === "push-refused-server-owned-summary");
1404
+ expect(summaries).toHaveLength(1);
1405
+ expect(summaries[0]).toMatchObject({ count: serverOwnedPathCount });
1406
+ expect(summaries[0].samplePaths).toHaveLength(10);
1407
+ expect(summaries[0].samplePaths.every((sample) => sample.startsWith("sources/meetings/"))).toBe(true);
1408
+ expect(events.filter((event) => event.type === "push-refused-server-owned")).toHaveLength(0);
1409
+ expect(result).toMatchObject({
1410
+ filesUploaded: 1,
1411
+ filesSkipped: serverOwnedPathCount,
1412
+ });
1413
+ expect(vi.mocked(uploadFile).mock.calls.map((call) => call[2])).toEqual(["board.json"]);
1414
+ });
1385
1415
  it("first-time-upload-with-cloud-collision: emits conflict + writes mirror under --on-conflict keep (Bug #7)", async () => {
1386
1416
  // Bug #7 (data-loss class) from the 5.33.0 deep test: when a file has
1387
1417
  // NO prior journal entry (fresh upload from this machine) but the
@@ -2950,6 +2980,7 @@ describe("share", () => {
2950
2980
  e.type === "scope-materialization-gap" ||
2951
2981
  e.type === "ignore-excluded" ||
2952
2982
  e.type === "not-shipped" ||
2983
+ e.type === "push-refused-server-owned-summary" ||
2953
2984
  e.type === "delete-refused-bulk-asymmetry")
2954
2985
  return;
2955
2986
  events.push({
@@ -4930,6 +4961,94 @@ describe("share", () => {
4930
4961
  delete process.env.HQ_STATE_DIR;
4931
4962
  }
4932
4963
  });
4964
+ it("journal prune is local-only while an in-scope missing entry stays on the normal delete path", async () => {
4965
+ const journalSlug = "personal-prune-safety";
4966
+ const journalPath = path.join(stateDir, `sync-journal.${journalSlug}.json`);
4967
+ seedJournalAt(journalPath, {
4968
+ version: "2",
4969
+ lastSync: new Date().toISOString(),
4970
+ pulls: [],
4971
+ files: {
4972
+ // Absent and currently ignored: this is positively out of scope and
4973
+ // may be forgotten without expressing a remote deletion intent.
4974
+ ".claude/worktrees/gone/stranded.ts": {
4975
+ hash: "stranded", size: 1, syncedAt: new Date().toISOString(), direction: "up",
4976
+ },
4977
+ // Absent but still in scope: this remains a genuine local-delete
4978
+ // candidate and must be handled by the ordinary delete planner.
4979
+ "personal/pending-delete.md": {
4980
+ hash: "pending",
4981
+ size: 1,
4982
+ syncedAt: new Date().toISOString(),
4983
+ direction: "up",
4984
+ remoteEtag: "pending-etag",
4985
+ kind: "file",
4986
+ localDeleteIntent: deleteIntent("pending-etag", "pending"),
4987
+ },
4988
+ },
4989
+ });
4990
+ const personalCtx = makeEntityContext({
4991
+ uid: "prs_01HASSAANTESTUSER",
4992
+ slug: journalSlug,
4993
+ bucketName: "hq-vault-prs-01HASSAANTESTUSER",
4994
+ });
4995
+ const result = await share({
4996
+ paths: [],
4997
+ deleteScopeRoots: ["personal/pending-delete.md"],
4998
+ entityContext: personalCtx,
4999
+ hqRoot: tmpDir,
5000
+ personalMode: true,
5001
+ journalSlug,
5002
+ skipUnchanged: true,
5003
+ propagateDeletes: true,
5004
+ propagateDeletePolicy: "owned-only",
5005
+ });
5006
+ // The in-scope deletion remains eligible and produces the existing normal
5007
+ // remote delete behavior; the prune itself never contributes a mutation.
5008
+ expect(result.filesDeleted).toBe(1);
5009
+ expect(deleteRemoteFile).toHaveBeenCalledTimes(1);
5010
+ expect(deleteRemoteFile).toHaveBeenCalledWith(expect.anything(), "personal/pending-delete.md");
5011
+ expect(uploadFile).not.toHaveBeenCalled();
5012
+ expect(uploadSymlink).not.toHaveBeenCalled();
5013
+ expect(journalAt(journalPath).files[".claude/worktrees/gone/stranded.ts"]).toBeUndefined();
5014
+ expect(journalAt(journalPath).files["personal/pending-delete.md"]).toBeUndefined();
5015
+ });
5016
+ it("journal prune of an ignored missing row issues zero push-side mutations", async () => {
5017
+ const journalSlug = "personal-prune-local-only";
5018
+ const journalPath = path.join(stateDir, `sync-journal.${journalSlug}.json`);
5019
+ seedJournalAt(journalPath, {
5020
+ version: "2",
5021
+ lastSync: new Date().toISOString(),
5022
+ pulls: [],
5023
+ files: {
5024
+ ".claude/worktrees/gone/stranded.ts": {
5025
+ hash: "stranded", size: 1, syncedAt: new Date().toISOString(), direction: "up",
5026
+ },
5027
+ },
5028
+ });
5029
+ const personalCtx = makeEntityContext({
5030
+ uid: "prs_01HASSAANTESTUSER",
5031
+ slug: journalSlug,
5032
+ bucketName: "hq-vault-prs-01HASSAANTESTUSER",
5033
+ });
5034
+ const result = await share({
5035
+ paths: [],
5036
+ entityContext: personalCtx,
5037
+ hqRoot: tmpDir,
5038
+ personalMode: true,
5039
+ journalSlug,
5040
+ skipUnchanged: true,
5041
+ propagateDeletes: true,
5042
+ propagateDeletePolicy: "owned-only",
5043
+ });
5044
+ expect(result.filesUploaded).toBe(0);
5045
+ expect(result.filesDeleted).toBe(0);
5046
+ expect(result.filesTombstoned).toBe(0);
5047
+ expect(uploadFile).not.toHaveBeenCalled();
5048
+ expect(uploadSymlink).not.toHaveBeenCalled();
5049
+ expect(deleteRemoteFile).not.toHaveBeenCalled();
5050
+ expect(journalAt(journalPath).files[".claude/worktrees/gone/stranded.ts"]).toBeUndefined();
5051
+ });
4933
5052
  // ── personalMode ───────────────────────────────────────────────────────────
4934
5053
  //
4935
5054
  // The personal vault (slug "personal" in the runner's fanout plan) shares
@@ -7657,5 +7776,23 @@ describe("defaultConsoleLogger — delete refusal wording per reason", () => {
7657
7776
  expect(out).toContain("aaa111");
7658
7777
  expect(out).toContain("bbb222");
7659
7778
  });
7779
+ it("logs personal journal prune reason classes", () => {
7780
+ const lines = [];
7781
+ const spy = vi.spyOn(console, "log").mockImplementation((msg) => {
7782
+ lines.push(String(msg));
7783
+ });
7784
+ try {
7785
+ shareTesting.defaultConsoleLogger({
7786
+ type: "personal-vault-journal-pruned",
7787
+ count: 3,
7788
+ samplePaths: ["workspace/scratch/a.md"],
7789
+ byReason: { "ignore-filtered": 2, "excluded-top-level": 1 },
7790
+ });
7791
+ }
7792
+ finally {
7793
+ spy.mockRestore();
7794
+ }
7795
+ expect(lines.join("\n")).toContain("ignore-filtered=2, excluded-top-level=1");
7796
+ });
7660
7797
  });
7661
7798
  //# sourceMappingURL=share.test.js.map