@indigoai-us/hq-cloud 6.14.16 → 6.14.18

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.
Files changed (69) hide show
  1. package/dist/active-company.d.ts +43 -0
  2. package/dist/active-company.d.ts.map +1 -0
  3. package/dist/active-company.js +132 -0
  4. package/dist/active-company.js.map +1 -0
  5. package/dist/active-company.test.d.ts +2 -0
  6. package/dist/active-company.test.d.ts.map +1 -0
  7. package/dist/active-company.test.js +149 -0
  8. package/dist/active-company.test.js.map +1 -0
  9. package/dist/bin/sync-runner-planning.d.ts +27 -1
  10. package/dist/bin/sync-runner-planning.d.ts.map +1 -1
  11. package/dist/bin/sync-runner-planning.js +33 -4
  12. package/dist/bin/sync-runner-planning.js.map +1 -1
  13. package/dist/bin/sync-runner-planning.test.d.ts +2 -0
  14. package/dist/bin/sync-runner-planning.test.d.ts.map +1 -0
  15. package/dist/bin/sync-runner-planning.test.js +115 -0
  16. package/dist/bin/sync-runner-planning.test.js.map +1 -0
  17. package/dist/bin/sync-runner.d.ts +22 -7
  18. package/dist/bin/sync-runner.d.ts.map +1 -1
  19. package/dist/bin/sync-runner.js +92 -16
  20. package/dist/bin/sync-runner.js.map +1 -1
  21. package/dist/bin/sync-runner.test.js +357 -5
  22. package/dist/bin/sync-runner.test.js.map +1 -1
  23. package/dist/cli/reindex.d.ts.map +1 -1
  24. package/dist/cli/reindex.js +127 -2
  25. package/dist/cli/reindex.js.map +1 -1
  26. package/dist/cli/reindex.test.js +88 -0
  27. package/dist/cli/reindex.test.js.map +1 -1
  28. package/dist/manifest-reconcile.d.ts +118 -5
  29. package/dist/manifest-reconcile.d.ts.map +1 -1
  30. package/dist/manifest-reconcile.js +319 -62
  31. package/dist/manifest-reconcile.js.map +1 -1
  32. package/dist/manifest-reconcile.test.js +824 -2
  33. package/dist/manifest-reconcile.test.js.map +1 -1
  34. package/dist/personal-vault-exclusions.d.ts +1 -6
  35. package/dist/personal-vault-exclusions.d.ts.map +1 -1
  36. package/dist/personal-vault-exclusions.js +34 -6
  37. package/dist/personal-vault-exclusions.js.map +1 -1
  38. package/dist/personal-vault-exclusions.test.js +22 -0
  39. package/dist/personal-vault-exclusions.test.js.map +1 -1
  40. package/dist/personal-vault.d.ts.map +1 -1
  41. package/dist/personal-vault.js +9 -1
  42. package/dist/personal-vault.js.map +1 -1
  43. package/dist/personal-vault.test.js +27 -1
  44. package/dist/personal-vault.test.js.map +1 -1
  45. package/dist/vault-client.d.ts +8 -1
  46. package/dist/vault-client.d.ts.map +1 -1
  47. package/dist/vault-client.js +29 -1
  48. package/dist/vault-client.js.map +1 -1
  49. package/dist/vault-client.test.js +10 -1
  50. package/dist/vault-client.test.js.map +1 -1
  51. package/package.json +1 -1
  52. package/pnpm-workspace.yaml +1 -1
  53. package/src/active-company.test.ts +188 -0
  54. package/src/active-company.ts +168 -0
  55. package/src/bin/sync-runner-planning.test.ts +131 -0
  56. package/src/bin/sync-runner-planning.ts +60 -7
  57. package/src/bin/sync-runner.test.ts +430 -10
  58. package/src/bin/sync-runner.ts +129 -23
  59. package/src/cli/reindex.test.ts +116 -0
  60. package/src/cli/reindex.ts +125 -2
  61. package/src/manifest-reconcile.test.ts +1019 -3
  62. package/src/manifest-reconcile.ts +424 -66
  63. package/src/personal-vault-exclusions.test.ts +24 -0
  64. package/src/personal-vault-exclusions.ts +35 -5
  65. package/src/personal-vault.test.ts +30 -0
  66. package/src/personal-vault.ts +9 -1
  67. package/src/vault-client.test.ts +12 -1
  68. package/src/vault-client.ts +44 -2
  69. package/test/joiner-manifest-reconcile.integration.test.ts +283 -0
@@ -3,7 +3,8 @@ import * as fs from "node:fs";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import yaml from "js-yaml";
6
- import { reconcileCompanyManifest } from "./manifest-reconcile.js";
6
+ import { reconcileCompanyManifest, reconcileManifest, } from "./manifest-reconcile.js";
7
+ import { VaultNotFoundError, VaultPermissionDeniedError, } from "./vault-client.js";
7
8
  let hqRoot;
8
9
  afterEach(() => {
9
10
  if (hqRoot) {
@@ -11,6 +12,448 @@ afterEach(() => {
11
12
  hqRoot = undefined;
12
13
  }
13
14
  });
15
+ /** The stock manifest shipped to a fresh HQ root: header comment, no companies. */
16
+ const EMPTY_MANIFEST_TEMPLATE = `# HQ company manifest.
17
+ # Source of truth for which company slugs this HQ root routes.
18
+ # Edited by humans; reconciled by hq-sync. Keep this header.
19
+ companies: {}
20
+ `;
21
+ /** Far enough in the past that any rewrite is unmistakable in the mtime. */
22
+ const FROZEN_MTIME = new Date("2020-01-01T00:00:00.000Z");
23
+ function makeHqRoot() {
24
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "hq-manifest-reconcile-"));
25
+ fs.mkdirSync(path.join(root, "companies"), { recursive: true });
26
+ return root;
27
+ }
28
+ function manifestPathFor(root) {
29
+ return path.join(root, "companies", "manifest.yaml");
30
+ }
31
+ function writeManifest(root, contents) {
32
+ const manifestPath = manifestPathFor(root);
33
+ fs.writeFileSync(manifestPath, contents, "utf8");
34
+ fs.utimesSync(manifestPath, FROZEN_MTIME, FROZEN_MTIME);
35
+ return manifestPath;
36
+ }
37
+ function readCompanies(manifestPath) {
38
+ const parsed = yaml.load(fs.readFileSync(manifestPath, "utf8"));
39
+ return parsed.companies ?? {};
40
+ }
41
+ /** Asserts the file was not touched at all — contents AND mtime. */
42
+ function expectUntouched(manifestPath, before) {
43
+ expect(fs.readFileSync(manifestPath, "utf8")).toBe(before);
44
+ expect(fs.statSync(manifestPath).mtimeMs).toBe(FROZEN_MTIME.getTime());
45
+ }
46
+ describe("reconcileManifest", () => {
47
+ it("inserts a company into the stock empty manifest template", () => {
48
+ hqRoot = makeHqRoot();
49
+ const manifestPath = writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
50
+ const result = reconcileManifest(hqRoot, [
51
+ {
52
+ slug: "acme",
53
+ uid: "cmp_acme",
54
+ name: "Acme Inc",
55
+ bucketName: "hq-vault-cmp-acme",
56
+ },
57
+ ]);
58
+ expect(result).toEqual({
59
+ written: true,
60
+ added: ["acme"],
61
+ updated: [],
62
+ skipped: [],
63
+ });
64
+ expect(readCompanies(manifestPath)).toEqual({
65
+ acme: {
66
+ cloud_uid: "cmp_acme",
67
+ name: "Acme Inc",
68
+ bucket_name: "hq-vault-cmp-acme",
69
+ },
70
+ });
71
+ });
72
+ it("inserts alongside an existing company without disturbing it", () => {
73
+ hqRoot = makeHqRoot();
74
+ const manifestPath = writeManifest(hqRoot, `version: 3\ncompanies:\n incumbent:\n name: Incumbent\n cloud_uid: cmp_incumbent\n bucket_name: hq-vault-cmp-incumbent\n`);
75
+ const result = reconcileManifest(hqRoot, [
76
+ { slug: "joiner", uid: "cmp_joiner", name: "Joiner", bucketName: "hq-vault-cmp-joiner" },
77
+ ]);
78
+ expect(result.written).toBe(true);
79
+ expect(result.added).toEqual(["joiner"]);
80
+ expect(result.updated).toEqual([]);
81
+ expect(readCompanies(manifestPath)).toEqual({
82
+ incumbent: {
83
+ name: "Incumbent",
84
+ cloud_uid: "cmp_incumbent",
85
+ bucket_name: "hq-vault-cmp-incumbent",
86
+ },
87
+ joiner: {
88
+ cloud_uid: "cmp_joiner",
89
+ name: "Joiner",
90
+ bucket_name: "hq-vault-cmp-joiner",
91
+ },
92
+ });
93
+ // Unrelated top-level state survives the merge.
94
+ expect(yaml.load(fs.readFileSync(manifestPath, "utf8")).version).toBe(3);
95
+ });
96
+ it("performs no write at all when every target is already current", () => {
97
+ hqRoot = makeHqRoot();
98
+ // The header comment is the canary: js-yaml cannot round-trip comments, so
99
+ // any rewrite would silently drop it and churn the personal-vault copy.
100
+ const contents = `# Keep this header comment.\ncompanies:\n acme:\n name: Acme Inc\n cloud_uid: cmp_acme\n bucket_name: hq-vault-cmp-acme\n`;
101
+ const manifestPath = writeManifest(hqRoot, contents);
102
+ const result = reconcileManifest(hqRoot, [
103
+ { slug: "acme", uid: "cmp_acme", name: "Acme Inc", bucketName: "hq-vault-cmp-acme" },
104
+ ]);
105
+ expect(result).toEqual({ written: false, added: [], updated: [], skipped: [] });
106
+ expectUntouched(manifestPath, contents);
107
+ expect(fs.readFileSync(manifestPath, "utf8")).toContain("# Keep this header comment.");
108
+ });
109
+ it("reports a partial drift as an update and leaves untouched fields alone", () => {
110
+ hqRoot = makeHqRoot();
111
+ const manifestPath = writeManifest(hqRoot, `companies:\n acme:\n name: Acme Inc\n cloud_uid: cmp_acme\n bucket_name: hq-vault-stale\n`);
112
+ const result = reconcileManifest(hqRoot, [
113
+ { slug: "acme", uid: "cmp_acme", name: "Acme Inc", bucketName: "hq-vault-cmp-acme" },
114
+ ]);
115
+ expect(result).toEqual({
116
+ written: true,
117
+ added: [],
118
+ updated: ["acme"],
119
+ skipped: [],
120
+ });
121
+ expect(readCompanies(manifestPath).acme).toEqual({
122
+ name: "Acme Inc",
123
+ cloud_uid: "cmp_acme",
124
+ bucket_name: "hq-vault-cmp-acme",
125
+ });
126
+ });
127
+ it("preserves unknown keys on both the untouched and the updated company", () => {
128
+ hqRoot = makeHqRoot();
129
+ const manifestPath = writeManifest(hqRoot, [
130
+ "companies:",
131
+ " bystander:",
132
+ " name: Bystander",
133
+ " cloud_uid: cmp_bystander",
134
+ " repos:",
135
+ " - repos/private/bystander",
136
+ " workers:",
137
+ " - designer",
138
+ " qmd_collections:",
139
+ " - bystander-docs",
140
+ " acme:",
141
+ " name: Acme Inc",
142
+ " cloud_uid: cmp_acme",
143
+ " bucket_name: hq-vault-stale",
144
+ " repos:",
145
+ " - repos/private/acme",
146
+ " workers:",
147
+ " - backend-dev",
148
+ " qmd_collections:",
149
+ " - acme-docs",
150
+ "",
151
+ ].join("\n"));
152
+ const result = reconcileManifest(hqRoot, [
153
+ { slug: "acme", uid: "cmp_acme", name: "Acme Inc", bucketName: "hq-vault-cmp-acme" },
154
+ ]);
155
+ expect(result.updated).toEqual(["acme"]);
156
+ const companies = readCompanies(manifestPath);
157
+ expect(companies.bystander).toEqual({
158
+ name: "Bystander",
159
+ cloud_uid: "cmp_bystander",
160
+ repos: ["repos/private/bystander"],
161
+ workers: ["designer"],
162
+ qmd_collections: ["bystander-docs"],
163
+ });
164
+ expect(companies.acme).toEqual({
165
+ name: "Acme Inc",
166
+ cloud_uid: "cmp_acme",
167
+ bucket_name: "hq-vault-cmp-acme",
168
+ repos: ["repos/private/acme"],
169
+ workers: ["backend-dev"],
170
+ qmd_collections: ["acme-docs"],
171
+ });
172
+ });
173
+ it("no-ops without throwing when the manifest cannot be parsed", () => {
174
+ hqRoot = makeHqRoot();
175
+ const contents = "companies:\n acme:\n - this: is\n not: valid yaml\n\t\tbad indent\n";
176
+ const manifestPath = writeManifest(hqRoot, contents);
177
+ let result;
178
+ expect(() => {
179
+ result = reconcileManifest(hqRoot, [
180
+ { slug: "acme", uid: "cmp_acme", name: "Acme Inc", bucketName: "hq-vault-cmp-acme" },
181
+ ]);
182
+ }).not.toThrow();
183
+ expect(result).toEqual({ written: false, added: [], updated: [], skipped: [] });
184
+ expectUntouched(manifestPath, contents);
185
+ });
186
+ it("no-ops when the manifest is missing and never creates one", () => {
187
+ hqRoot = makeHqRoot();
188
+ const manifestPath = manifestPathFor(hqRoot);
189
+ expect(fs.existsSync(manifestPath)).toBe(false);
190
+ const result = reconcileManifest(hqRoot, [
191
+ { slug: "acme", uid: "cmp_acme", name: "Acme Inc", bucketName: "hq-vault-cmp-acme" },
192
+ ]);
193
+ expect(result).toEqual({ written: false, added: [], updated: [], skipped: [] });
194
+ expect(fs.existsSync(manifestPath)).toBe(false);
195
+ });
196
+ it("no-ops when `companies` is present but is not a mapping", () => {
197
+ hqRoot = makeHqRoot();
198
+ const contents = "companies:\n - acme\n - beta\n";
199
+ const manifestPath = writeManifest(hqRoot, contents);
200
+ const result = reconcileManifest(hqRoot, [
201
+ { slug: "acme", uid: "cmp_acme", name: "Acme Inc", bucketName: "hq-vault-cmp-acme" },
202
+ ]);
203
+ expect(result.written).toBe(false);
204
+ expectUntouched(manifestPath, contents);
205
+ });
206
+ it("skips a slug whose existing entry is not a mapping", () => {
207
+ hqRoot = makeHqRoot();
208
+ const contents = "companies:\n acme: not-a-mapping\n";
209
+ const manifestPath = writeManifest(hqRoot, contents);
210
+ const result = reconcileManifest(hqRoot, [
211
+ { slug: "acme", uid: "cmp_acme", name: "Acme Inc", bucketName: "hq-vault-cmp-acme" },
212
+ ]);
213
+ expect(result).toEqual({ written: false, added: [], updated: [], skipped: ["acme"] });
214
+ expectUntouched(manifestPath, contents);
215
+ });
216
+ describe("path traversal guard", () => {
217
+ const unsafeSlugs = [
218
+ ["a forward-slash path", "nested/evil"],
219
+ ["a backslash path", "nested\\evil"],
220
+ ["the current directory", "."],
221
+ ["the parent directory", ".."],
222
+ ["a slug resolving outside the companies dir", "../escape"],
223
+ ["an empty slug", ""],
224
+ ["the __proto__ prototype key", "__proto__"],
225
+ ["the constructor prototype key", "constructor"],
226
+ ["the prototype prototype key", "prototype"],
227
+ ];
228
+ for (const [label, slug] of unsafeSlugs) {
229
+ it(`rejects ${label} without writing`, () => {
230
+ hqRoot = makeHqRoot();
231
+ const contents = EMPTY_MANIFEST_TEMPLATE;
232
+ const manifestPath = writeManifest(hqRoot, contents);
233
+ const result = reconcileManifest(hqRoot, [
234
+ { slug, uid: "cmp_evil", name: "Evil", bucketName: "hq-vault-cmp-evil" },
235
+ ]);
236
+ expect(result.skipped).toEqual([slug]);
237
+ expect(result.written).toBe(false);
238
+ expect(result.added).toEqual([]);
239
+ expect(result.updated).toEqual([]);
240
+ expectUntouched(manifestPath, contents);
241
+ expect(readCompanies(manifestPath)).toEqual({});
242
+ });
243
+ }
244
+ });
245
+ it("does not fall into a write loop on a __proto__ slug", () => {
246
+ // Regression: `__proto__` read back as Object.prototype, so it passed the
247
+ // non-mapping skip, always reported drift, and never persisted through
248
+ // yaml.dump — a rewrite on every run of a vault-synced file, forever.
249
+ hqRoot = makeHqRoot();
250
+ const contents = EMPTY_MANIFEST_TEMPLATE;
251
+ const manifestPath = writeManifest(hqRoot, contents);
252
+ const targets = [{ slug: "__proto__", uid: "cmp_evil", name: "Evil" }];
253
+ for (let run = 0; run < 3; run += 1) {
254
+ const result = reconcileManifest(hqRoot, targets);
255
+ expect(result.written).toBe(false);
256
+ expect(result.skipped).toEqual(["__proto__"]);
257
+ expectUntouched(manifestPath, contents);
258
+ }
259
+ // The prototype itself must be clean — no pollution leaked out.
260
+ expect({}.cloud_uid).toBeUndefined();
261
+ expect(readCompanies(manifestPath)).toEqual({});
262
+ });
263
+ it("no-ops when `companies` is a timestamp scalar rather than a mapping", () => {
264
+ // js-yaml parses this into a Date, which a `typeof value === "object"`
265
+ // check would have accepted as the company map.
266
+ hqRoot = makeHqRoot();
267
+ const contents = "companies: 2026-01-01\n";
268
+ const manifestPath = writeManifest(hqRoot, contents);
269
+ const result = reconcileManifest(hqRoot, [
270
+ { slug: "acme", uid: "cmp_acme", name: "Acme Inc", bucketName: "hq-vault-cmp-acme" },
271
+ ]);
272
+ expect(result).toEqual({ written: false, added: [], updated: [], skipped: [] });
273
+ expectUntouched(manifestPath, contents);
274
+ });
275
+ it("no-ops when `companies` is a plain string scalar", () => {
276
+ hqRoot = makeHqRoot();
277
+ const contents = "companies: nonsense\n";
278
+ const manifestPath = writeManifest(hqRoot, contents);
279
+ expect(reconcileManifest(hqRoot, [{ slug: "acme", uid: "cmp_acme" }]).written).toBe(false);
280
+ expectUntouched(manifestPath, contents);
281
+ });
282
+ it("skips a timestamp entry without destroying its value", () => {
283
+ // A Date entry used to slip past the non-mapping skip and then be silently
284
+ // flattened to {} by the object spread.
285
+ hqRoot = makeHqRoot();
286
+ const contents = "companies:\n acme: 2026-01-01\n";
287
+ const manifestPath = writeManifest(hqRoot, contents);
288
+ const result = reconcileManifest(hqRoot, [
289
+ { slug: "acme", uid: "cmp_acme", name: "Acme Inc", bucketName: "hq-vault-cmp-acme" },
290
+ ]);
291
+ expect(result).toEqual({ written: false, added: [], updated: [], skipped: ["acme"] });
292
+ expectUntouched(manifestPath, contents);
293
+ });
294
+ it("treats a bare `companies:` key as an empty map and inserts into it", () => {
295
+ hqRoot = makeHqRoot();
296
+ const manifestPath = writeManifest(hqRoot, "version: 3\ncompanies:\n");
297
+ const result = reconcileManifest(hqRoot, [{ slug: "acme", uid: "cmp_acme" }]);
298
+ expect(result.written).toBe(true);
299
+ expect(result.added).toEqual(["acme"]);
300
+ expect(readCompanies(manifestPath)).toEqual({ acme: { cloud_uid: "cmp_acme" } });
301
+ });
302
+ it("does not write when duplicate slugs produce no net change", () => {
303
+ // Regression: a sticky change flag was flipped on by the first target and
304
+ // back off by the second, leaving it true while content was identical.
305
+ hqRoot = makeHqRoot();
306
+ const contents = "companies:\n acme:\n cloud_uid: cmp_b\n";
307
+ const manifestPath = writeManifest(hqRoot, contents);
308
+ const result = reconcileManifest(hqRoot, [
309
+ { slug: "acme", uid: "cmp_a" },
310
+ { slug: "acme", uid: "cmp_b" },
311
+ ]);
312
+ expect(result.written).toBe(false);
313
+ expect(result.updated).toEqual([]);
314
+ expectUntouched(manifestPath, contents);
315
+ });
316
+ it("applies last-wins for duplicate slugs that do change the file", () => {
317
+ hqRoot = makeHqRoot();
318
+ const manifestPath = writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
319
+ const result = reconcileManifest(hqRoot, [
320
+ { slug: "acme", uid: "cmp_a" },
321
+ { slug: "acme", uid: "cmp_b" },
322
+ ]);
323
+ expect(result.written).toBe(true);
324
+ expect(result.added).toEqual(["acme"]);
325
+ expect(readCompanies(manifestPath)).toEqual({ acme: { cloud_uid: "cmp_b" } });
326
+ });
327
+ it("leaves no temp file behind when the write fails", () => {
328
+ hqRoot = makeHqRoot();
329
+ const companiesDir = path.join(hqRoot, "companies");
330
+ writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
331
+ // Read-only directory: the temp write itself fails, and on platforms where
332
+ // it does not, the rename over the target does.
333
+ fs.chmodSync(companiesDir, 0o500);
334
+ try {
335
+ expect(() => reconcileManifest(hqRoot, [{ slug: "acme", uid: "cmp_acme" }])).toThrow();
336
+ const strays = fs.readdirSync(companiesDir).filter((n) => n.startsWith(".manifest-"));
337
+ expect(strays).toEqual([]);
338
+ }
339
+ finally {
340
+ fs.chmodSync(companiesDir, 0o700);
341
+ }
342
+ });
343
+ it("documents that the first genuine write drops the manifest header comment", () => {
344
+ // yaml.dump cannot round-trip comments. This loss is accepted and is exactly
345
+ // why skip-if-unchanged must prevent every needless rewrite.
346
+ hqRoot = makeHqRoot();
347
+ const manifestPath = writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
348
+ expect(fs.readFileSync(manifestPath, "utf8")).toContain("# HQ company manifest.");
349
+ expect(reconcileManifest(hqRoot, [{ slug: "acme", uid: "cmp_acme" }]).written).toBe(true);
350
+ const after = fs.readFileSync(manifestPath, "utf8");
351
+ expect(after).not.toContain("# HQ company manifest.");
352
+ // ...and the very next run must not touch it again.
353
+ const replay = reconcileManifest(hqRoot, [{ slug: "acme", uid: "cmp_acme" }]);
354
+ expect(replay.written).toBe(false);
355
+ expect(fs.readFileSync(manifestPath, "utf8")).toBe(after);
356
+ });
357
+ it("omits bucket_name entirely when the target has no bucket", () => {
358
+ hqRoot = makeHqRoot();
359
+ const manifestPath = writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
360
+ const result = reconcileManifest(hqRoot, [
361
+ { slug: "joiner", uid: "cmp_joiner", name: "Joiner Inc" },
362
+ ]);
363
+ expect(result.added).toEqual(["joiner"]);
364
+ const entry = readCompanies(manifestPath).joiner;
365
+ expect("bucket_name" in entry).toBe(false);
366
+ expect(entry).toEqual({ cloud_uid: "cmp_joiner", name: "Joiner Inc" });
367
+ expect(fs.readFileSync(manifestPath, "utf8")).not.toContain("bucket_name");
368
+ });
369
+ it("omits name entirely when the target has no name", () => {
370
+ hqRoot = makeHqRoot();
371
+ const manifestPath = writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
372
+ const result = reconcileManifest(hqRoot, [
373
+ { slug: "joiner", uid: "cmp_joiner", bucketName: "hq-vault-cmp-joiner" },
374
+ ]);
375
+ expect(result.added).toEqual(["joiner"]);
376
+ const entry = readCompanies(manifestPath).joiner;
377
+ expect("name" in entry).toBe(false);
378
+ expect(entry).toEqual({ cloud_uid: "cmp_joiner", bucket_name: "hq-vault-cmp-joiner" });
379
+ // No `name:` key line at all (`bucket_name:` must not satisfy this).
380
+ expect(fs.readFileSync(manifestPath, "utf8")).not.toMatch(/^\s*name:/m);
381
+ });
382
+ it("treats empty-string name and bucket as absent rather than writing blanks", () => {
383
+ hqRoot = makeHqRoot();
384
+ const manifestPath = writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
385
+ const result = reconcileManifest(hqRoot, [
386
+ { slug: "joiner", uid: "cmp_joiner", name: "", bucketName: "" },
387
+ ]);
388
+ expect(result).toEqual({
389
+ written: true,
390
+ added: ["joiner"],
391
+ updated: [],
392
+ skipped: [],
393
+ });
394
+ const entry = readCompanies(manifestPath).joiner;
395
+ expect("name" in entry).toBe(false);
396
+ expect("bucket_name" in entry).toBe(false);
397
+ expect(entry).toEqual({ cloud_uid: "cmp_joiner" });
398
+ // An empty-string field must not read as drift on the replay, or the
399
+ // manifest would be rewritten on every run.
400
+ fs.utimesSync(manifestPath, FROZEN_MTIME, FROZEN_MTIME);
401
+ const replay = reconcileManifest(hqRoot, [
402
+ { slug: "joiner", uid: "cmp_joiner", name: "", bucketName: "" },
403
+ ]);
404
+ expect(replay.written).toBe(false);
405
+ expect(fs.statSync(manifestPath).mtimeMs).toBe(FROZEN_MTIME.getTime());
406
+ });
407
+ it("still writes cloud_uid for a company that has neither name nor bucket", () => {
408
+ hqRoot = makeHqRoot();
409
+ const manifestPath = writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
410
+ const result = reconcileManifest(hqRoot, [{ slug: "joiner", uid: "cmp_joiner" }]);
411
+ expect(result.written).toBe(true);
412
+ expect(result.added).toEqual(["joiner"]);
413
+ expect(readCompanies(manifestPath).joiner).toEqual({ cloud_uid: "cmp_joiner" });
414
+ });
415
+ it("never deletes an existing key just because the target omits it", () => {
416
+ hqRoot = makeHqRoot();
417
+ const manifestPath = writeManifest(hqRoot, `companies:\n acme:\n name: Acme Inc\n cloud_uid: cmp_old\n bucket_name: hq-vault-cmp-acme\n`);
418
+ const result = reconcileManifest(hqRoot, [{ slug: "acme", uid: "cmp_acme" }]);
419
+ expect(result.updated).toEqual(["acme"]);
420
+ expect(readCompanies(manifestPath).acme).toEqual({
421
+ name: "Acme Inc",
422
+ cloud_uid: "cmp_acme",
423
+ bucket_name: "hq-vault-cmp-acme",
424
+ });
425
+ });
426
+ it("reports adds, updates and skips together in one pass", () => {
427
+ hqRoot = makeHqRoot();
428
+ const manifestPath = writeManifest(hqRoot, [
429
+ "companies:",
430
+ " current:",
431
+ " cloud_uid: cmp_current",
432
+ " drifted:",
433
+ " cloud_uid: cmp_stale",
434
+ " broken: not-a-mapping",
435
+ "",
436
+ ].join("\n"));
437
+ const result = reconcileManifest(hqRoot, [
438
+ { slug: "fresh", uid: "cmp_fresh" },
439
+ { slug: "drifted", uid: "cmp_drifted" },
440
+ { slug: "current", uid: "cmp_current" },
441
+ { slug: "broken", uid: "cmp_broken" },
442
+ { slug: "../escape", uid: "cmp_escape" },
443
+ ]);
444
+ expect(result).toEqual({
445
+ written: true,
446
+ added: ["fresh"],
447
+ updated: ["drifted"],
448
+ skipped: ["../escape", "broken"],
449
+ });
450
+ const companies = readCompanies(manifestPath);
451
+ expect(companies.current).toEqual({ cloud_uid: "cmp_current" });
452
+ expect(companies.drifted).toEqual({ cloud_uid: "cmp_drifted" });
453
+ expect(companies.fresh).toEqual({ cloud_uid: "cmp_fresh" });
454
+ expect(companies.broken).toBe("not-a-mapping");
455
+ });
456
+ });
14
457
  describe("reconcileCompanyManifest", () => {
15
458
  it("atomically restores one successful cloud entry after the personal leg without replacing existing manifest state", async () => {
16
459
  hqRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-manifest-reconcile-"));
@@ -71,8 +514,14 @@ describe("reconcileCompanyManifest", () => {
71
514
  };
72
515
  await reconcileCompanyManifest(options);
73
516
  const firstWrite = fs.readFileSync(manifestPath, "utf8");
74
- await reconcileCompanyManifest(options);
517
+ // Freeze the mtime so the replay cannot pass by coincidence: identical bytes
518
+ // written a second time would still bump mtime, and mtime is what the sync
519
+ // layer keys on, so a byte comparison alone would miss a real conflict loop.
520
+ fs.utimesSync(manifestPath, FROZEN_MTIME, FROZEN_MTIME);
521
+ const replay = await reconcileCompanyManifest(options);
522
+ expect(replay.written).toBe(false);
75
523
  expect(fs.readFileSync(manifestPath, "utf8")).toBe(firstWrite);
524
+ expect(fs.statSync(manifestPath).mtimeMs).toBe(FROZEN_MTIME.getTime());
76
525
  expect(yaml.load(firstWrite)).toEqual({
77
526
  version: 3,
78
527
  settings: { keep: true },
@@ -93,5 +542,378 @@ describe("reconcileCompanyManifest", () => {
93
542
  },
94
543
  });
95
544
  });
545
+ it("returns the reconcile result on the insert path and again on the no-op replay", async () => {
546
+ hqRoot = makeHqRoot();
547
+ fs.mkdirSync(path.join(hqRoot, "companies", "acme"), { recursive: true });
548
+ writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
549
+ const options = {
550
+ hqRoot,
551
+ targets: [{ uid: "cmp_acme", slug: "acme" }],
552
+ completedCompanySlugs: new Set(["acme"]),
553
+ getEntity: async () => ({
554
+ uid: "cmp_acme",
555
+ slug: "acme",
556
+ type: "company",
557
+ status: "active",
558
+ name: "Acme Inc",
559
+ bucketName: "hq-vault-cmp-acme",
560
+ createdAt: "2026-01-01T00:00:00Z",
561
+ }),
562
+ };
563
+ expect(await reconcileCompanyManifest(options)).toEqual({
564
+ written: true,
565
+ added: ["acme"],
566
+ updated: [],
567
+ skipped: [],
568
+ });
569
+ expect(await reconcileCompanyManifest(options)).toEqual({
570
+ written: false,
571
+ added: [],
572
+ updated: [],
573
+ skipped: [],
574
+ });
575
+ });
576
+ it("returns an empty result when no target is eligible", async () => {
577
+ hqRoot = makeHqRoot();
578
+ writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
579
+ expect(await reconcileCompanyManifest({
580
+ hqRoot,
581
+ targets: [{ uid: "prs_me", slug: "personal", personalMode: true }],
582
+ completedCompanySlugs: new Set(["personal"]),
583
+ getEntity: async () => {
584
+ throw new Error("should not be called");
585
+ },
586
+ })).toEqual({ written: false, added: [], updated: [], skipped: [] });
587
+ });
588
+ it("keeps a company that has no name or bucket instead of dropping it", async () => {
589
+ hqRoot = makeHqRoot();
590
+ fs.mkdirSync(path.join(hqRoot, "companies", "joiner"), { recursive: true });
591
+ const manifestPath = writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
592
+ const options = {
593
+ hqRoot,
594
+ targets: [{ uid: "cmp_joiner", slug: "joiner" }],
595
+ completedCompanySlugs: new Set(["joiner"]),
596
+ getEntity: async () => ({
597
+ uid: "cmp_joiner",
598
+ slug: "joiner",
599
+ type: "company",
600
+ status: "active",
601
+ createdAt: "2026-01-01T00:00:00Z",
602
+ }),
603
+ };
604
+ const result = await reconcileCompanyManifest(options);
605
+ expect(result.written).toBe(true);
606
+ expect(result.added).toEqual(["joiner"]);
607
+ const entry = readCompanies(manifestPath).joiner;
608
+ expect(entry).toEqual({ cloud_uid: "cmp_joiner" });
609
+ expect("name" in entry).toBe(false);
610
+ expect("bucket_name" in entry).toBe(false);
611
+ // The highest-risk idempotency property: a target missing both optional
612
+ // fields must not re-report drift forever.
613
+ const after = fs.readFileSync(manifestPath, "utf8");
614
+ const mtime = fs.statSync(manifestPath).mtimeMs;
615
+ expect((await reconcileCompanyManifest(options)).written).toBe(false);
616
+ expect((await reconcileCompanyManifest(options)).written).toBe(false);
617
+ expect(fs.readFileSync(manifestPath, "utf8")).toBe(after);
618
+ expect(fs.statSync(manifestPath).mtimeMs).toBe(mtime);
619
+ });
620
+ // -------------------------------------------------------------------------
621
+ // US-002 — plan-carried path (no `getEntity`). This is the shape the
622
+ // sync-runner uses: `buildFanoutPlan` already resolved the entity, so the
623
+ // reconciler makes zero API calls and reads name/bucketName off the target.
624
+ // -------------------------------------------------------------------------
625
+ it("without getEntity: writes entries straight from the plan-carried target fields", async () => {
626
+ hqRoot = makeHqRoot();
627
+ fs.mkdirSync(path.join(hqRoot, "companies", "acme"), { recursive: true });
628
+ const manifestPath = writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
629
+ const options = {
630
+ hqRoot,
631
+ targets: [
632
+ {
633
+ uid: "cmp_acme",
634
+ slug: "acme",
635
+ name: "Acme Inc",
636
+ bucketName: "hq-vault-cmp-acme",
637
+ entityType: "company",
638
+ entityStatus: "active",
639
+ },
640
+ ],
641
+ completedCompanySlugs: new Set(["acme"]),
642
+ };
643
+ expect(await reconcileCompanyManifest(options)).toEqual({
644
+ written: true,
645
+ added: ["acme"],
646
+ updated: [],
647
+ skipped: [],
648
+ });
649
+ expect(readCompanies(manifestPath).acme).toEqual({
650
+ cloud_uid: "cmp_acme",
651
+ name: "Acme Inc",
652
+ bucket_name: "hq-vault-cmp-acme",
653
+ });
654
+ // Same idempotency contract as the getEntity path.
655
+ const after = fs.readFileSync(manifestPath, "utf8");
656
+ fs.utimesSync(manifestPath, FROZEN_MTIME, FROZEN_MTIME);
657
+ expect((await reconcileCompanyManifest(options)).written).toBe(false);
658
+ expectUntouched(manifestPath, after);
659
+ });
660
+ it("without getEntity: still writes a target that carries neither name nor bucketName", async () => {
661
+ hqRoot = makeHqRoot();
662
+ fs.mkdirSync(path.join(hqRoot, "companies", "joiner"), { recursive: true });
663
+ const manifestPath = writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
664
+ const result = await reconcileCompanyManifest({
665
+ hqRoot,
666
+ targets: [
667
+ {
668
+ uid: "cmp_joiner",
669
+ slug: "joiner",
670
+ entityType: "company",
671
+ entityStatus: "active",
672
+ },
673
+ ],
674
+ completedCompanySlugs: new Set(["joiner"]),
675
+ });
676
+ expect(result.written).toBe(true);
677
+ expect(readCompanies(manifestPath).joiner).toEqual({ cloud_uid: "cmp_joiner" });
678
+ });
679
+ it("without getEntity: keeps every eligibility filter — personal, incomplete, and unmaterialized targets are skipped", async () => {
680
+ hqRoot = makeHqRoot();
681
+ fs.mkdirSync(path.join(hqRoot, "companies", "acme"), { recursive: true });
682
+ fs.mkdirSync(path.join(hqRoot, "companies", "incomplete"), { recursive: true });
683
+ const manifestPath = writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
684
+ const result = await reconcileCompanyManifest({
685
+ hqRoot,
686
+ targets: [
687
+ {
688
+ uid: "cmp_acme",
689
+ slug: "acme",
690
+ bucketName: "hq-vault-cmp-acme",
691
+ entityType: "company",
692
+ entityStatus: "active",
693
+ },
694
+ // Completed and live, but never materialized a directory on disk.
695
+ {
696
+ uid: "cmp_ghost",
697
+ slug: "ghost",
698
+ bucketName: "hq-vault-cmp-ghost",
699
+ entityType: "company",
700
+ entityStatus: "active",
701
+ },
702
+ // On disk and live, but its fanout leg did not complete.
703
+ {
704
+ uid: "cmp_incomplete",
705
+ slug: "incomplete",
706
+ entityType: "company",
707
+ entityStatus: "active",
708
+ },
709
+ // Personal vault — never a manifest entry, even if it completed.
710
+ { uid: "prs_me", slug: "personal", personalMode: true },
711
+ ],
712
+ completedCompanySlugs: new Set(["acme", "ghost", "personal"]),
713
+ });
714
+ expect(result).toEqual({
715
+ written: true,
716
+ added: ["acme"],
717
+ updated: [],
718
+ skipped: [],
719
+ });
720
+ expect(Object.keys(readCompanies(manifestPath))).toEqual(["acme"]);
721
+ });
722
+ // Removing the per-target `entity.get` round trip (US-002) must not remove the
723
+ // liveness GUARANTEE it provided. Without an entity resolver the only evidence
724
+ // left is what the plan captured, so a target that cannot prove it was an
725
+ // active company at plan time must fail closed.
726
+ // Entity-probe failures all skip the target, but they are not all equally
727
+ // routine. "The company is gone" is expected; "the resolver is broken" is the
728
+ // failure mode that would otherwise masquerade as "joiners never get manifest
729
+ // entries" — the exact bug this module exists to prevent.
730
+ it("stays silent when a probe fails for the ordinary reasons (404 / 403)", async () => {
731
+ hqRoot = makeHqRoot();
732
+ fs.mkdirSync(path.join(hqRoot, "companies", "gone"), { recursive: true });
733
+ fs.mkdirSync(path.join(hqRoot, "companies", "forbidden"), { recursive: true });
734
+ const manifestPath = writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
735
+ const diagnostics = [];
736
+ const result = await reconcileCompanyManifest({
737
+ hqRoot,
738
+ targets: [
739
+ { uid: "cmp_gone", slug: "gone" },
740
+ { uid: "cmp_forbidden", slug: "forbidden" },
741
+ ],
742
+ completedCompanySlugs: new Set(["gone", "forbidden"]),
743
+ getEntity: (uid) => uid === "cmp_gone"
744
+ ? Promise.reject(new VaultNotFoundError("company deleted"))
745
+ : Promise.reject(new VaultPermissionDeniedError("no longer a member")),
746
+ reportDiagnostic: (d) => diagnostics.push(d),
747
+ });
748
+ expect(result).toEqual({ written: false, added: [], updated: [], skipped: [] });
749
+ expect(diagnostics).toEqual([]);
750
+ expectUntouched(manifestPath, EMPTY_MANIFEST_TEMPLATE);
751
+ });
752
+ it("reports an unexpected probe failure instead of silently skipping the target", async () => {
753
+ hqRoot = makeHqRoot();
754
+ fs.mkdirSync(path.join(hqRoot, "companies", "acme"), { recursive: true });
755
+ const manifestPath = writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
756
+ const diagnostics = [];
757
+ const result = await reconcileCompanyManifest({
758
+ hqRoot,
759
+ targets: [{ uid: "cmp_acme", slug: "acme" }],
760
+ completedCompanySlugs: new Set(["acme"]),
761
+ // A programming error, not a lookup answer.
762
+ getEntity: () => Promise.reject(new TypeError("resolver is not a function")),
763
+ reportDiagnostic: (d) => diagnostics.push(d),
764
+ });
765
+ // The target is still skipped — a broken probe never mints an entry.
766
+ expect(result).toEqual({ written: false, added: [], updated: [], skipped: [] });
767
+ expectUntouched(manifestPath, EMPTY_MANIFEST_TEMPLATE);
768
+ // ...but it is no longer invisible.
769
+ expect(diagnostics).toHaveLength(1);
770
+ expect(diagnostics[0].event).toBe("runner.manifest_reconcile.entity_probe_failed");
771
+ expect(diagnostics[0].err).toBeInstanceOf(TypeError);
772
+ expect(diagnostics[0].context).toMatchObject({ slug: "acme", uid: "cmp_acme" });
773
+ });
774
+ it("without getEntity: fails closed when the plan carried no liveness evidence at all", async () => {
775
+ hqRoot = makeHqRoot();
776
+ fs.mkdirSync(path.join(hqRoot, "companies", "gone"), { recursive: true });
777
+ const manifestPath = writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
778
+ expect(await reconcileCompanyManifest({
779
+ hqRoot,
780
+ // Completed, non-personal, directory on disk — every OTHER filter passes.
781
+ targets: [{ uid: "cmp_deleted", slug: "gone" }],
782
+ completedCompanySlugs: new Set(["gone"]),
783
+ })).toEqual({ written: false, added: [], updated: [], skipped: [] });
784
+ expect(readCompanies(manifestPath).gone).toBeUndefined();
785
+ expectUntouched(manifestPath, EMPTY_MANIFEST_TEMPLATE);
786
+ });
787
+ it("without getEntity: refuses a target the plan saw as non-active or non-company", async () => {
788
+ hqRoot = makeHqRoot();
789
+ for (const slug of ["suspended", "deleted", "notacompany"]) {
790
+ fs.mkdirSync(path.join(hqRoot, "companies", slug), { recursive: true });
791
+ }
792
+ const manifestPath = writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
793
+ expect(await reconcileCompanyManifest({
794
+ hqRoot,
795
+ targets: [
796
+ {
797
+ uid: "cmp_suspended",
798
+ slug: "suspended",
799
+ entityType: "company",
800
+ entityStatus: "suspended",
801
+ },
802
+ {
803
+ uid: "cmp_deleted",
804
+ slug: "deleted",
805
+ entityType: "company",
806
+ entityStatus: "deleted",
807
+ },
808
+ {
809
+ uid: "prs_person",
810
+ slug: "notacompany",
811
+ entityType: "person",
812
+ entityStatus: "active",
813
+ },
814
+ ],
815
+ completedCompanySlugs: new Set(["suspended", "deleted", "notacompany"]),
816
+ })).toEqual({ written: false, added: [], updated: [], skipped: [] });
817
+ expect(Object.keys(readCompanies(manifestPath))).toEqual([]);
818
+ expectUntouched(manifestPath, EMPTY_MANIFEST_TEMPLATE);
819
+ });
820
+ it("without getEntity: no eligible target means no write at all", async () => {
821
+ hqRoot = makeHqRoot();
822
+ const manifestPath = writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
823
+ expect(await reconcileCompanyManifest({
824
+ hqRoot,
825
+ targets: [{ uid: "prs_me", slug: "personal", personalMode: true }],
826
+ completedCompanySlugs: new Set(["personal"]),
827
+ })).toEqual({ written: false, added: [], updated: [], skipped: [] });
828
+ expectUntouched(manifestPath, EMPTY_MANIFEST_TEMPLATE);
829
+ });
830
+ async function reconcileWithProbeError(err) {
831
+ hqRoot = makeHqRoot();
832
+ fs.mkdirSync(path.join(hqRoot, "companies", "acme"), { recursive: true });
833
+ const manifestPath = writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
834
+ const diagnostics = [];
835
+ const result = await reconcileCompanyManifest({
836
+ hqRoot,
837
+ targets: [{ uid: "cmp_acme", slug: "acme" }],
838
+ completedCompanySlugs: new Set(["acme"]),
839
+ getEntity: () => Promise.reject(err),
840
+ reportDiagnostic: (diagnostic) => diagnostics.push(diagnostic),
841
+ });
842
+ // Invariant across every case: skipped, nothing written, file untouched.
843
+ expect(result).toEqual({ written: false, added: [], updated: [], skipped: [] });
844
+ expectUntouched(manifestPath, EMPTY_MANIFEST_TEMPLATE);
845
+ return { diagnostics, manifestPath };
846
+ }
847
+ it("with getEntity: a broken resolver (TypeError) is skipped AND reported", async () => {
848
+ const { diagnostics } = await reconcileWithProbeError(new TypeError("client.entity.get is not a function"));
849
+ expect(diagnostics).toHaveLength(1);
850
+ expect(diagnostics[0].event).toBe("runner.manifest_reconcile.entity_probe_failed");
851
+ expect(diagnostics[0].context).toMatchObject({ slug: "acme", uid: "cmp_acme" });
852
+ expect(diagnostics[0].err).toBeInstanceOf(TypeError);
853
+ });
854
+ it("with getEntity: an ordinary not-found is skipped SILENTLY", async () => {
855
+ const { diagnostics } = await reconcileWithProbeError(new VaultNotFoundError("company deleted"));
856
+ expect(diagnostics).toEqual([]);
857
+ });
858
+ it("with getEntity: a permission denial is skipped SILENTLY", async () => {
859
+ const { diagnostics } = await reconcileWithProbeError(new VaultPermissionDeniedError());
860
+ expect(diagnostics).toEqual([]);
861
+ });
862
+ it("with getEntity: a duck-typed { statusCode } rejection is treated as a lookup answer, not a bug", async () => {
863
+ // buildFanoutPlan classifies this same call's failures by duck-typed
864
+ // statusCode, so this shape is one the codebase genuinely produces.
865
+ const { diagnostics } = await reconcileWithProbeError({ statusCode: 404 });
866
+ expect(diagnostics).toEqual([]);
867
+ });
868
+ it("with getEntity: reporting is optional — omitting the sink never throws", async () => {
869
+ hqRoot = makeHqRoot();
870
+ fs.mkdirSync(path.join(hqRoot, "companies", "acme"), { recursive: true });
871
+ const manifestPath = writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
872
+ await expect(reconcileCompanyManifest({
873
+ hqRoot,
874
+ targets: [{ uid: "cmp_acme", slug: "acme" }],
875
+ completedCompanySlugs: new Set(["acme"]),
876
+ getEntity: () => Promise.reject(new TypeError("boom")),
877
+ })).resolves.toEqual({ written: false, added: [], updated: [], skipped: [] });
878
+ expectUntouched(manifestPath, EMPTY_MANIFEST_TEMPLATE);
879
+ });
880
+ it("with getEntity: a suspended company is rejected — the A/B twin of the plan-carried case", async () => {
881
+ hqRoot = makeHqRoot();
882
+ fs.mkdirSync(path.join(hqRoot, "companies", "susp"), { recursive: true });
883
+ const manifestPath = writeManifest(hqRoot, EMPTY_MANIFEST_TEMPLATE);
884
+ // Both paths must reach the SAME verdict on the same company. This is the
885
+ // pair that was empirically divergent before the guard was relocated.
886
+ expect(await reconcileCompanyManifest({
887
+ hqRoot,
888
+ targets: [{ uid: "cmp_susp", slug: "susp" }],
889
+ completedCompanySlugs: new Set(["susp"]),
890
+ getEntity: () => Promise.resolve({
891
+ uid: "cmp_susp",
892
+ slug: "susp",
893
+ type: "company",
894
+ status: "suspended",
895
+ name: "Suspended Corp",
896
+ bucketName: "hq-vault-cmp-susp",
897
+ createdAt: "2026-01-01T00:00:00Z",
898
+ }),
899
+ })).toEqual({ written: false, added: [], updated: [], skipped: [] });
900
+ expectUntouched(manifestPath, EMPTY_MANIFEST_TEMPLATE);
901
+ // ...and the plan-carried path agrees, with no resolver at all.
902
+ expect(await reconcileCompanyManifest({
903
+ hqRoot,
904
+ targets: [
905
+ {
906
+ uid: "cmp_susp",
907
+ slug: "susp",
908
+ name: "Suspended Corp",
909
+ bucketName: "hq-vault-cmp-susp",
910
+ entityType: "company",
911
+ entityStatus: "suspended",
912
+ },
913
+ ],
914
+ completedCompanySlugs: new Set(["susp"]),
915
+ })).toEqual({ written: false, added: [], updated: [], skipped: [] });
916
+ expectUntouched(manifestPath, EMPTY_MANIFEST_TEMPLATE);
917
+ });
96
918
  });
97
919
  //# sourceMappingURL=manifest-reconcile.test.js.map