@indigoai-us/hq-cloud 6.16.58-alpha.3 → 6.16.59
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 +0 -14
- package/dist/bin/sync-runner-company.d.ts.map +1 -1
- package/dist/bin/sync-runner-company.js +89 -57
- package/dist/bin/sync-runner-company.js.map +1 -1
- package/dist/bin/sync-runner-company.test.js +313 -56
- package/dist/bin/sync-runner-company.test.js.map +1 -1
- package/dist/bin/sync-runner-planning.d.ts +3 -12
- package/dist/bin/sync-runner-planning.d.ts.map +1 -1
- package/dist/bin/sync-runner-planning.js +3 -65
- package/dist/bin/sync-runner-planning.js.map +1 -1
- package/dist/bin/sync-runner-planning.test.js +1 -172
- package/dist/bin/sync-runner-planning.test.js.map +1 -1
- package/dist/bin/sync-runner-watch-loop.d.ts +2 -51
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-loop.js +165 -409
- package/dist/bin/sync-runner-watch-loop.js.map +1 -1
- package/dist/bin/sync-runner-watch-routes.d.ts +2 -19
- package/dist/bin/sync-runner-watch-routes.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-routes.js +5 -40
- package/dist/bin/sync-runner-watch-routes.js.map +1 -1
- package/dist/bin/sync-runner-watch-routes.test.js +0 -47
- package/dist/bin/sync-runner-watch-routes.test.js.map +1 -1
- package/dist/bin/sync-runner.d.ts +13 -59
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +4 -48
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +229 -1499
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/share.d.ts +0 -19
- package/dist/cli/share.d.ts.map +1 -1
- package/dist/cli/share.js +0 -55
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +1 -87
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/sync-scope.test.js +0 -8
- package/dist/cli/sync-scope.test.js.map +1 -1
- package/dist/cli/sync.d.ts +8 -12
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +152 -302
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +126 -256
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/cognito-auth.d.ts +34 -1
- package/dist/cognito-auth.d.ts.map +1 -1
- package/dist/cognito-auth.js +92 -21
- package/dist/cognito-auth.js.map +1 -1
- package/dist/context.d.ts +5 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +92 -12
- package/dist/context.js.map +1 -1
- package/dist/context.test.js +77 -2
- package/dist/context.test.js.map +1 -1
- package/dist/ignore.d.ts +10 -0
- package/dist/ignore.d.ts.map +1 -1
- package/dist/ignore.js +47 -5
- package/dist/ignore.js.map +1 -1
- package/dist/ignore.test.js +30 -3
- package/dist/ignore.test.js.map +1 -1
- package/dist/journal-row-store.d.ts +5 -40
- package/dist/journal-row-store.d.ts.map +1 -1
- package/dist/journal-row-store.js +21 -240
- package/dist/journal-row-store.js.map +1 -1
- package/dist/journal-row-store.test.js +0 -54
- 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 +6 -11
- package/dist/journal-snapshot-v4.js.map +1 -1
- package/dist/journal-store.test.js +7 -324
- package/dist/journal-store.test.js.map +1 -1
- package/dist/journal.d.ts +16 -51
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +79 -303
- package/dist/journal.js.map +1 -1
- package/dist/lib/exit-codes.d.ts +0 -15
- package/dist/lib/exit-codes.d.ts.map +1 -1
- package/dist/lib/exit-codes.js +0 -15
- package/dist/lib/exit-codes.js.map +1 -1
- package/dist/lib/net-errors.d.ts +12 -0
- package/dist/lib/net-errors.d.ts.map +1 -1
- package/dist/lib/net-errors.js +35 -0
- package/dist/lib/net-errors.js.map +1 -1
- package/dist/lib/net-errors.test.js +19 -1
- package/dist/lib/net-errors.test.js.map +1 -1
- package/dist/machine-auth.test.js +6 -4
- package/dist/machine-auth.test.js.map +1 -1
- package/dist/machine-token-cache-isolation.test.d.ts +24 -0
- package/dist/machine-token-cache-isolation.test.d.ts.map +1 -0
- package/dist/machine-token-cache-isolation.test.js +241 -0
- package/dist/machine-token-cache-isolation.test.js.map +1 -0
- package/dist/manifest/build-manifest-resident-parity.test.js +3 -3
- package/dist/manifest/build-manifest-resident-parity.test.js.map +1 -1
- package/dist/personal-vault.d.ts.map +1 -1
- package/dist/personal-vault.js +3 -10
- package/dist/personal-vault.js.map +1 -1
- package/dist/personal-vault.test.js +0 -35
- package/dist/personal-vault.test.js.map +1 -1
- package/dist/remote-pull.d.ts.map +1 -1
- package/dist/remote-pull.js +2 -9
- package/dist/remote-pull.js.map +1 -1
- package/dist/remote-pull.test.js +2 -59
- package/dist/remote-pull.test.js.map +1 -1
- package/dist/s3.d.ts +0 -1
- package/dist/s3.d.ts.map +1 -1
- package/dist/s3.js +2 -20
- package/dist/s3.js.map +1 -1
- package/dist/sync/area-ledger.d.ts +3 -27
- package/dist/sync/area-ledger.d.ts.map +1 -1
- package/dist/sync/area-ledger.js +39 -149
- package/dist/sync/area-ledger.js.map +1 -1
- package/dist/sync/area-ledger.test.js +5 -192
- package/dist/sync/area-ledger.test.js.map +1 -1
- package/dist/sync/parallel-sync-coordinator.test.js +0 -29
- 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 +6 -14
- package/dist/sync/realtime-telemetry.js.map +1 -1
- package/dist/sync/realtime-telemetry.test.js +16 -136
- package/dist/sync/realtime-telemetry.test.js.map +1 -1
- package/dist/sync/state-store.d.ts +0 -6
- package/dist/sync/state-store.d.ts.map +1 -1
- package/dist/sync/state-store.js +2 -14
- package/dist/sync/state-store.js.map +1 -1
- package/dist/watcher.d.ts +0 -12
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +0 -131
- package/dist/watcher.js.map +1 -1
- package/dist/watcher.test.js +0 -63
- package/dist/watcher.test.js.map +1 -1
- package/package.json +1 -2
- package/dist/cli/onepass-pull-planner-regressions.test.d.ts +0 -2
- package/dist/cli/onepass-pull-planner-regressions.test.d.ts.map +0 -1
- package/dist/cli/onepass-pull-planner-regressions.test.js +0 -300
- package/dist/cli/onepass-pull-planner-regressions.test.js.map +0 -1
- package/dist/cli/onepass-pull-planner.test.d.ts +0 -2
- package/dist/cli/onepass-pull-planner.test.d.ts.map +0 -1
- package/dist/cli/onepass-pull-planner.test.js +0 -266
- package/dist/cli/onepass-pull-planner.test.js.map +0 -1
- package/dist/cli/streaming-pull-planner.bench.test.d.ts +0 -15
- package/dist/cli/streaming-pull-planner.bench.test.d.ts.map +0 -1
- package/dist/cli/streaming-pull-planner.bench.test.js +0 -273
- package/dist/cli/streaming-pull-planner.bench.test.js.map +0 -1
- package/dist/s3.list-cost.test.d.ts +0 -2
- package/dist/s3.list-cost.test.d.ts.map +0 -1
- package/dist/s3.list-cost.test.js +0 -51
- package/dist/s3.list-cost.test.js.map +0 -1
- package/dist/sync/remote-file-spool.d.ts +0 -16
- package/dist/sync/remote-file-spool.d.ts.map +0 -1
- package/dist/sync/remote-file-spool.js +0 -91
- package/dist/sync/remote-file-spool.js.map +0 -1
- package/dist/sync/remote-file-spool.test.d.ts +0 -2
- package/dist/sync/remote-file-spool.test.d.ts.map +0 -1
- package/dist/sync/remote-file-spool.test.js +0 -77
- package/dist/sync/remote-file-spool.test.js.map +0 -1
|
@@ -27,9 +27,10 @@ vi.mock("../s3.js", async (importOriginal) => {
|
|
|
27
27
|
};
|
|
28
28
|
});
|
|
29
29
|
import { executeCompanyFanout } from "./sync-runner-company.js";
|
|
30
|
+
import { clearContextCache, resolveEntityContext, setEntityFetchSleepForTest, } from "../context.js";
|
|
30
31
|
import { CognitoAuthError, CognitoRefreshError } from "../cognito-auth.js";
|
|
31
32
|
import { _testing as shareTesting, share, parseRegisteredCompanySkillUid, } from "../cli/share.js";
|
|
32
|
-
import { readJournal } from "../journal.js";
|
|
33
|
+
import { PERSONAL_VAULT_JOURNAL_SLUG, readJournal } from "../journal.js";
|
|
33
34
|
import { VaultConflictError } from "../vault-client.js";
|
|
34
35
|
import { headRemoteFile, uploadFile } from "../s3.js";
|
|
35
36
|
import { setBandwidthGovernorForTesting } from "../bandwidth.js";
|
|
@@ -68,61 +69,6 @@ describe("executeCompanyFanout realtime inventory gate", () => {
|
|
|
68
69
|
expect(emit).toHaveBeenCalledWith(expect.objectContaining({ type: "error", company: "personal", path: "(scope)" }));
|
|
69
70
|
});
|
|
70
71
|
});
|
|
71
|
-
describe("executeCompanyFanout cooperative checkpoints", () => {
|
|
72
|
-
it("hands off only after each target leg has settled and retains the remaining plan", async () => {
|
|
73
|
-
const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-fanout-checkpoint-"));
|
|
74
|
-
const order = [];
|
|
75
|
-
const shareResult = {
|
|
76
|
-
filesUploaded: 0,
|
|
77
|
-
bytesUploaded: 0,
|
|
78
|
-
filesSkipped: 0,
|
|
79
|
-
filesDeleted: 0,
|
|
80
|
-
filesTombstoned: 0,
|
|
81
|
-
filesRefusedStale: 0,
|
|
82
|
-
filesRefusedStalePaths: [],
|
|
83
|
-
filesSuppressedByTombstone: 0,
|
|
84
|
-
filesExcludedByPolicy: 0,
|
|
85
|
-
filesExcludedByScope: 0,
|
|
86
|
-
filesExcludedByIgnore: 0,
|
|
87
|
-
unreachablePaths: [],
|
|
88
|
-
linkedSubtreesNotShipped: [],
|
|
89
|
-
conflictPaths: [],
|
|
90
|
-
aborted: false,
|
|
91
|
-
};
|
|
92
|
-
try {
|
|
93
|
-
await executeCompanyFanout({
|
|
94
|
-
plan: [
|
|
95
|
-
{ uid: "prs_1", slug: "personal", personalMode: true },
|
|
96
|
-
{ uid: "prs_2", slug: "personal-next", personalMode: true },
|
|
97
|
-
],
|
|
98
|
-
direction: "push",
|
|
99
|
-
hqRoot,
|
|
100
|
-
onConflict: "abort",
|
|
101
|
-
client: {},
|
|
102
|
-
vaultConfig: { apiUrl: "https://api.example", authToken: "access-token" },
|
|
103
|
-
syncFn: vi.fn(),
|
|
104
|
-
shareFn: vi.fn(async (options) => {
|
|
105
|
-
order.push(`target:${options.company}`);
|
|
106
|
-
return shareResult;
|
|
107
|
-
}),
|
|
108
|
-
resolveDeletePolicy: () => "all",
|
|
109
|
-
emit: vi.fn(),
|
|
110
|
-
ensureCompanyDir: vi.fn(),
|
|
111
|
-
cooperativeCheckpoint: async () => { order.push("checkpoint"); },
|
|
112
|
-
telemetryClient: null,
|
|
113
|
-
});
|
|
114
|
-
expect(order).toEqual([
|
|
115
|
-
"target:prs_1",
|
|
116
|
-
"checkpoint",
|
|
117
|
-
"target:prs_2",
|
|
118
|
-
"checkpoint",
|
|
119
|
-
]);
|
|
120
|
-
}
|
|
121
|
-
finally {
|
|
122
|
-
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
72
|
describe("executeCompanyFanout — not-shipped event forwarding", () => {
|
|
127
73
|
it("preserves a company skill validation message", async () => {
|
|
128
74
|
const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-fanout-skill-validation-"));
|
|
@@ -609,6 +555,71 @@ describe("executeCompanyFanout — archived session-log skip", () => {
|
|
|
609
555
|
}
|
|
610
556
|
});
|
|
611
557
|
});
|
|
558
|
+
describe("executeCompanyFanout — diagnostic tombstone HEAD deferral", () => {
|
|
559
|
+
it("does not error the company when the only events are diagnostic tombstone deferrals", async () => {
|
|
560
|
+
// A vault 100/hr throttle during planned-deletion HEAD verify is
|
|
561
|
+
// emitted as `{ type: "error", diagnostic: true }`. The fanout must
|
|
562
|
+
// forward that marker and leave errors[] empty so the company
|
|
563
|
+
// settles `complete` and the runner keeps exit 0 (never 2).
|
|
564
|
+
const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-fanout-tombstone-"));
|
|
565
|
+
try {
|
|
566
|
+
const events = [];
|
|
567
|
+
const pullResult = {
|
|
568
|
+
filesDownloaded: 0,
|
|
569
|
+
bytesDownloaded: 0,
|
|
570
|
+
filesSkipped: 0,
|
|
571
|
+
conflicts: 0,
|
|
572
|
+
conflictPaths: [],
|
|
573
|
+
aborted: false,
|
|
574
|
+
newFiles: [],
|
|
575
|
+
newFilesCount: 0,
|
|
576
|
+
filesExcludedByPolicy: 0,
|
|
577
|
+
filesTombstoned: 0,
|
|
578
|
+
filesOutOfScope: 0,
|
|
579
|
+
scopeOrphansRemoved: 0,
|
|
580
|
+
scopeOrphansBlocked: 0,
|
|
581
|
+
};
|
|
582
|
+
const syncFn = vi.fn(async (opts) => {
|
|
583
|
+
opts.onEvent?.({
|
|
584
|
+
type: "error",
|
|
585
|
+
diagnostic: true,
|
|
586
|
+
path: "skills/wbr-finalize/SKILL.md",
|
|
587
|
+
message: "tombstone HEAD verify failed (deferring): RateLimited rate limited (100/hr) — get skills/wbr-finalize/SKILL.md deferred to next sync",
|
|
588
|
+
});
|
|
589
|
+
return pullResult;
|
|
590
|
+
});
|
|
591
|
+
const result = await executeCompanyFanout({
|
|
592
|
+
plan: [{ uid: "prs_1", slug: "personal", personalMode: true }],
|
|
593
|
+
direction: "pull",
|
|
594
|
+
hqRoot,
|
|
595
|
+
onConflict: "keep",
|
|
596
|
+
client: {},
|
|
597
|
+
vaultConfig: { apiUrl: "https://api.example", authToken: "access-token" },
|
|
598
|
+
syncFn,
|
|
599
|
+
shareFn: vi.fn(),
|
|
600
|
+
resolveDeletePolicy: () => "all",
|
|
601
|
+
emit: (e) => events.push(e),
|
|
602
|
+
ensureCompanyDir: vi.fn(),
|
|
603
|
+
telemetryClient: null,
|
|
604
|
+
});
|
|
605
|
+
expect(events).toContainEqual({
|
|
606
|
+
type: "error",
|
|
607
|
+
diagnostic: true,
|
|
608
|
+
company: "personal",
|
|
609
|
+
path: "skills/wbr-finalize/SKILL.md",
|
|
610
|
+
message: "tombstone HEAD verify failed (deferring): RateLimited rate limited (100/hr) — get skills/wbr-finalize/SKILL.md deferred to next sync",
|
|
611
|
+
});
|
|
612
|
+
expect(result.errors).toEqual([]);
|
|
613
|
+
expect(result.companyFailures).toEqual([]);
|
|
614
|
+
expect(result.transientCompanies).toEqual([]);
|
|
615
|
+
expect(events.some((e) => e.type === "error" && e.diagnostic !== true)).toBe(false);
|
|
616
|
+
expect(result.stateByCompany.get("personal")?.status).toBe("complete");
|
|
617
|
+
}
|
|
618
|
+
finally {
|
|
619
|
+
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
});
|
|
612
623
|
describe("executeCompanyFanout — Windows rename-blocked skip (HQ-DESKTOP-54, second path)", () => {
|
|
613
624
|
it("re-emits the skip with the company label, never errors the company, and keeps it complete", async () => {
|
|
614
625
|
// A transient Windows rename refusal surfaces from the pull leg as the
|
|
@@ -1163,6 +1174,104 @@ describe("executeCompanyFanout — scoped push walk roots", () => {
|
|
|
1163
1174
|
}
|
|
1164
1175
|
});
|
|
1165
1176
|
});
|
|
1177
|
+
describe("executeCompanyFanout — entity-fetch 429 (2026-09-18 Odin)", () => {
|
|
1178
|
+
const vaultConfig = {
|
|
1179
|
+
apiUrl: "https://vault-api.test",
|
|
1180
|
+
authToken: "access-token",
|
|
1181
|
+
};
|
|
1182
|
+
const entity = {
|
|
1183
|
+
uid: "cmp_01KQ2RYAHXHDPCTY9GPQPTH3DG",
|
|
1184
|
+
slug: "indigo",
|
|
1185
|
+
bucketName: "hq-vault-indigo",
|
|
1186
|
+
status: "active",
|
|
1187
|
+
};
|
|
1188
|
+
const vend = {
|
|
1189
|
+
credentials: {
|
|
1190
|
+
accessKeyId: "ASIA_TEST",
|
|
1191
|
+
secretAccessKey: "secret",
|
|
1192
|
+
sessionToken: "session",
|
|
1193
|
+
expiration: new Date(Date.now() + 15 * 60 * 1000).toISOString(),
|
|
1194
|
+
},
|
|
1195
|
+
expiresAt: new Date(Date.now() + 15 * 60 * 1000).toISOString(),
|
|
1196
|
+
};
|
|
1197
|
+
const runIndigoPull = async (syncFn) => {
|
|
1198
|
+
const hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-fanout-429-"));
|
|
1199
|
+
const events = [];
|
|
1200
|
+
try {
|
|
1201
|
+
const result = await executeCompanyFanout({
|
|
1202
|
+
plan: [{ uid: entity.uid, slug: "indigo" }],
|
|
1203
|
+
direction: "pull",
|
|
1204
|
+
hqRoot,
|
|
1205
|
+
onConflict: "keep",
|
|
1206
|
+
client: {},
|
|
1207
|
+
vaultConfig,
|
|
1208
|
+
syncFn,
|
|
1209
|
+
shareFn: vi.fn(),
|
|
1210
|
+
resolveDeletePolicy: () => "all",
|
|
1211
|
+
emit: (e) => events.push(e),
|
|
1212
|
+
ensureCompanyDir: vi.fn(),
|
|
1213
|
+
telemetryClient: null,
|
|
1214
|
+
});
|
|
1215
|
+
return { result, events };
|
|
1216
|
+
}
|
|
1217
|
+
finally {
|
|
1218
|
+
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
beforeEach(() => {
|
|
1222
|
+
clearContextCache();
|
|
1223
|
+
setEntityFetchSleepForTest(async () => undefined);
|
|
1224
|
+
});
|
|
1225
|
+
afterEach(() => {
|
|
1226
|
+
setEntityFetchSleepForTest(undefined);
|
|
1227
|
+
vi.unstubAllGlobals();
|
|
1228
|
+
});
|
|
1229
|
+
it("a 429 then success on retry yields a complete company sync", async () => {
|
|
1230
|
+
let entityCalls = 0;
|
|
1231
|
+
vi.stubGlobal("fetch", vi.fn(async (url) => {
|
|
1232
|
+
const u = String(url);
|
|
1233
|
+
if (u.includes(`/entity/${entity.uid}`)) {
|
|
1234
|
+
entityCalls += 1;
|
|
1235
|
+
if (entityCalls === 1) {
|
|
1236
|
+
return new Response(JSON.stringify({ message: "Too Many Requests" }), {
|
|
1237
|
+
status: 429,
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
return new Response(JSON.stringify({ entity }), { status: 200 });
|
|
1241
|
+
}
|
|
1242
|
+
if (u.includes("/sts/vend")) {
|
|
1243
|
+
return new Response(JSON.stringify(vend), { status: 200 });
|
|
1244
|
+
}
|
|
1245
|
+
return new Response("Not found", { status: 404 });
|
|
1246
|
+
}));
|
|
1247
|
+
const emptyPull = {
|
|
1248
|
+
filesDownloaded: 0,
|
|
1249
|
+
bytesDownloaded: 0,
|
|
1250
|
+
filesSkipped: 0,
|
|
1251
|
+
conflicts: 0,
|
|
1252
|
+
conflictPaths: [],
|
|
1253
|
+
aborted: false,
|
|
1254
|
+
newFiles: [],
|
|
1255
|
+
newFilesCount: 0,
|
|
1256
|
+
filesExcludedByPolicy: 0,
|
|
1257
|
+
filesTombstoned: 0,
|
|
1258
|
+
filesOutOfScope: 0,
|
|
1259
|
+
scopeOrphansRemoved: 0,
|
|
1260
|
+
scopeOrphansBlocked: 0,
|
|
1261
|
+
changedPaths: [],
|
|
1262
|
+
};
|
|
1263
|
+
const { result, events } = await runIndigoPull(async (opts) => {
|
|
1264
|
+
await resolveEntityContext(opts.company ?? entity.uid, opts.vaultConfig);
|
|
1265
|
+
return emptyPull;
|
|
1266
|
+
});
|
|
1267
|
+
expect(entityCalls).toBe(2);
|
|
1268
|
+
expect(result.errors).toEqual([]);
|
|
1269
|
+
expect(result.transientCompanies).toEqual([]);
|
|
1270
|
+
expect(result.stateByCompany.get("indigo")?.status).toBe("complete");
|
|
1271
|
+
expect(events.some((e) => e.type === "error")).toBe(false);
|
|
1272
|
+
expect(events.some((e) => e.type === "transient-network")).toBe(false);
|
|
1273
|
+
});
|
|
1274
|
+
});
|
|
1166
1275
|
describe("executeCompanyFanout — mid-pass identity refusal (HQ-DESKTOP-5S)", () => {
|
|
1167
1276
|
// A mid-pass Cognito refusal surfaces from the leg (share.ts/sync.ts now
|
|
1168
1277
|
// rethrow it instead of recording a per-file error). personalMode avoids the
|
|
@@ -1361,4 +1470,152 @@ describe("executeCompanyFanout — a company-skill 409 never aborts the leg (HQ-
|
|
|
1361
1470
|
expect(parseRegisteredCompanySkillUid(fs.readFileSync(skillFile, "utf8"))).toBe("skl_SOURCE1");
|
|
1362
1471
|
});
|
|
1363
1472
|
});
|
|
1473
|
+
describe("executeCompanyFanout — personal vault skips journaled company dirs", () => {
|
|
1474
|
+
const shareResult = {
|
|
1475
|
+
filesUploaded: 0,
|
|
1476
|
+
bytesUploaded: 0,
|
|
1477
|
+
filesSkipped: 0,
|
|
1478
|
+
filesDeleted: 0,
|
|
1479
|
+
filesTombstoned: 0,
|
|
1480
|
+
filesRefusedStale: 0,
|
|
1481
|
+
filesRefusedStalePaths: [],
|
|
1482
|
+
filesSuppressedByTombstone: 0,
|
|
1483
|
+
filesExcludedByPolicy: 0,
|
|
1484
|
+
filesExcludedByScope: 0,
|
|
1485
|
+
filesExcludedByIgnore: 0,
|
|
1486
|
+
unreachablePaths: [],
|
|
1487
|
+
linkedSubtreesNotShipped: [],
|
|
1488
|
+
conflictPaths: [],
|
|
1489
|
+
aborted: false,
|
|
1490
|
+
};
|
|
1491
|
+
const syncResult = {
|
|
1492
|
+
filesDownloaded: 0,
|
|
1493
|
+
bytesDownloaded: 0,
|
|
1494
|
+
filesSkipped: 0,
|
|
1495
|
+
conflicts: 0,
|
|
1496
|
+
conflictPaths: [],
|
|
1497
|
+
aborted: false,
|
|
1498
|
+
newFiles: [],
|
|
1499
|
+
newFilesCount: 0,
|
|
1500
|
+
filesExcludedByPolicy: 0,
|
|
1501
|
+
filesTombstoned: 0,
|
|
1502
|
+
filesOutOfScope: 0,
|
|
1503
|
+
scopeOrphansRemoved: 0,
|
|
1504
|
+
scopeOrphansBlocked: 0,
|
|
1505
|
+
changedPaths: [],
|
|
1506
|
+
};
|
|
1507
|
+
let hqRoot;
|
|
1508
|
+
let stateDir;
|
|
1509
|
+
let previousStateDir;
|
|
1510
|
+
beforeEach(() => {
|
|
1511
|
+
hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-fanout-journal-slugs-"));
|
|
1512
|
+
stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "hq-fanout-journal-state-"));
|
|
1513
|
+
previousStateDir = process.env.HQ_STATE_DIR;
|
|
1514
|
+
process.env.HQ_STATE_DIR = stateDir;
|
|
1515
|
+
fs.mkdirSync(path.join(hqRoot, "personal"), { recursive: true });
|
|
1516
|
+
});
|
|
1517
|
+
afterEach(() => {
|
|
1518
|
+
fs.rmSync(hqRoot, { recursive: true, force: true });
|
|
1519
|
+
fs.rmSync(stateDir, { recursive: true, force: true });
|
|
1520
|
+
if (previousStateDir === undefined)
|
|
1521
|
+
delete process.env.HQ_STATE_DIR;
|
|
1522
|
+
else
|
|
1523
|
+
process.env.HQ_STATE_DIR = previousStateDir;
|
|
1524
|
+
});
|
|
1525
|
+
function writeCompanyDir(slug) {
|
|
1526
|
+
fs.mkdirSync(path.join(hqRoot, "companies", slug), { recursive: true });
|
|
1527
|
+
}
|
|
1528
|
+
function writeJournalLocator(slug) {
|
|
1529
|
+
fs.writeFileSync(path.join(stateDir, `sync-journal.${slug}.json`), JSON.stringify({ schema: "hq-sync-journal-locator@1" }));
|
|
1530
|
+
}
|
|
1531
|
+
function relSharePaths(shareFn) {
|
|
1532
|
+
const call = shareFn.mock.calls[0];
|
|
1533
|
+
expect(call).toBeDefined();
|
|
1534
|
+
return call[0].paths.map((p) => path.relative(hqRoot, p)).sort();
|
|
1535
|
+
}
|
|
1536
|
+
async function runPersonalBoth(shareFn = vi.fn(async (_options) => shareResult), syncFn = vi.fn(async (_options) => syncResult)) {
|
|
1537
|
+
const events = [];
|
|
1538
|
+
await executeCompanyFanout({
|
|
1539
|
+
plan: [{ uid: "prs_1", slug: "personal", personalMode: true }],
|
|
1540
|
+
direction: "both",
|
|
1541
|
+
hqRoot,
|
|
1542
|
+
onConflict: "abort",
|
|
1543
|
+
client: {},
|
|
1544
|
+
vaultConfig: { apiUrl: "https://api.example", authToken: "access-token" },
|
|
1545
|
+
fetchFileTombstones: async () => new Map(),
|
|
1546
|
+
syncFn,
|
|
1547
|
+
shareFn,
|
|
1548
|
+
resolveDeletePolicy: () => "all",
|
|
1549
|
+
emit: (event) => events.push(event),
|
|
1550
|
+
ensureCompanyDir: vi.fn(),
|
|
1551
|
+
telemetryClient: null,
|
|
1552
|
+
});
|
|
1553
|
+
return { events, shareFn, syncFn };
|
|
1554
|
+
}
|
|
1555
|
+
it("excludes companies/dandy from the personal push when its sync journal exists and there is no manifest", async () => {
|
|
1556
|
+
writeCompanyDir("dandy");
|
|
1557
|
+
writeJournalLocator("dandy");
|
|
1558
|
+
const { events, shareFn, syncFn } = await runPersonalBoth();
|
|
1559
|
+
const rel = relSharePaths(shareFn);
|
|
1560
|
+
expect(rel).not.toContain(path.join("companies", "dandy"));
|
|
1561
|
+
expect(rel).toContain("personal");
|
|
1562
|
+
const shareArgs = shareFn.mock.calls[0][0];
|
|
1563
|
+
expect(shareArgs.decommissionPrefixes).toEqual(["companies/dandy"]);
|
|
1564
|
+
const pullArgs = syncFn.mock.calls[0][0];
|
|
1565
|
+
expect(Array.from(pullArgs.teamSyncedSlugs ?? [])).toEqual(["dandy"]);
|
|
1566
|
+
expect(events).toContainEqual({
|
|
1567
|
+
type: "error",
|
|
1568
|
+
diagnostic: true,
|
|
1569
|
+
company: "personal",
|
|
1570
|
+
path: "(personal-vault)",
|
|
1571
|
+
message: "excluding journaled companies from personal vault: dandy",
|
|
1572
|
+
});
|
|
1573
|
+
expect(events.some((e) => e.type === "error" && e.diagnostic !== true)).toBe(false);
|
|
1574
|
+
});
|
|
1575
|
+
it("still includes a local-only company that has no journal when there is no manifest", async () => {
|
|
1576
|
+
writeCompanyDir("local-only");
|
|
1577
|
+
const { events, shareFn, syncFn } = await runPersonalBoth();
|
|
1578
|
+
const rel = relSharePaths(shareFn);
|
|
1579
|
+
expect(rel).toContain(path.join("companies", "local-only"));
|
|
1580
|
+
expect(shareFn.mock.calls[0][0]).not.toHaveProperty("decommissionPrefixes");
|
|
1581
|
+
expect(Array.from(syncFn.mock.calls[0][0].teamSyncedSlugs ?? [])).toEqual([]);
|
|
1582
|
+
expect(events.some((e) => e.type === "error")).toBe(false);
|
|
1583
|
+
});
|
|
1584
|
+
it("does not treat the personal-vault sentinel journal as a company slug", async () => {
|
|
1585
|
+
writeCompanyDir("local-only");
|
|
1586
|
+
writeJournalLocator(PERSONAL_VAULT_JOURNAL_SLUG);
|
|
1587
|
+
const { events, shareFn, syncFn } = await runPersonalBoth();
|
|
1588
|
+
const rel = relSharePaths(shareFn);
|
|
1589
|
+
expect(rel).toContain(path.join("companies", "local-only"));
|
|
1590
|
+
expect(shareFn.mock.calls[0][0]).not.toHaveProperty("decommissionPrefixes");
|
|
1591
|
+
expect(Array.from(syncFn.mock.calls[0][0].teamSyncedSlugs ?? [])).toEqual([]);
|
|
1592
|
+
expect(events.some((e) => e.type === "error")).toBe(false);
|
|
1593
|
+
});
|
|
1594
|
+
it("does not pass journal-derived teamSyncedSlugs to a company target", async () => {
|
|
1595
|
+
writeCompanyDir("dandy");
|
|
1596
|
+
writeJournalLocator("dandy");
|
|
1597
|
+
const shareFn = vi.fn(async (_options) => shareResult);
|
|
1598
|
+
const syncFn = vi.fn(async (_options) => syncResult);
|
|
1599
|
+
await executeCompanyFanout({
|
|
1600
|
+
plan: [{ uid: "cmp_dandy", slug: "dandy" }],
|
|
1601
|
+
direction: "both",
|
|
1602
|
+
hqRoot,
|
|
1603
|
+
onConflict: "abort",
|
|
1604
|
+
client: {},
|
|
1605
|
+
vaultConfig: { apiUrl: "https://api.example", authToken: "access-token" },
|
|
1606
|
+
fetchFileTombstones: async () => new Map(),
|
|
1607
|
+
syncFn,
|
|
1608
|
+
shareFn,
|
|
1609
|
+
resolveDeletePolicy: () => "all",
|
|
1610
|
+
emit: vi.fn(),
|
|
1611
|
+
ensureCompanyDir: vi.fn(),
|
|
1612
|
+
telemetryClient: null,
|
|
1613
|
+
});
|
|
1614
|
+
expect(shareFn.mock.calls[0][0].paths).toEqual([
|
|
1615
|
+
path.join(hqRoot, "companies", "dandy"),
|
|
1616
|
+
]);
|
|
1617
|
+
expect(shareFn.mock.calls[0][0]).not.toHaveProperty("decommissionPrefixes");
|
|
1618
|
+
expect(Object.keys(syncFn.mock.calls[0][0])).not.toContain("teamSyncedSlugs");
|
|
1619
|
+
});
|
|
1620
|
+
});
|
|
1364
1621
|
//# sourceMappingURL=sync-runner-company.test.js.map
|