@indigoai-us/hq-cloud 6.16.64 → 6.16.66
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 +14 -0
- package/dist/bin/sync-runner-company.d.ts.map +1 -1
- package/dist/bin/sync-runner-company.js +42 -8
- package/dist/bin/sync-runner-company.js.map +1 -1
- package/dist/bin/sync-runner-company.test.js +62 -7
- package/dist/bin/sync-runner-company.test.js.map +1 -1
- package/dist/bin/sync-runner-planning.d.ts +12 -3
- package/dist/bin/sync-runner-planning.d.ts.map +1 -1
- package/dist/bin/sync-runner-planning.js +65 -3
- package/dist/bin/sync-runner-planning.js.map +1 -1
- package/dist/bin/sync-runner-planning.test.js +172 -1
- package/dist/bin/sync-runner-planning.test.js.map +1 -1
- package/dist/bin/sync-runner-watch-loop.d.ts +52 -3
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-loop.js +443 -167
- package/dist/bin/sync-runner-watch-loop.js.map +1 -1
- package/dist/bin/sync-runner-watch-routes.d.ts +19 -2
- package/dist/bin/sync-runner-watch-routes.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-routes.js +40 -5
- package/dist/bin/sync-runner-watch-routes.js.map +1 -1
- package/dist/bin/sync-runner-watch-routes.test.js +47 -0
- package/dist/bin/sync-runner-watch-routes.test.js.map +1 -1
- package/dist/bin/sync-runner.d.ts +64 -6
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +48 -4
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +1577 -169
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/onepass-pull-planner-regressions.test.d.ts +2 -0
- package/dist/cli/onepass-pull-planner-regressions.test.d.ts.map +1 -0
- package/dist/cli/onepass-pull-planner-regressions.test.js +300 -0
- package/dist/cli/onepass-pull-planner-regressions.test.js.map +1 -0
- package/dist/cli/onepass-pull-planner.test.d.ts +2 -0
- package/dist/cli/onepass-pull-planner.test.d.ts.map +1 -0
- package/dist/cli/onepass-pull-planner.test.js +266 -0
- package/dist/cli/onepass-pull-planner.test.js.map +1 -0
- package/dist/cli/push-scan-exclusions.test.js +4 -4
- package/dist/cli/push-scan-exclusions.test.js.map +1 -1
- package/dist/cli/share.d.ts +31 -1
- package/dist/cli/share.d.ts.map +1 -1
- package/dist/cli/share.js +136 -6
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +295 -5
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/streaming-pull-planner.bench.test.d.ts +15 -0
- package/dist/cli/streaming-pull-planner.bench.test.d.ts.map +1 -0
- package/dist/cli/streaming-pull-planner.bench.test.js +273 -0
- package/dist/cli/streaming-pull-planner.bench.test.js.map +1 -0
- package/dist/cli/sync-scope.test.js +8 -0
- package/dist/cli/sync-scope.test.js.map +1 -1
- package/dist/cli/sync.d.ts +12 -0
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +302 -123
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +255 -2
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/hq-root-recovery.d.ts +30 -0
- package/dist/hq-root-recovery.d.ts.map +1 -0
- package/dist/hq-root-recovery.js +131 -0
- package/dist/hq-root-recovery.js.map +1 -0
- package/dist/hq-root-recovery.test.d.ts +2 -0
- package/dist/hq-root-recovery.test.d.ts.map +1 -0
- package/dist/hq-root-recovery.test.js +65 -0
- package/dist/hq-root-recovery.test.js.map +1 -0
- package/dist/journal-row-store.d.ts +40 -5
- package/dist/journal-row-store.d.ts.map +1 -1
- package/dist/journal-row-store.js +240 -21
- package/dist/journal-row-store.js.map +1 -1
- package/dist/journal-row-store.test.js +54 -0
- package/dist/journal-row-store.test.js.map +1 -1
- package/dist/journal-snapshot-v4.d.ts.map +1 -1
- package/dist/journal-snapshot-v4.js +11 -6
- package/dist/journal-snapshot-v4.js.map +1 -1
- package/dist/journal-store.test.js +324 -7
- package/dist/journal-store.test.js.map +1 -1
- package/dist/journal.d.ts +51 -16
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +303 -79
- package/dist/journal.js.map +1 -1
- package/dist/lib/exit-codes.d.ts +15 -0
- package/dist/lib/exit-codes.d.ts.map +1 -1
- package/dist/lib/exit-codes.js +15 -0
- package/dist/lib/exit-codes.js.map +1 -1
- package/dist/personal-vault.d.ts.map +1 -1
- package/dist/personal-vault.js +10 -3
- package/dist/personal-vault.js.map +1 -1
- package/dist/personal-vault.test.js +35 -0
- package/dist/personal-vault.test.js.map +1 -1
- package/dist/remote-pull.d.ts.map +1 -1
- package/dist/remote-pull.js +9 -2
- package/dist/remote-pull.js.map +1 -1
- package/dist/remote-pull.test.js +59 -2
- package/dist/remote-pull.test.js.map +1 -1
- package/dist/s3.d.ts +1 -0
- package/dist/s3.d.ts.map +1 -1
- package/dist/s3.js +20 -2
- package/dist/s3.js.map +1 -1
- package/dist/s3.list-cost.test.d.ts +2 -0
- package/dist/s3.list-cost.test.d.ts.map +1 -0
- package/dist/s3.list-cost.test.js +51 -0
- package/dist/s3.list-cost.test.js.map +1 -0
- package/dist/sync/area-ledger.d.ts +27 -3
- package/dist/sync/area-ledger.d.ts.map +1 -1
- package/dist/sync/area-ledger.js +149 -39
- package/dist/sync/area-ledger.js.map +1 -1
- package/dist/sync/area-ledger.test.js +192 -5
- package/dist/sync/area-ledger.test.js.map +1 -1
- package/dist/sync/parallel-sync-coordinator.test.js +29 -0
- package/dist/sync/parallel-sync-coordinator.test.js.map +1 -1
- package/dist/sync/realtime-telemetry.d.ts +1 -1
- package/dist/sync/realtime-telemetry.d.ts.map +1 -1
- package/dist/sync/realtime-telemetry.js +14 -6
- package/dist/sync/realtime-telemetry.js.map +1 -1
- package/dist/sync/realtime-telemetry.test.js +136 -16
- package/dist/sync/realtime-telemetry.test.js.map +1 -1
- package/dist/sync/remote-file-spool.d.ts +16 -0
- package/dist/sync/remote-file-spool.d.ts.map +1 -0
- package/dist/sync/remote-file-spool.js +91 -0
- package/dist/sync/remote-file-spool.js.map +1 -0
- package/dist/sync/remote-file-spool.test.d.ts +2 -0
- package/dist/sync/remote-file-spool.test.d.ts.map +1 -0
- package/dist/sync/remote-file-spool.test.js +77 -0
- package/dist/sync/remote-file-spool.test.js.map +1 -0
- package/dist/sync/state-store.d.ts +6 -0
- package/dist/sync/state-store.d.ts.map +1 -1
- package/dist/sync/state-store.js +14 -2
- package/dist/sync/state-store.js.map +1 -1
- package/dist/watch-coverage.d.ts +24 -0
- package/dist/watch-coverage.d.ts.map +1 -0
- package/dist/watch-coverage.js +46 -0
- package/dist/watch-coverage.js.map +1 -0
- package/dist/watch-coverage.test.d.ts +2 -0
- package/dist/watch-coverage.test.d.ts.map +1 -0
- package/dist/watch-coverage.test.js +56 -0
- package/dist/watch-coverage.test.js.map +1 -0
- package/dist/watcher.d.ts +31 -1
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +172 -0
- package/dist/watcher.js.map +1 -1
- package/dist/watcher.test.js +78 -0
- package/dist/watcher.test.js.map +1 -1
- package/package.json +2 -1
package/dist/cli/share.test.js
CHANGED
|
@@ -64,7 +64,7 @@ vi.mock("readline", () => ({
|
|
|
64
64
|
})),
|
|
65
65
|
}));
|
|
66
66
|
import * as readline from "readline";
|
|
67
|
-
import { share, ServerOwnedPushPathsError, UnreachablePushPathsError, UnregisteredCompanySkillError, currentUploadBytes, parseRegisteredCompanySkillUid, _testing as shareTesting, } from "./share.js";
|
|
67
|
+
import { share, ServerOwnedPushPathsError, UnreachablePushPathsError, UnregisteredCompanySkillError, currentUploadBytes, FULL_PUSH_COOPERATIVE_CHECKPOINT_BATCH_SIZE, parseRegisteredCompanySkillUid, _testing as shareTesting, } from "./share.js";
|
|
68
68
|
import { deleteRemoteFile, downloadFile, headRemoteFile, primeObjectTransport, primeUploads, uploadFile, uploadSymlink, WindowsSymlinkPrivilegeError, } from "../s3.js";
|
|
69
69
|
import { VaultAuthError, VaultClientError, VaultConflictError, VaultPermissionDeniedError, } from "../vault-client.js";
|
|
70
70
|
import { CognitoRefreshError } from "../cognito-auth.js";
|
|
@@ -72,6 +72,7 @@ import { RateLimitedError } from "../object-io.js";
|
|
|
72
72
|
import { AreaLedgerMigration, closeCachedAreaJournalMigration, hashFile, openJournalStateStoreForMigration, readJournal, readJournalScoped, setAreaLedgerMigrationTestHooksForTest, writeJournal, } from "../journal.js";
|
|
73
73
|
import { ReconcileCursorStore } from "../sync/reconcile-cursor.js";
|
|
74
74
|
import { AreaResolver } from "../sync/area-resolver.js";
|
|
75
|
+
import { CooperativePassScheduler } from "../bin/sync-runner-watch-loop.js";
|
|
75
76
|
/**
|
|
76
77
|
* Read a journal the way production does.
|
|
77
78
|
*
|
|
@@ -1658,6 +1659,7 @@ describe("share", () => {
|
|
|
1658
1659
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
1659
1660
|
fs.rmSync(stateDir, { recursive: true, force: true });
|
|
1660
1661
|
delete process.env.HQ_STATE_DIR;
|
|
1662
|
+
shareTesting.disableCooperativeRowVisits();
|
|
1661
1663
|
});
|
|
1662
1664
|
it("shares a single file keyed relative to the company root", async () => {
|
|
1663
1665
|
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
@@ -1675,6 +1677,285 @@ describe("share", () => {
|
|
|
1675
1677
|
// Remote key must be company-relative, not hqRoot-relative
|
|
1676
1678
|
expect(uploadFile).toHaveBeenCalledWith(expect.anything(), testFile, "test.md", undefined, expect.anything());
|
|
1677
1679
|
});
|
|
1680
|
+
it("hands queued realtime work a settled full-push boundary without losing journal rows", async () => {
|
|
1681
|
+
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
1682
|
+
const fileCount = FULL_PUSH_COOPERATIVE_CHECKPOINT_BATCH_SIZE + 1;
|
|
1683
|
+
for (let index = 0; index < fileCount; index++) {
|
|
1684
|
+
const file = path.join(companyRoot, "docs", "full-push", `${index}.md`);
|
|
1685
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
1686
|
+
fs.writeFileSync(file, "full-push body");
|
|
1687
|
+
}
|
|
1688
|
+
const scheduler = new CooperativePassScheduler(() => 0);
|
|
1689
|
+
let uploadsCompleted = 0;
|
|
1690
|
+
let uploadsInFlight = 0;
|
|
1691
|
+
let maxUploadsInFlight = 0;
|
|
1692
|
+
let realtimeQueued = false;
|
|
1693
|
+
let realtimeDispatchedAt = -1;
|
|
1694
|
+
let checkpointCalls = 0;
|
|
1695
|
+
let realtimePass;
|
|
1696
|
+
const upload = vi.mocked(uploadFile);
|
|
1697
|
+
const originalUpload = upload.getMockImplementation();
|
|
1698
|
+
upload.mockImplementation(async () => {
|
|
1699
|
+
uploadsInFlight++;
|
|
1700
|
+
maxUploadsInFlight = Math.max(maxUploadsInFlight, uploadsInFlight);
|
|
1701
|
+
await Promise.resolve();
|
|
1702
|
+
uploadsCompleted++;
|
|
1703
|
+
if (uploadsCompleted === 1) {
|
|
1704
|
+
realtimeQueued = true;
|
|
1705
|
+
realtimePass = scheduler.submit(async () => {
|
|
1706
|
+
// `executeUploads` drains the full unit before the scheduler grants
|
|
1707
|
+
// realtime the root lock: no two durable writers overlap.
|
|
1708
|
+
expect(uploadsInFlight).toBe(0);
|
|
1709
|
+
realtimeDispatchedAt = uploadsCompleted;
|
|
1710
|
+
const realtimePath = path.join(companyRoot, "docs", "realtime.md");
|
|
1711
|
+
fs.writeFileSync(realtimePath, "realtime body");
|
|
1712
|
+
const journal = readJournal("acme");
|
|
1713
|
+
journal.files["docs/realtime.md"] = {
|
|
1714
|
+
hash: hashFile(realtimePath),
|
|
1715
|
+
size: fs.statSync(realtimePath).size,
|
|
1716
|
+
syncedAt: "2026-09-17T12:00:00.000Z",
|
|
1717
|
+
direction: "up",
|
|
1718
|
+
remoteEtag: "realtime",
|
|
1719
|
+
mtimeMs: fs.statSync(realtimePath).mtimeMs,
|
|
1720
|
+
ctimeMs: fs.statSync(realtimePath).ctimeMs,
|
|
1721
|
+
};
|
|
1722
|
+
writeJournal("acme", journal);
|
|
1723
|
+
}, "realtime");
|
|
1724
|
+
}
|
|
1725
|
+
uploadsInFlight--;
|
|
1726
|
+
return { etag: '"full-push-etag"' };
|
|
1727
|
+
});
|
|
1728
|
+
try {
|
|
1729
|
+
await scheduler.submit(async ({ checkpoint }) => {
|
|
1730
|
+
await share({
|
|
1731
|
+
paths: [companyRoot],
|
|
1732
|
+
company: "acme",
|
|
1733
|
+
vaultConfig: mockConfig,
|
|
1734
|
+
hqRoot: tmpDir,
|
|
1735
|
+
skipUnchanged: true,
|
|
1736
|
+
propagateDeletes: true,
|
|
1737
|
+
cooperativeCheckpoint: async () => {
|
|
1738
|
+
checkpointCalls++;
|
|
1739
|
+
return checkpoint();
|
|
1740
|
+
},
|
|
1741
|
+
});
|
|
1742
|
+
}, "chore");
|
|
1743
|
+
await realtimePass;
|
|
1744
|
+
}
|
|
1745
|
+
finally {
|
|
1746
|
+
if (originalUpload)
|
|
1747
|
+
upload.mockImplementation(originalUpload);
|
|
1748
|
+
else
|
|
1749
|
+
upload.mockReset();
|
|
1750
|
+
}
|
|
1751
|
+
expect(maxUploadsInFlight).toBeGreaterThan(1);
|
|
1752
|
+
expect(realtimeQueued).toBe(true);
|
|
1753
|
+
expect(checkpointCalls).toBeGreaterThan(0);
|
|
1754
|
+
expect(realtimeDispatchedAt).toBeGreaterThan(0);
|
|
1755
|
+
expect(realtimeDispatchedAt).toBeLessThanOrEqual(FULL_PUSH_COOPERATIVE_CHECKPOINT_BATCH_SIZE);
|
|
1756
|
+
// Reopen the durable journal, rather than inspecting the stale first-pass
|
|
1757
|
+
// object: every pre-handoff key and the realtime key must survive.
|
|
1758
|
+
const durable = readJournal("acme");
|
|
1759
|
+
expect(Object.keys(durable.files)).toHaveLength(fileCount + 1);
|
|
1760
|
+
expect(durable.files["docs/realtime.md"]).toBeDefined();
|
|
1761
|
+
for (let index = 0; index < fileCount; index++) {
|
|
1762
|
+
expect(durable.files[`docs/full-push/${index}.md`]).toBeDefined();
|
|
1763
|
+
}
|
|
1764
|
+
}, 120_000);
|
|
1765
|
+
it("hands an all-skipped full personal push to queued realtime work", async () => {
|
|
1766
|
+
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
1767
|
+
const fileCount = FULL_PUSH_COOPERATIVE_CHECKPOINT_BATCH_SIZE + 1;
|
|
1768
|
+
const files = {};
|
|
1769
|
+
for (let index = 0; index < fileCount; index++) {
|
|
1770
|
+
const relativePath = `docs/unchanged/${index}.md`;
|
|
1771
|
+
const file = path.join(companyRoot, relativePath);
|
|
1772
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
1773
|
+
fs.writeFileSync(file, "unchanged body");
|
|
1774
|
+
const stat = fs.statSync(file);
|
|
1775
|
+
files[relativePath] = {
|
|
1776
|
+
hash: hashFile(file),
|
|
1777
|
+
size: stat.size,
|
|
1778
|
+
mtimeMs: stat.mtimeMs,
|
|
1779
|
+
ctimeMs: stat.ctimeMs,
|
|
1780
|
+
syncedAt: "2026-09-17T12:00:00.000Z",
|
|
1781
|
+
direction: "up",
|
|
1782
|
+
remoteEtag: "unchanged",
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
seedJournalAt(path.join(stateDir, "sync-journal.acme.json"), {
|
|
1786
|
+
version: "2",
|
|
1787
|
+
lastSync: "2026-09-17T12:00:00.000Z",
|
|
1788
|
+
files,
|
|
1789
|
+
pulls: [],
|
|
1790
|
+
});
|
|
1791
|
+
const scheduler = new CooperativePassScheduler(() => 0);
|
|
1792
|
+
let fullPassFinished = false;
|
|
1793
|
+
let eventLoopTicked = false;
|
|
1794
|
+
let realtimeQueued = false;
|
|
1795
|
+
let realtimeRanBeforeFullPassFinished = false;
|
|
1796
|
+
let checkpointCalls = 0;
|
|
1797
|
+
let realtimePass;
|
|
1798
|
+
let resolveRealtimeScheduled;
|
|
1799
|
+
const realtimeScheduled = new Promise((resolve) => {
|
|
1800
|
+
resolveRealtimeScheduled = resolve;
|
|
1801
|
+
});
|
|
1802
|
+
const queueRealtime = () => {
|
|
1803
|
+
if (realtimeQueued)
|
|
1804
|
+
return;
|
|
1805
|
+
realtimeQueued = true;
|
|
1806
|
+
setImmediate(() => {
|
|
1807
|
+
eventLoopTicked = true;
|
|
1808
|
+
realtimePass = scheduler.submit(async () => {
|
|
1809
|
+
realtimeRanBeforeFullPassFinished = !fullPassFinished;
|
|
1810
|
+
}, "realtime");
|
|
1811
|
+
resolveRealtimeScheduled?.();
|
|
1812
|
+
});
|
|
1813
|
+
};
|
|
1814
|
+
await scheduler.submit(async ({ checkpoint }) => {
|
|
1815
|
+
try {
|
|
1816
|
+
const result = await share({
|
|
1817
|
+
paths: [companyRoot],
|
|
1818
|
+
company: "acme",
|
|
1819
|
+
vaultConfig: mockConfig,
|
|
1820
|
+
hqRoot: tmpDir,
|
|
1821
|
+
skipUnchanged: true,
|
|
1822
|
+
onEvent: (event) => {
|
|
1823
|
+
if (event.type === "plan")
|
|
1824
|
+
queueRealtime();
|
|
1825
|
+
},
|
|
1826
|
+
cooperativeCheckpoint: async () => {
|
|
1827
|
+
checkpointCalls++;
|
|
1828
|
+
return checkpoint();
|
|
1829
|
+
},
|
|
1830
|
+
});
|
|
1831
|
+
expect(result.filesSkipped).toBe(fileCount);
|
|
1832
|
+
expect(result.filesUploaded).toBe(0);
|
|
1833
|
+
}
|
|
1834
|
+
finally {
|
|
1835
|
+
fullPassFinished = true;
|
|
1836
|
+
}
|
|
1837
|
+
}, "chore");
|
|
1838
|
+
await realtimeScheduled;
|
|
1839
|
+
await realtimePass;
|
|
1840
|
+
expect(eventLoopTicked).toBe(true);
|
|
1841
|
+
expect(realtimeQueued).toBe(true);
|
|
1842
|
+
expect(realtimeRanBeforeFullPassFinished).toBe(true);
|
|
1843
|
+
expect(checkpointCalls).toBeGreaterThan(0);
|
|
1844
|
+
expect(uploadFile).not.toHaveBeenCalled();
|
|
1845
|
+
expect(Object.keys(readJournal("acme").files)).toHaveLength(fileCount);
|
|
1846
|
+
}, 120_000);
|
|
1847
|
+
it("resumes a skip-only full push at the next unit after realtime hand-off", async () => {
|
|
1848
|
+
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
1849
|
+
const fileCount = FULL_PUSH_COOPERATIVE_CHECKPOINT_BATCH_SIZE * 3;
|
|
1850
|
+
const files = {};
|
|
1851
|
+
for (let index = 0; index < fileCount; index++) {
|
|
1852
|
+
const relativePath = `docs/resume/${index}.md`;
|
|
1853
|
+
const file = path.join(companyRoot, relativePath);
|
|
1854
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
1855
|
+
fs.writeFileSync(file, "resume body");
|
|
1856
|
+
const stat = fs.statSync(file);
|
|
1857
|
+
files[relativePath] = {
|
|
1858
|
+
hash: hashFile(file),
|
|
1859
|
+
size: stat.size,
|
|
1860
|
+
mtimeMs: stat.mtimeMs,
|
|
1861
|
+
ctimeMs: stat.ctimeMs,
|
|
1862
|
+
syncedAt: "2026-09-17T12:00:00.000Z",
|
|
1863
|
+
direction: "up",
|
|
1864
|
+
remoteEtag: "unchanged",
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
seedJournalAt(path.join(stateDir, "sync-journal.acme.json"), {
|
|
1868
|
+
version: "2",
|
|
1869
|
+
lastSync: "2026-09-17T12:00:00.000Z",
|
|
1870
|
+
files,
|
|
1871
|
+
pulls: [],
|
|
1872
|
+
});
|
|
1873
|
+
const scheduler = new CooperativePassScheduler(() => 0);
|
|
1874
|
+
let fullPassFinished = false;
|
|
1875
|
+
let realtimeRanBeforeFullPassFinished = false;
|
|
1876
|
+
const planEvents = [];
|
|
1877
|
+
const realtimePhaseRuns = [];
|
|
1878
|
+
const realtimePasses = [];
|
|
1879
|
+
const queuedForPhase = new Set();
|
|
1880
|
+
let currentPhase = "collection";
|
|
1881
|
+
const queueRealtimeForCurrentPhase = () => {
|
|
1882
|
+
const phase = currentPhase;
|
|
1883
|
+
if (queuedForPhase.has(phase))
|
|
1884
|
+
return;
|
|
1885
|
+
queuedForPhase.add(phase);
|
|
1886
|
+
setImmediate(() => {
|
|
1887
|
+
realtimePasses.push(scheduler.submit(async () => {
|
|
1888
|
+
realtimePhaseRuns.push(phase);
|
|
1889
|
+
if (!fullPassFinished)
|
|
1890
|
+
realtimeRanBeforeFullPassFinished = true;
|
|
1891
|
+
}, "realtime"));
|
|
1892
|
+
});
|
|
1893
|
+
};
|
|
1894
|
+
shareTesting.enableCooperativeRowVisits();
|
|
1895
|
+
let visits = {
|
|
1896
|
+
collect: new Map(),
|
|
1897
|
+
plan: new Map(),
|
|
1898
|
+
};
|
|
1899
|
+
try {
|
|
1900
|
+
await scheduler.submit(async ({ checkpoint }) => {
|
|
1901
|
+
queueRealtimeForCurrentPhase();
|
|
1902
|
+
try {
|
|
1903
|
+
const result = await share({
|
|
1904
|
+
paths: [companyRoot],
|
|
1905
|
+
company: "acme",
|
|
1906
|
+
vaultConfig: mockConfig,
|
|
1907
|
+
hqRoot: tmpDir,
|
|
1908
|
+
skipUnchanged: true,
|
|
1909
|
+
propagateDeletes: true,
|
|
1910
|
+
onEvent: (event) => {
|
|
1911
|
+
if (event.type === "plan") {
|
|
1912
|
+
planEvents.push(event);
|
|
1913
|
+
currentPhase = "execution";
|
|
1914
|
+
queueRealtimeForCurrentPhase();
|
|
1915
|
+
}
|
|
1916
|
+
},
|
|
1917
|
+
cooperativeCheckpoint: async () => {
|
|
1918
|
+
if (currentPhase === "collection" &&
|
|
1919
|
+
shareTesting.cooperativeRowVisits().plan.size > 0) {
|
|
1920
|
+
currentPhase = "planning";
|
|
1921
|
+
}
|
|
1922
|
+
if (currentPhase === "planning")
|
|
1923
|
+
queueRealtimeForCurrentPhase();
|
|
1924
|
+
return checkpoint();
|
|
1925
|
+
},
|
|
1926
|
+
});
|
|
1927
|
+
expect(result.filesSkipped).toBe(fileCount);
|
|
1928
|
+
expect(result.filesUploaded).toBe(0);
|
|
1929
|
+
}
|
|
1930
|
+
finally {
|
|
1931
|
+
fullPassFinished = true;
|
|
1932
|
+
}
|
|
1933
|
+
}, "chore");
|
|
1934
|
+
await Promise.all(realtimePasses);
|
|
1935
|
+
visits = shareTesting.cooperativeRowVisits();
|
|
1936
|
+
}
|
|
1937
|
+
finally {
|
|
1938
|
+
shareTesting.disableCooperativeRowVisits();
|
|
1939
|
+
}
|
|
1940
|
+
expect(realtimeRanBeforeFullPassFinished).toBe(true);
|
|
1941
|
+
expect(realtimePhaseRuns).toEqual(["collection", "planning", "execution"]);
|
|
1942
|
+
expect(planEvents).toHaveLength(1);
|
|
1943
|
+
expect(planEvents[0]).toMatchObject({
|
|
1944
|
+
type: "plan",
|
|
1945
|
+
filesToUpload: 0,
|
|
1946
|
+
filesToSkip: fileCount,
|
|
1947
|
+
filesToDelete: 0,
|
|
1948
|
+
});
|
|
1949
|
+
expect(visits.collect.size).toBe(fileCount);
|
|
1950
|
+
expect(visits.plan.size).toBe(fileCount);
|
|
1951
|
+
for (let index = 0; index < fileCount; index++) {
|
|
1952
|
+
const relativePath = `docs/resume/${index}.md`;
|
|
1953
|
+
expect(visits.collect.get(relativePath)).toBe(1);
|
|
1954
|
+
expect(visits.plan.get(relativePath)).toBe(1);
|
|
1955
|
+
}
|
|
1956
|
+
expect(uploadFile).not.toHaveBeenCalled();
|
|
1957
|
+
expect(Object.keys(readJournal("acme").files)).toHaveLength(fileCount);
|
|
1958
|
+
}, 120_000);
|
|
1678
1959
|
it("defers a rate-limited upload as a diagnostic error and leaves the journal row unchanged", async () => {
|
|
1679
1960
|
// Production (2026-09-18): vault 100/hr throttle on a per-file PUT was
|
|
1680
1961
|
// emitted as a hard type:"error", so the company landed in errors[] and
|
|
@@ -2085,7 +2366,7 @@ describe("share", () => {
|
|
|
2085
2366
|
});
|
|
2086
2367
|
expect(result.filesUploaded).toBe(2);
|
|
2087
2368
|
});
|
|
2088
|
-
it("walks a very large directory without using its entries as call arguments", () => {
|
|
2369
|
+
it("walks a very large directory without using its entries as call arguments", async () => {
|
|
2089
2370
|
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
2090
2371
|
fs.mkdirSync(companyRoot, { recursive: true });
|
|
2091
2372
|
const nestedDir = path.join(companyRoot, "nested");
|
|
@@ -2112,8 +2393,15 @@ describe("share", () => {
|
|
|
2112
2393
|
.spyOn(fs, "readdirSync")
|
|
2113
2394
|
.mockImplementation(((dir) => path.resolve(String(dir)) === nestedDir ? nestedEntries : rootEntries));
|
|
2114
2395
|
let heartbeatCalls = 0;
|
|
2396
|
+
let eventLoopTicked = false;
|
|
2397
|
+
const eventLoopTick = new Promise((resolve) => {
|
|
2398
|
+
setImmediate(() => {
|
|
2399
|
+
eventLoopTicked = true;
|
|
2400
|
+
resolve();
|
|
2401
|
+
});
|
|
2402
|
+
});
|
|
2115
2403
|
try {
|
|
2116
|
-
const collected = shareTesting.collectFiles([companyRoot], tmpDir, companyRoot, () => true, { onHeartbeat: () => heartbeatCalls++ });
|
|
2404
|
+
const collected = await shareTesting.collectFiles([companyRoot], tmpDir, companyRoot, () => true, { onHeartbeat: () => heartbeatCalls++ });
|
|
2117
2405
|
expect(collected).toHaveLength(entryCount);
|
|
2118
2406
|
expect(collected[0]).toMatchObject({ relativePath: "before.md" });
|
|
2119
2407
|
expect(collected[1]).toMatchObject({ relativePath: "nested/child.md" });
|
|
@@ -2122,6 +2410,8 @@ describe("share", () => {
|
|
|
2122
2410
|
relativePath: `entry-${entryCount - 3}.md`,
|
|
2123
2411
|
});
|
|
2124
2412
|
expect(heartbeatCalls).toBeGreaterThan(entryCount);
|
|
2413
|
+
expect(eventLoopTicked).toBe(true);
|
|
2414
|
+
await eventLoopTick;
|
|
2125
2415
|
}
|
|
2126
2416
|
finally {
|
|
2127
2417
|
readdirSpy.mockRestore();
|
|
@@ -7851,7 +8141,7 @@ describe("share", () => {
|
|
|
7851
8141
|
expect(uploadFile).toHaveBeenCalledWith(expect.anything(), realPolicy, "policies/real.md", undefined, expect.anything());
|
|
7852
8142
|
expect(uploadSymlink).toHaveBeenCalledWith(expect.anything(), "real.md", "policies/link.md", undefined, expect.anything());
|
|
7853
8143
|
});
|
|
7854
|
-
it("skips and reports a top-level link whose win32 readlink raises EINVAL", () => {
|
|
8144
|
+
it("skips and reports a top-level link whose win32 readlink raises EINVAL", async () => {
|
|
7855
8145
|
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
7856
8146
|
const targetDir = path.join(tmpDir, "outside", "target");
|
|
7857
8147
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
@@ -7870,7 +8160,7 @@ describe("share", () => {
|
|
|
7870
8160
|
const warnings = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
7871
8161
|
const unreachable = [];
|
|
7872
8162
|
try {
|
|
7873
|
-
const collected = shareTesting.collectFiles([unreadableLink], tmpDir, companyRoot, () => true, {
|
|
8163
|
+
const collected = await shareTesting.collectFiles([unreadableLink], tmpDir, companyRoot, () => true, {
|
|
7874
8164
|
onUnreachablePath: (namedPath, reason) => unreachable.push([namedPath, reason]),
|
|
7875
8165
|
});
|
|
7876
8166
|
expect(collected).toEqual([]);
|