@indigoai-us/hq-cloud 6.15.18 → 6.15.19
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 +5 -39
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +45 -66
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/sync.js +14 -28
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +60 -4
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/telemetry.d.ts +23 -5
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +264 -75
- package/dist/telemetry.js.map +1 -1
- package/dist/telemetry.test.js +391 -0
- package/dist/telemetry.test.js.map +1 -1
- package/dist/types.d.ts +4 -13
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/share.test.js
CHANGED
|
@@ -5598,45 +5598,41 @@ describe("currency-gated: journal version 2 fixtures", () => {
|
|
|
5598
5598
|
expect(result.filesDeleted).toBe(1);
|
|
5599
5599
|
expect(deleteRemoteFile).toHaveBeenCalledWith(expect.anything(), "core/policies/old.md");
|
|
5600
5600
|
});
|
|
5601
|
-
// ──
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
// file AND (lstat.size, lstat.mtimeMs) match the recorded values, the
|
|
5605
|
-
// push planner classifies the file `unchanged` WITHOUT reading its
|
|
5606
|
-
// bytes. Same trade-off rsync/gitignore use; turns no-op syncs from
|
|
5607
|
-
// O(file bytes) into O(file count).
|
|
5608
|
-
//
|
|
5609
|
-
// We prove "the SHA256 path didn't run" by seeding the journal with an
|
|
5610
|
-
// intentionally WRONG hash but matching mtimeMs+size. If the fast-path
|
|
5611
|
-
// fires → file is classified `unchanged` purely on lstat (no hash
|
|
5612
|
-
// compare). If the fast-path is skipped → hashFile runs, hashes the
|
|
5613
|
-
// real file, fails the !== journal-hash check, and uploads. The
|
|
5614
|
-
// upload-vs-skip outcome is the diagnostic signal.
|
|
5615
|
-
describe("lstat fast-path (5.36.0)", () => {
|
|
5616
|
-
it("skips upload when mtimeMs + size match journal (even with wrong stored hash)", async () => {
|
|
5601
|
+
// ── Regular-file content identity ─────────────────────────────────────────
|
|
5602
|
+
describe("regular-file content identity", () => {
|
|
5603
|
+
it("uploads a same-size edit whose mtime matches the journal", async () => {
|
|
5617
5604
|
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
5618
5605
|
fs.mkdirSync(companyRoot, { recursive: true });
|
|
5619
5606
|
const testFile = path.join(companyRoot, "stable.md");
|
|
5620
5607
|
fs.writeFileSync(testFile, "stable bytes");
|
|
5621
|
-
const
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5608
|
+
const preservedMtime = new Date("2026-01-02T03:04:05.000Z");
|
|
5609
|
+
fs.utimesSync(testFile, preservedMtime, preservedMtime);
|
|
5610
|
+
const baselineLstat = fs.lstatSync(testFile);
|
|
5611
|
+
const { hashFile } = await import("../journal.js");
|
|
5612
|
+
const baselineHash = hashFile(testFile);
|
|
5625
5613
|
const journalPath = path.join(stateDir, "sync-journal.acme.json");
|
|
5626
5614
|
fs.writeFileSync(journalPath, JSON.stringify({
|
|
5627
5615
|
version: "2",
|
|
5628
5616
|
lastSync: new Date().toISOString(),
|
|
5629
5617
|
files: {
|
|
5630
5618
|
"stable.md": {
|
|
5631
|
-
hash:
|
|
5632
|
-
size:
|
|
5633
|
-
mtimeMs:
|
|
5619
|
+
hash: baselineHash,
|
|
5620
|
+
size: baselineLstat.size,
|
|
5621
|
+
mtimeMs: baselineLstat.mtimeMs,
|
|
5634
5622
|
syncedAt: new Date().toISOString(),
|
|
5635
5623
|
direction: "up",
|
|
5636
5624
|
},
|
|
5637
5625
|
},
|
|
5638
5626
|
pulls: [],
|
|
5639
5627
|
}));
|
|
5628
|
+
// Rewrite the bytes without changing either cheap stat signal. A planner
|
|
5629
|
+
// that treats (size, mtimeMs) as content identity will skip this forever.
|
|
5630
|
+
fs.writeFileSync(testFile, "edited bytes");
|
|
5631
|
+
fs.utimesSync(testFile, preservedMtime, preservedMtime);
|
|
5632
|
+
const editedLstat = fs.lstatSync(testFile);
|
|
5633
|
+
expect(editedLstat.size).toBe(baselineLstat.size);
|
|
5634
|
+
expect(editedLstat.mtimeMs).toBe(baselineLstat.mtimeMs);
|
|
5635
|
+
expect(hashFile(testFile)).not.toBe(baselineHash);
|
|
5640
5636
|
const result = await share({
|
|
5641
5637
|
paths: [testFile],
|
|
5642
5638
|
company: "acme",
|
|
@@ -5644,21 +5640,17 @@ describe("currency-gated: journal version 2 fixtures", () => {
|
|
|
5644
5640
|
hqRoot: tmpDir,
|
|
5645
5641
|
skipUnchanged: true,
|
|
5646
5642
|
});
|
|
5647
|
-
|
|
5648
|
-
expect(result.
|
|
5649
|
-
expect(
|
|
5650
|
-
expect(uploadFile).not.toHaveBeenCalled();
|
|
5643
|
+
expect(result.filesUploaded).toBe(1);
|
|
5644
|
+
expect(result.filesSkipped).toBe(0);
|
|
5645
|
+
expect(uploadFile).toHaveBeenCalled();
|
|
5651
5646
|
});
|
|
5652
|
-
it("uploads when
|
|
5647
|
+
it("uploads when the content hash differs after an mtime change", async () => {
|
|
5653
5648
|
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
5654
5649
|
fs.mkdirSync(companyRoot, { recursive: true });
|
|
5655
5650
|
const testFile = path.join(companyRoot, "touched.md");
|
|
5656
5651
|
fs.writeFileSync(testFile, "original bytes!");
|
|
5657
5652
|
const originalLstat = fs.lstatSync(testFile);
|
|
5658
|
-
//
|
|
5659
|
-
// mtime stayed the same. We're about to bump mtime, so fast-path
|
|
5660
|
-
// MUST miss, hashFile MUST run, hash MUST mismatch the wrong stored
|
|
5661
|
-
// value, and the file MUST upload.
|
|
5653
|
+
// The recorded hash intentionally differs from the current bytes.
|
|
5662
5654
|
const journalPath = path.join(stateDir, "sync-journal.acme.json");
|
|
5663
5655
|
fs.writeFileSync(journalPath, JSON.stringify({
|
|
5664
5656
|
version: "2",
|
|
@@ -5674,7 +5666,7 @@ describe("currency-gated: journal version 2 fixtures", () => {
|
|
|
5674
5666
|
},
|
|
5675
5667
|
pulls: [],
|
|
5676
5668
|
}));
|
|
5677
|
-
// Bump mtime forward
|
|
5669
|
+
// Bump mtime forward; content hashing remains the source of truth.
|
|
5678
5670
|
const futureTime = new Date(Date.now() + 60_000);
|
|
5679
5671
|
fs.utimesSync(testFile, futureTime, futureTime);
|
|
5680
5672
|
const result = await share({
|
|
@@ -5684,8 +5676,6 @@ describe("currency-gated: journal version 2 fixtures", () => {
|
|
|
5684
5676
|
hqRoot: tmpDir,
|
|
5685
5677
|
skipUnchanged: true,
|
|
5686
5678
|
});
|
|
5687
|
-
// Fast-path missed → second-stage hash compare ran → wrong stored hash
|
|
5688
|
-
// forced the upload.
|
|
5689
5679
|
expect(result.filesUploaded).toBe(1);
|
|
5690
5680
|
expect(uploadFile).toHaveBeenCalled();
|
|
5691
5681
|
});
|
|
@@ -5693,13 +5683,11 @@ describe("currency-gated: journal version 2 fixtures", () => {
|
|
|
5693
5683
|
// create a conflict/drift file." This is the push-side complement of the
|
|
5694
5684
|
// pull-side reconcile test (sync.test.ts "byte-identical ... no litter").
|
|
5695
5685
|
//
|
|
5696
|
-
// A pure `touch` (bytes identical, mtime bumped) MUST
|
|
5697
|
-
//
|
|
5698
|
-
// second-stage hash compare (real hash === CORRECT stored hash), and
|
|
5686
|
+
// A pure `touch` (bytes identical, mtime bumped) MUST hash the bytes,
|
|
5687
|
+
// compare the real hash with the correct stored hash, and
|
|
5699
5688
|
// classify the file `unchanged`. No upload, no conflict event, and no
|
|
5700
5689
|
// `.conflict-*`/`.drift-*` mirror on disk — mtime alone never manufactures
|
|
5701
|
-
// divergence.
|
|
5702
|
-
// this proves a CORRECT stored hash then reconciles instead of uploading.)
|
|
5690
|
+
// divergence.
|
|
5703
5691
|
it("mtime bumped but bytes identical → re-hash reconciles → no upload, no conflict, no mirror", async () => {
|
|
5704
5692
|
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
5705
5693
|
fs.mkdirSync(companyRoot, { recursive: true });
|
|
@@ -5707,8 +5695,7 @@ describe("currency-gated: journal version 2 fixtures", () => {
|
|
|
5707
5695
|
fs.writeFileSync(testFile, "identical bytes, only mtime moves");
|
|
5708
5696
|
const originalLstat = fs.lstatSync(testFile);
|
|
5709
5697
|
// CORRECT stored hash (unlike 3722's sentinel) + matching size. Once the
|
|
5710
|
-
//
|
|
5711
|
-
// and the second-stage compare MUST classify the file unchanged.
|
|
5698
|
+
// The real hash MUST equal this value and classify the file unchanged.
|
|
5712
5699
|
const { hashFile } = await import("../journal.js");
|
|
5713
5700
|
const realHash = hashFile(testFile);
|
|
5714
5701
|
const journalPath = path.join(stateDir, "sync-journal.acme.json");
|
|
@@ -5726,7 +5713,7 @@ describe("currency-gated: journal version 2 fixtures", () => {
|
|
|
5726
5713
|
},
|
|
5727
5714
|
pulls: [],
|
|
5728
5715
|
}));
|
|
5729
|
-
// Bump mtime forward only — bytes untouched.
|
|
5716
|
+
// Bump mtime forward only — bytes untouched.
|
|
5730
5717
|
const futureTime = new Date(Date.now() + 60_000);
|
|
5731
5718
|
fs.utimesSync(testFile, futureTime, futureTime);
|
|
5732
5719
|
const events = [];
|
|
@@ -5738,7 +5725,7 @@ describe("currency-gated: journal version 2 fixtures", () => {
|
|
|
5738
5725
|
skipUnchanged: true,
|
|
5739
5726
|
onEvent: (e) => events.push(e),
|
|
5740
5727
|
});
|
|
5741
|
-
//
|
|
5728
|
+
// Hash matched → reconciled.
|
|
5742
5729
|
expect(result.filesUploaded).toBe(0);
|
|
5743
5730
|
expect(result.filesSkipped).toBe(1);
|
|
5744
5731
|
expect(uploadFile).not.toHaveBeenCalled();
|
|
@@ -5751,12 +5738,9 @@ describe("currency-gated: journal version 2 fixtures", () => {
|
|
|
5751
5738
|
.filter((f) => f.includes(".conflict-") || f.includes(".drift-"));
|
|
5752
5739
|
expect(litter).toEqual([]);
|
|
5753
5740
|
});
|
|
5754
|
-
//
|
|
5755
|
-
//
|
|
5756
|
-
|
|
5757
|
-
// NEXT sync short-circuits on lstat alone. Pure performance — still no
|
|
5758
|
-
// upload, no conflict, no mirror.
|
|
5759
|
-
it("re-stamps journal mtimeMs after a no-op skip so the next sync hits the fast-path", async () => {
|
|
5741
|
+
// Keep stat metadata current after a touched-but-identical no-op. Content
|
|
5742
|
+
// identity still comes exclusively from the hash.
|
|
5743
|
+
it("re-stamps journal mtimeMs after a no-op hash match", async () => {
|
|
5760
5744
|
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
5761
5745
|
fs.mkdirSync(companyRoot, { recursive: true });
|
|
5762
5746
|
const testFile = path.join(companyRoot, "touched-then-fast.md");
|
|
@@ -5779,7 +5763,7 @@ describe("currency-gated: journal version 2 fixtures", () => {
|
|
|
5779
5763
|
},
|
|
5780
5764
|
pulls: [],
|
|
5781
5765
|
}));
|
|
5782
|
-
// Bump mtime —
|
|
5766
|
+
// Bump mtime — hash still matches, so this is a no-op skip.
|
|
5783
5767
|
const futureTime = new Date(Date.now() + 120_000);
|
|
5784
5768
|
fs.utimesSync(testFile, futureTime, futureTime);
|
|
5785
5769
|
const bumpedLstat = fs.lstatSync(testFile);
|
|
@@ -5795,8 +5779,8 @@ describe("currency-gated: journal version 2 fixtures", () => {
|
|
|
5795
5779
|
expect(result.filesUploaded).toBe(0);
|
|
5796
5780
|
expect(result.filesSkipped).toBe(1);
|
|
5797
5781
|
expect(uploadFile).not.toHaveBeenCalled();
|
|
5798
|
-
// Journal mtimeMs is now the
|
|
5799
|
-
//
|
|
5782
|
+
// Journal mtimeMs is now the bumped value, while content hashing remains
|
|
5783
|
+
// authoritative on the next sync.
|
|
5800
5784
|
const journalAfter = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
|
|
5801
5785
|
expect(journalAfter.files["touched-then-fast.md"].mtimeMs).toBe(bumpedLstat.mtimeMs);
|
|
5802
5786
|
expect(journalAfter.files["touched-then-fast.md"].size).toBe(bumpedLstat.size);
|
|
@@ -5822,11 +5806,9 @@ describe("currency-gated: journal version 2 fixtures", () => {
|
|
|
5822
5806
|
expect(journal.files["fresh.md"].mtimeMs).toBe(lstat.mtimeMs);
|
|
5823
5807
|
expect(journal.files["fresh.md"].size).toBe(lstat.size);
|
|
5824
5808
|
});
|
|
5825
|
-
it("
|
|
5826
|
-
// Back-compat: pre-5.36 journals don't carry mtimeMs
|
|
5827
|
-
//
|
|
5828
|
-
// still work; the post-upload stamp adds the field so subsequent
|
|
5829
|
-
// syncs use the fast-path.
|
|
5809
|
+
it("hashes content when the journal has no mtimeMs (pre-5.36 entry)", async () => {
|
|
5810
|
+
// Back-compat: pre-5.36 journals don't carry mtimeMs, but the content
|
|
5811
|
+
// hash still decides whether the file is unchanged.
|
|
5830
5812
|
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
5831
5813
|
fs.mkdirSync(companyRoot, { recursive: true });
|
|
5832
5814
|
const testFile = path.join(companyRoot, "legacy.md");
|
|
@@ -5834,8 +5816,7 @@ describe("currency-gated: journal version 2 fixtures", () => {
|
|
|
5834
5816
|
const { hashFile: realHashFile } = await import("../journal.js");
|
|
5835
5817
|
const realHash = realHashFile(testFile);
|
|
5836
5818
|
const realSize = fs.statSync(testFile).size;
|
|
5837
|
-
// Real hash but NO mtimeMs
|
|
5838
|
-
// second-stage compare skips upload.
|
|
5819
|
+
// Real hash but NO mtimeMs. Hash matches → skip upload.
|
|
5839
5820
|
const journalPath = path.join(stateDir, "sync-journal.acme.json");
|
|
5840
5821
|
fs.writeFileSync(journalPath, JSON.stringify({
|
|
5841
5822
|
version: "1",
|
|
@@ -5857,13 +5838,11 @@ describe("currency-gated: journal version 2 fixtures", () => {
|
|
|
5857
5838
|
hqRoot: tmpDir,
|
|
5858
5839
|
skipUnchanged: true,
|
|
5859
5840
|
});
|
|
5860
|
-
//
|
|
5861
|
-
// matched → upload skipped at second stage. Same outcome as 5.35.0.
|
|
5841
|
+
// Real hash matched → upload skipped.
|
|
5862
5842
|
expect(result.filesSkipped).toBe(1);
|
|
5863
5843
|
expect(uploadFile).not.toHaveBeenCalled();
|
|
5864
|
-
//
|
|
5865
|
-
//
|
|
5866
|
-
// Verified separately by the "stamps mtimeMs" test above.
|
|
5844
|
+
// A follow-up upload still stamps mtimeMs for journal compatibility,
|
|
5845
|
+
// verified separately by the "stamps mtimeMs" test above.
|
|
5867
5846
|
});
|
|
5868
5847
|
});
|
|
5869
5848
|
// ── Fix #2 (5.36.0): bounded-parallel transfer pool ──────────────────────
|
|
@@ -6395,7 +6374,7 @@ describe("currency-gated: journal version 2 fixtures", () => {
|
|
|
6395
6374
|
// entries for the 9 successful uploads. Pre-fix the journal was
|
|
6396
6375
|
// never written (the throw escaped before writeJournal()), so
|
|
6397
6376
|
// the next sync re-uploaded all 9 successful files unnecessarily
|
|
6398
|
-
// AND lost
|
|
6377
|
+
// AND lost their etag/content-hash/stat journal metadata.
|
|
6399
6378
|
const journalPath = path.join(stateDir, "sync-journal.acme.json");
|
|
6400
6379
|
expect(fs.existsSync(journalPath)).toBe(true);
|
|
6401
6380
|
const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
|