@indigoai-us/hq-cloud 6.14.24 → 6.14.26

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.
@@ -75,7 +75,28 @@ describe("buildRepoCompanyMap + resolveCompanyForCwd", () => {
75
75
  hqRoot = setupHqRoot();
76
76
  const map = await buildRepoCompanyMap(hqRoot);
77
77
  expect(resolveCompanyForCwd("/Users/x/random-repo", map)).toBeUndefined();
78
- expect(resolveCompanyForCwd(path.join(hqRoot, "companies/indigo"), map)).toBeUndefined();
78
+ // The HQ root itself is company-agnostic — it belongs to no single tenant.
79
+ expect(resolveCompanyForCwd(hqRoot, map)).toBeUndefined();
80
+ });
81
+
82
+ /**
83
+ * Behaviour change (was: `companies/<slug>` resolved to undefined).
84
+ *
85
+ * A company's own HQ directory holds its skills, knowledge, and project
86
+ * files, so work done there is unambiguously that company's work. The prior
87
+ * repos-only scope dropped it, and in production a large share of sessions
88
+ * run from inside the HQ root rather than a repo checkout — leaving that work
89
+ * unattributed and the owner's Projects view empty. Attribution is scoped to
90
+ * the single tenant that owns the directory, so this widens coverage without
91
+ * crossing a tenant boundary.
92
+ */
93
+ it("attributes a cwd inside the company's own HQ directory", async () => {
94
+ hqRoot = setupHqRoot();
95
+ const map = await buildRepoCompanyMap(hqRoot);
96
+ expect(resolveCompanyForCwd(path.join(hqRoot, "companies/indigo"), map)).toBe(INDIGO);
97
+ expect(
98
+ resolveCompanyForCwd(path.join(hqRoot, "companies/hpo/knowledge"), map),
99
+ ).toBe(HPO);
79
100
  });
80
101
 
81
102
  it("does NOT attribute a company that lacks a cloud_uid (not cloud-backed)", async () => {
@@ -185,3 +206,413 @@ describe("resolveCompanyForSkill", () => {
185
206
  expect(resolveCompanyForSkill("", map)).toBeUndefined();
186
207
  });
187
208
  });
209
+
210
+ /**
211
+ * Regression: the real `companies/manifest.yaml` records `repos` as BARE NAMES
212
+ * (`hq-cloud`), not HQ-root-relative paths (`repos/private/hq-cloud`). The
213
+ * original resolver did `path.resolve(hqRoot, repo)` and produced
214
+ * `<hqRoot>/hq-cloud` — a path no checkout lives at — so every entry matched
215
+ * nothing and NO telemetry event was ever attributed. Verified in prod at the
216
+ * time of the fix: 0 of 3200 sampled usage events across 9 people carried a
217
+ * `companyUid`, which left the Team Activity "Projects" tab permanently empty.
218
+ */
219
+ describe("real-manifest shape: bare repo names + company directories", () => {
220
+ const ACME = "cmp_01ACME";
221
+ const BETA = "cmp_01BETA";
222
+
223
+ // Mirrors the live manifest: bare repo names, an explicit `path`, and a
224
+ // company with no cloud_uid (not cloud-backed).
225
+ const REAL_SHAPE = `companies:
226
+ acme:
227
+ name: Acme
228
+ path: companies/acme
229
+ repos:
230
+ - hq-cloud
231
+ - hq-console
232
+ cloud_uid: ${ACME}
233
+ beta:
234
+ name: Beta
235
+ path: companies/beta
236
+ repos:
237
+ - beta-site
238
+ cloud_uid: ${BETA}
239
+ nocloud:
240
+ name: NoCloud
241
+ path: companies/nocloud
242
+ repos:
243
+ - orphan-repo
244
+ `;
245
+
246
+ let hqRoot: string;
247
+ afterEach(() => {
248
+ if (hqRoot) fs.rmSync(hqRoot, { recursive: true, force: true });
249
+ });
250
+
251
+ it("resolves a bare repo name to its repos/private checkout", async () => {
252
+ hqRoot = setupHqRoot(REAL_SHAPE);
253
+ const map = await buildRepoCompanyMap(hqRoot);
254
+ const cwd = path.join(hqRoot, "repos/private/hq-console/src/app");
255
+ expect(resolveCompanyForCwd(cwd, map)).toBe(ACME);
256
+ });
257
+
258
+ it("resolves a bare repo name to its repos/public checkout", async () => {
259
+ hqRoot = setupHqRoot(REAL_SHAPE);
260
+ const map = await buildRepoCompanyMap(hqRoot);
261
+ const cwd = path.join(hqRoot, "repos/public/hq-cloud");
262
+ expect(resolveCompanyForCwd(cwd, map)).toBe(ACME);
263
+ });
264
+
265
+ it("attributes work inside the company's own HQ directory", async () => {
266
+ hqRoot = setupHqRoot(REAL_SHAPE);
267
+ const map = await buildRepoCompanyMap(hqRoot);
268
+ expect(resolveCompanyForCwd(path.join(hqRoot, "companies/acme"), map)).toBe(ACME);
269
+ expect(
270
+ resolveCompanyForCwd(path.join(hqRoot, "companies/beta/projects/x"), map),
271
+ ).toBe(BETA);
272
+ });
273
+
274
+ it("still honours an explicit HQ-root-relative repo path (back-compat)", async () => {
275
+ hqRoot = setupHqRoot();
276
+ const map = await buildRepoCompanyMap(hqRoot);
277
+ expect(
278
+ resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-cloud/src"), map),
279
+ ).toBe("cmp_01INDIGO");
280
+ });
281
+
282
+ it("leaves a non-cloud-backed company's repos and directory unattributed", async () => {
283
+ hqRoot = setupHqRoot(REAL_SHAPE);
284
+ const map = await buildRepoCompanyMap(hqRoot);
285
+ expect(
286
+ resolveCompanyForCwd(path.join(hqRoot, "repos/private/orphan-repo"), map),
287
+ ).toBeUndefined();
288
+ expect(resolveCompanyForCwd(path.join(hqRoot, "companies/nocloud"), map)).toBeUndefined();
289
+ });
290
+
291
+ /**
292
+ * Case folding follows the FILESYSTEM, not the OS.
293
+ *
294
+ * Where the filesystem folds case, `.../HQ` and `.../hq` are literally the
295
+ * same directory and a differently-cased cwd must still attribute. Where it
296
+ * does not, they are different directories and an unattributed event is the
297
+ * correct (safe) outcome.
298
+ *
299
+ * The expectation is derived from the resolver's own probe (`map.foldsCase`),
300
+ * NOT from `process.platform` — case-sensitive APFS and case-insensitive
301
+ * volumes mounted on Linux both make the platform a liar, and this test would
302
+ * fail on them despite the resolver behaving correctly.
303
+ */
304
+ it("follows the probed filesystem for a differently-cased path", async () => {
305
+ hqRoot = setupHqRoot(REAL_SHAPE);
306
+ const map = await buildRepoCompanyMap(hqRoot);
307
+ const cwd = path.join(hqRoot, "repos/private/hq-console").toUpperCase();
308
+ expect(resolveCompanyForCwd(cwd, map)).toBe(map.foldsCase ? ACME : undefined);
309
+ });
310
+
311
+ it("always matches an exactly-cased path", async () => {
312
+ hqRoot = setupHqRoot(REAL_SHAPE);
313
+ const map = await buildRepoCompanyMap(hqRoot);
314
+ expect(
315
+ resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-console/src"), map),
316
+ ).toBe(ACME);
317
+ });
318
+
319
+ it("treats backslash-separated paths as equivalent to forward slashes", async () => {
320
+ hqRoot = setupHqRoot(REAL_SHAPE);
321
+ const map = await buildRepoCompanyMap(hqRoot);
322
+ const cwd = path.join(hqRoot, "repos/private/hq-console/src").replace(/\//g, "\\");
323
+ expect(resolveCompanyForCwd(cwd, map)).toBe(ACME);
324
+ });
325
+
326
+ it("does not let a sibling sharing a string prefix match", async () => {
327
+ hqRoot = setupHqRoot(REAL_SHAPE);
328
+ const map = await buildRepoCompanyMap(hqRoot);
329
+ expect(
330
+ resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-console-other"), map),
331
+ ).toBeUndefined();
332
+ });
333
+
334
+ it("emits no duplicate match keys", async () => {
335
+ hqRoot = setupHqRoot(REAL_SHAPE);
336
+ const map = await buildRepoCompanyMap(hqRoot);
337
+ const keys = map.entries.map((e) => e.matchKey);
338
+ expect(new Set(keys).size).toBe(keys.length);
339
+ });
340
+ });
341
+
342
+ /**
343
+ * Codex-review follow-ups. Each of these is a real HQ layout that pure lexical
344
+ * matching against the primary checkout silently misses, leaving the work
345
+ * unattributed — the same class of gap that left the Projects tab empty.
346
+ */
347
+ describe("real HQ layouts: worktrees, symlinks, and ambiguity", () => {
348
+ const ACME = "cmp_01ACME";
349
+ const BETA = "cmp_01BETA";
350
+
351
+ const SHAPE = `companies:
352
+ acme:
353
+ name: Acme
354
+ path: companies/acme
355
+ repos:
356
+ - hq-cloud
357
+ cloud_uid: ${ACME}
358
+ beta:
359
+ name: Beta
360
+ path: companies/beta
361
+ repos:
362
+ - beta-site
363
+ cloud_uid: ${BETA}
364
+ `;
365
+
366
+ let hqRoot: string;
367
+ afterEach(() => {
368
+ if (hqRoot) fs.rmSync(hqRoot, { recursive: true, force: true });
369
+ });
370
+
371
+ function mkdirp(...segments: string[]): string {
372
+ const p = path.join(hqRoot, ...segments);
373
+ fs.mkdirSync(p, { recursive: true });
374
+ return p;
375
+ }
376
+
377
+ /**
378
+ * HQ does most feature work in a sibling git worktree named
379
+ * `<repo>-wt-<branch>`. The resolver's slash boundary rejects that as a mere
380
+ * string-prefix sibling of `<repo>`, so without explicit expansion every
381
+ * worktree session is unattributed.
382
+ */
383
+ it("attributes work inside a `<repo>-wt-*` git worktree", async () => {
384
+ hqRoot = setupHqRoot(SHAPE);
385
+ mkdirp("repos/private/hq-cloud");
386
+ const wt = mkdirp("repos/private/hq-cloud-wt-feature-x");
387
+
388
+ const map = await buildRepoCompanyMap(hqRoot);
389
+ expect(resolveCompanyForCwd(wt, map)).toBe(ACME);
390
+ expect(resolveCompanyForCwd(path.join(wt, "src/lib"), map)).toBe(ACME);
391
+ });
392
+
393
+ it("still rejects an unrelated sibling that merely shares the prefix", async () => {
394
+ hqRoot = setupHqRoot(SHAPE);
395
+ mkdirp("repos/private/hq-cloud");
396
+ const other = mkdirp("repos/private/hq-cloud-other");
397
+
398
+ const map = await buildRepoCompanyMap(hqRoot);
399
+ expect(resolveCompanyForCwd(other, map)).toBeUndefined();
400
+ });
401
+
402
+ /**
403
+ * A symlinked checkout reports its PHYSICAL path as cwd, which lives outside
404
+ * the HQ tree entirely and can never match the lexical root.
405
+ */
406
+ it("attributes a symlinked checkout reached by its physical path", async () => {
407
+ hqRoot = setupHqRoot(SHAPE);
408
+ const physical = fs.mkdtempSync(path.join(os.tmpdir(), "hq-physical-repo-"));
409
+ fs.mkdirSync(path.join(hqRoot, "repos/private"), { recursive: true });
410
+ try {
411
+ fs.symlinkSync(physical, path.join(hqRoot, "repos/private/hq-cloud"), "dir");
412
+ } catch {
413
+ return; // symlinks unavailable (e.g. unprivileged Windows) — skip
414
+ }
415
+
416
+ const map = await buildRepoCompanyMap(hqRoot);
417
+ try {
418
+ expect(resolveCompanyForCwd(fs.realpathSync(physical), map)).toBe(ACME);
419
+ // The lexical path must keep working too.
420
+ expect(
421
+ resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-cloud"), map),
422
+ ).toBe(ACME);
423
+ } finally {
424
+ fs.rmSync(physical, { recursive: true, force: true });
425
+ }
426
+ });
427
+
428
+ /**
429
+ * Fail CLOSED: when two companies claim the same path, attributing to
430
+ * whichever the manifest listed first would be a cross-tenant
431
+ * misattribution. Unattributed is the correct answer.
432
+ */
433
+ it("leaves a path claimed by two companies unattributed", async () => {
434
+ const AMBIGUOUS = `companies:
435
+ acme:
436
+ name: Acme
437
+ repos:
438
+ - shared-repo
439
+ cloud_uid: ${ACME}
440
+ beta:
441
+ name: Beta
442
+ repos:
443
+ - shared-repo
444
+ cloud_uid: ${BETA}
445
+ `;
446
+ hqRoot = setupHqRoot(AMBIGUOUS);
447
+ mkdirp("repos/private/shared-repo");
448
+
449
+ const map = await buildRepoCompanyMap(hqRoot);
450
+ expect(
451
+ resolveCompanyForCwd(path.join(hqRoot, "repos/private/shared-repo"), map),
452
+ ).toBeUndefined();
453
+ });
454
+
455
+ it("an ambiguous repo does not poison that company's other repos", async () => {
456
+ const AMBIGUOUS = `companies:
457
+ acme:
458
+ name: Acme
459
+ repos:
460
+ - shared-repo
461
+ - acme-only
462
+ cloud_uid: ${ACME}
463
+ beta:
464
+ name: Beta
465
+ repos:
466
+ - shared-repo
467
+ cloud_uid: ${BETA}
468
+ `;
469
+ hqRoot = setupHqRoot(AMBIGUOUS);
470
+ mkdirp("repos/private/shared-repo");
471
+ mkdirp("repos/private/acme-only");
472
+
473
+ const map = await buildRepoCompanyMap(hqRoot);
474
+ expect(
475
+ resolveCompanyForCwd(path.join(hqRoot, "repos/private/acme-only"), map),
476
+ ).toBe(ACME);
477
+ });
478
+
479
+ it("reports the probed case-folding behaviour of the real filesystem", async () => {
480
+ hqRoot = setupHqRoot(SHAPE);
481
+ const map = await buildRepoCompanyMap(hqRoot);
482
+ expect(typeof map.foldsCase).toBe("boolean");
483
+ // Whatever the probe found, an exactly-cased path always resolves.
484
+ mkdirp("repos/private/hq-cloud");
485
+ const fresh = await buildRepoCompanyMap(hqRoot);
486
+ expect(
487
+ resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-cloud"), fresh),
488
+ ).toBe(ACME);
489
+ });
490
+ });
491
+
492
+ /**
493
+ * A worktree that no longer exists on disk.
494
+ *
495
+ * `buildRepoCompanyMap` discovers worktrees by reading the directory, which
496
+ * only sees what exists at build time. Telemetry, though, is read from a
497
+ * cursor — and a branch worktree is routinely deleted before the next pass,
498
+ * while pending JSONL rows still carry its cwd. Since a successful POST
499
+ * advances the cursor permanently, failing to resolve those rows loses the
500
+ * attribution for good.
501
+ */
502
+ describe("worktree paths that no longer exist on disk", () => {
503
+ const ACME = "cmp_01ACME";
504
+ const SHAPE = `companies:
505
+ acme:
506
+ name: Acme
507
+ path: companies/acme
508
+ repos:
509
+ - hq-cloud
510
+ cloud_uid: ${ACME}
511
+ `;
512
+
513
+ let hqRoot: string;
514
+ afterEach(() => {
515
+ if (hqRoot) fs.rmSync(hqRoot, { recursive: true, force: true });
516
+ });
517
+
518
+ it("resolves a deleted worktree's cwd by its documented naming convention", async () => {
519
+ hqRoot = setupHqRoot(SHAPE);
520
+ fs.mkdirSync(path.join(hqRoot, "repos/private/hq-cloud"), { recursive: true });
521
+ // NOTE: the worktree is deliberately NOT created — it was cleaned up before
522
+ // this telemetry pass, exactly as in the real failure.
523
+ const map = await buildRepoCompanyMap(hqRoot);
524
+
525
+ const gone = path.join(hqRoot, "repos/private/hq-cloud-wt-feature-gone");
526
+ expect(resolveCompanyForCwd(gone, map)).toBe(ACME);
527
+ expect(resolveCompanyForCwd(path.join(gone, "src/lib"), map)).toBe(ACME);
528
+ });
529
+
530
+ it("does not let the worktree fallback rescue an unrelated sibling", async () => {
531
+ hqRoot = setupHqRoot(SHAPE);
532
+ fs.mkdirSync(path.join(hqRoot, "repos/private/hq-cloud"), { recursive: true });
533
+ const map = await buildRepoCompanyMap(hqRoot);
534
+
535
+ // `-other` is not the `-wt-` convention, so it stays a foreign sibling...
536
+ expect(
537
+ resolveCompanyForCwd(path.join(hqRoot, "repos/private/hq-cloud-other"), map),
538
+ ).toBeUndefined();
539
+ // ...and a `-wt-` suffix on a repo nobody owns resolves to nothing.
540
+ expect(
541
+ resolveCompanyForCwd(path.join(hqRoot, "repos/private/stranger-wt-x"), map),
542
+ ).toBeUndefined();
543
+ });
544
+ });
545
+
546
+ /**
547
+ * Fail-closed must survive the worktree fallback.
548
+ *
549
+ * The fallback collapses `<repo>-wt-<branch>` to `<repo>`. If the worktree-
550
+ * shaped path was itself dropped for ambiguity, collapsing it would hand the
551
+ * event to whoever owns the base repo — quietly undoing the protection.
552
+ */
553
+ describe("ambiguity is not escapable via the worktree fallback", () => {
554
+ const ACME = "cmp_01ACME";
555
+ const BETA = "cmp_01BETA";
556
+
557
+ let hqRoot: string;
558
+ afterEach(() => {
559
+ if (hqRoot) fs.rmSync(hqRoot, { recursive: true, force: true });
560
+ });
561
+
562
+ it("keeps an ambiguous `<repo>-wt-*` path unattributed instead of collapsing it", async () => {
563
+ // Acme owns `foo`; Beta owns a real repo that happens to be named
564
+ // `foo-wt-team`, which also looks like a worktree of Acme's `foo`.
565
+ hqRoot = setupHqRoot(`companies:
566
+ acme:
567
+ name: Acme
568
+ repos:
569
+ - foo
570
+ cloud_uid: ${ACME}
571
+ beta:
572
+ name: Beta
573
+ repos:
574
+ - foo-wt-team
575
+ cloud_uid: ${BETA}
576
+ `);
577
+ fs.mkdirSync(path.join(hqRoot, "repos/private/foo"), { recursive: true });
578
+ fs.mkdirSync(path.join(hqRoot, "repos/private/foo-wt-team"), { recursive: true });
579
+
580
+ const map = await buildRepoCompanyMap(hqRoot);
581
+ const contested = path.join(hqRoot, "repos/private/foo-wt-team");
582
+
583
+ expect(resolveCompanyForCwd(contested, map)).toBeUndefined();
584
+ expect(resolveCompanyForCwd(path.join(contested, "src"), map)).toBeUndefined();
585
+ // The unambiguous base repo still resolves.
586
+ expect(resolveCompanyForCwd(path.join(hqRoot, "repos/private/foo"), map)).toBe(ACME);
587
+ });
588
+ });
589
+
590
+ /**
591
+ * `path` is documented as HQ-root-relative, but the manifest is synced and
592
+ * hand-editable. A path that escapes the root would claim a directory spanning
593
+ * other tenants and stamp this company's uid on everything beneath it.
594
+ */
595
+ describe("company paths that escape the HQ root are rejected", () => {
596
+ const ACME = "cmp_01ACME";
597
+
598
+ let hqRoot: string;
599
+ afterEach(() => {
600
+ if (hqRoot) fs.rmSync(hqRoot, { recursive: true, force: true });
601
+ });
602
+
603
+ it.each([[" .."], ["."], ["/"]])("ignores path: %s", async (badPath) => {
604
+ hqRoot = setupHqRoot(`companies:
605
+ acme:
606
+ name: Acme
607
+ path: "${badPath.trim()}"
608
+ repos: []
609
+ cloud_uid: ${ACME}
610
+ `);
611
+ const map = await buildRepoCompanyMap(hqRoot);
612
+
613
+ // Neither the HQ root nor its parent may be claimed.
614
+ expect(resolveCompanyForCwd(hqRoot, map)).toBeUndefined();
615
+ expect(resolveCompanyForCwd(path.dirname(hqRoot), map)).toBeUndefined();
616
+ expect(resolveCompanyForCwd(path.join(hqRoot, "companies/other"), map)).toBeUndefined();
617
+ });
618
+ });