@indigoai-us/hq-cloud 6.14.17 → 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 (39) 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/manifest-reconcile.d.ts +118 -5
  24. package/dist/manifest-reconcile.d.ts.map +1 -1
  25. package/dist/manifest-reconcile.js +319 -62
  26. package/dist/manifest-reconcile.js.map +1 -1
  27. package/dist/manifest-reconcile.test.js +824 -2
  28. package/dist/manifest-reconcile.test.js.map +1 -1
  29. package/package.json +1 -1
  30. package/pnpm-workspace.yaml +1 -1
  31. package/src/active-company.test.ts +188 -0
  32. package/src/active-company.ts +168 -0
  33. package/src/bin/sync-runner-planning.test.ts +131 -0
  34. package/src/bin/sync-runner-planning.ts +60 -7
  35. package/src/bin/sync-runner.test.ts +430 -10
  36. package/src/bin/sync-runner.ts +129 -23
  37. package/src/manifest-reconcile.test.ts +1019 -3
  38. package/src/manifest-reconcile.ts +424 -66
  39. package/test/joiner-manifest-reconcile.integration.test.ts +283 -0
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Seed `activeCompany` in `.hq/config.json` for a joiner who has exactly one
3
+ * cloud company.
4
+ *
5
+ * A newly invited member arrives with no local company routing at all. The
6
+ * manifest reconciler restores their `companies/manifest.yaml` entry, but
7
+ * nothing tells HQ which company is *current*, so they stay unrouted even
8
+ * though membership resolved fine. When the answer is unambiguous — one company
9
+ * and no existing choice — recording it is strictly better than leaving the
10
+ * user to guess.
11
+ *
12
+ * Everything here is deliberately conservative. This runs unattended on every
13
+ * sync, against a file the user also edits by hand, so the bar for writing is
14
+ * "the answer cannot be wrong": never overwrite an existing choice, never guess
15
+ * between two companies, and never clobber a file we could not parse.
16
+ */
17
+ export interface SeedActiveCompanyOptions {
18
+ hqRoot: string;
19
+ /** Slugs of companies that reconciled successfully this run. */
20
+ companySlugs: readonly string[];
21
+ reportDiagnostic?: (diagnostic: {
22
+ event: string;
23
+ message: string;
24
+ err: unknown;
25
+ context: Record<string, unknown>;
26
+ }) => void;
27
+ }
28
+ export interface SeedActiveCompanyResult {
29
+ /** True only when `.hq/config.json` was actually written. */
30
+ written: boolean;
31
+ /** The slug that was seeded, when one was. */
32
+ activeCompany?: string;
33
+ /** Why nothing was written. Absent when `written` is true. */
34
+ skippedReason?: "ambiguous" | "no-companies" | "already-set" | "unreadable-config" | "write-failed";
35
+ }
36
+ /**
37
+ * Seed `activeCompany` when — and only when — the answer is unambiguous.
38
+ *
39
+ * Never throws: the caller is a sync run whose exit code must not depend on
40
+ * local routing convenience.
41
+ */
42
+ export declare function seedActiveCompany(options: SeedActiveCompanyOptions): SeedActiveCompanyResult;
43
+ //# sourceMappingURL=active-company.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-company.d.ts","sourceRoot":"","sources":["../src/active-company.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,gBAAgB,CAAC,EAAE,CAAC,UAAU,EAAE;QAC9B,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,GAAG,EAAE,OAAO,CAAC;QACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAClC,KAAK,IAAI,CAAC;CACZ;AAED,MAAM,WAAW,uBAAuB;IACtC,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAC;IACjB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,8DAA8D;IAC9D,aAAa,CAAC,EACV,WAAW,GACX,cAAc,GACd,aAAa,GACb,mBAAmB,GACnB,cAAc,CAAC;CACpB;AAwCD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,wBAAwB,GAChC,uBAAuB,CA0EzB"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Seed `activeCompany` in `.hq/config.json` for a joiner who has exactly one
3
+ * cloud company.
4
+ *
5
+ * A newly invited member arrives with no local company routing at all. The
6
+ * manifest reconciler restores their `companies/manifest.yaml` entry, but
7
+ * nothing tells HQ which company is *current*, so they stay unrouted even
8
+ * though membership resolved fine. When the answer is unambiguous — one company
9
+ * and no existing choice — recording it is strictly better than leaving the
10
+ * user to guess.
11
+ *
12
+ * Everything here is deliberately conservative. This runs unattended on every
13
+ * sync, against a file the user also edits by hand, so the bar for writing is
14
+ * "the answer cannot be wrong": never overwrite an existing choice, never guess
15
+ * between two companies, and never clobber a file we could not parse.
16
+ */
17
+ import { randomUUID } from "node:crypto";
18
+ import * as fs from "node:fs";
19
+ import * as path from "node:path";
20
+ function isPlainObject(value) {
21
+ if (value === null || typeof value !== "object" || Array.isArray(value))
22
+ return false;
23
+ const proto = Object.getPrototypeOf(value);
24
+ return proto === Object.prototype || proto === null;
25
+ }
26
+ function isNonEmptyString(value) {
27
+ return typeof value === "string" && value.length > 0;
28
+ }
29
+ /**
30
+ * Write `config` to `configPath` via temp-file + rename, so a reader never
31
+ * observes a half-written config and a crash cannot truncate the original.
32
+ */
33
+ function writeConfigAtomically(configPath, config) {
34
+ const dir = path.dirname(configPath);
35
+ fs.mkdirSync(dir, { recursive: true });
36
+ const temporaryPath = path.join(dir, `.config-${process.pid}-${randomUUID()}.json`);
37
+ try {
38
+ fs.writeFileSync(temporaryPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
39
+ fs.renameSync(temporaryPath, configPath);
40
+ }
41
+ finally {
42
+ // The rename consumed the temp file on the success path; this only fires
43
+ // when writing or renaming threw, and must not mask the original error.
44
+ try {
45
+ fs.rmSync(temporaryPath, { force: true });
46
+ }
47
+ catch {
48
+ // Best effort — a stray temp file is not worth failing a sync over.
49
+ }
50
+ }
51
+ }
52
+ /**
53
+ * Seed `activeCompany` when — and only when — the answer is unambiguous.
54
+ *
55
+ * Never throws: the caller is a sync run whose exit code must not depend on
56
+ * local routing convenience.
57
+ */
58
+ export function seedActiveCompany(options) {
59
+ const slugs = Array.from(new Set(options.companySlugs.filter(isNonEmptyString)));
60
+ if (slugs.length === 0)
61
+ return { written: false, skippedReason: "no-companies" };
62
+ // Two or more companies is a genuine choice and belongs to the user. Picking
63
+ // one would route them somewhere arbitrary and look like a bug.
64
+ if (slugs.length > 1)
65
+ return { written: false, skippedReason: "ambiguous" };
66
+ const activeCompany = slugs[0];
67
+ const configPath = path.join(options.hqRoot, ".hq", "config.json");
68
+ let config = {};
69
+ let raw;
70
+ try {
71
+ raw = fs.readFileSync(configPath, "utf8");
72
+ }
73
+ catch (err) {
74
+ if (err.code !== "ENOENT") {
75
+ // An unreadable config is user state we cannot reason about. Leave it.
76
+ options.reportDiagnostic?.({
77
+ event: "runner.active_company.config_unreadable",
78
+ message: "could not read .hq/config.json; activeCompany not seeded",
79
+ err,
80
+ context: { configPath },
81
+ });
82
+ return { written: false, skippedReason: "unreadable-config" };
83
+ }
84
+ }
85
+ if (raw !== undefined) {
86
+ let parsed;
87
+ try {
88
+ parsed = JSON.parse(raw);
89
+ }
90
+ catch (err) {
91
+ // Malformed JSON is almost always a half-finished hand edit. Overwriting
92
+ // it would destroy whatever the user was in the middle of doing.
93
+ options.reportDiagnostic?.({
94
+ event: "runner.active_company.config_malformed",
95
+ message: ".hq/config.json is not valid JSON; left untouched",
96
+ err,
97
+ context: { configPath },
98
+ });
99
+ return { written: false, skippedReason: "unreadable-config" };
100
+ }
101
+ if (!isPlainObject(parsed)) {
102
+ options.reportDiagnostic?.({
103
+ event: "runner.active_company.config_malformed",
104
+ message: ".hq/config.json is not a JSON object; left untouched",
105
+ err: undefined,
106
+ context: { configPath },
107
+ });
108
+ return { written: false, skippedReason: "unreadable-config" };
109
+ }
110
+ config = parsed;
111
+ }
112
+ // `companySlug` counts as already-set: sync-core resolves it as the active
113
+ // company when `activeCompany` is absent, so writing `activeCompany` here
114
+ // would silently re-route a user who already had an effective answer.
115
+ if (isNonEmptyString(config.activeCompany) || isNonEmptyString(config.companySlug)) {
116
+ return { written: false, skippedReason: "already-set" };
117
+ }
118
+ try {
119
+ writeConfigAtomically(configPath, { ...config, activeCompany });
120
+ }
121
+ catch (err) {
122
+ options.reportDiagnostic?.({
123
+ event: "runner.active_company.write_failed",
124
+ message: "failed to seed activeCompany in .hq/config.json",
125
+ err,
126
+ context: { configPath, activeCompany },
127
+ });
128
+ return { written: false, skippedReason: "write-failed" };
129
+ }
130
+ return { written: true, activeCompany };
131
+ }
132
+ //# sourceMappingURL=active-company.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-company.js","sourceRoot":"","sources":["../src/active-company.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=active-company.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-company.test.d.ts","sourceRoot":"","sources":["../src/active-company.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,149 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { seedActiveCompany } from "./active-company.js";
6
+ let hqRoot;
7
+ afterEach(() => {
8
+ if (hqRoot) {
9
+ fs.rmSync(hqRoot, { recursive: true, force: true });
10
+ hqRoot = undefined;
11
+ }
12
+ });
13
+ function makeHqRoot() {
14
+ return fs.mkdtempSync(path.join(os.tmpdir(), "hq-active-company-"));
15
+ }
16
+ function configPath(root) {
17
+ return path.join(root, ".hq", "config.json");
18
+ }
19
+ function writeConfig(root, contents) {
20
+ const p = configPath(root);
21
+ fs.mkdirSync(path.dirname(p), { recursive: true });
22
+ fs.writeFileSync(p, contents, "utf8");
23
+ return p;
24
+ }
25
+ function readConfig(root) {
26
+ return JSON.parse(fs.readFileSync(configPath(root), "utf8"));
27
+ }
28
+ describe("seedActiveCompany", () => {
29
+ it("seeds the single company when activeCompany is unset", () => {
30
+ hqRoot = makeHqRoot();
31
+ writeConfig(hqRoot, JSON.stringify({ someOtherSetting: true }));
32
+ expect(seedActiveCompany({ hqRoot, companySlugs: ["boring-ecom"] })).toEqual({
33
+ written: true,
34
+ activeCompany: "boring-ecom",
35
+ });
36
+ expect(readConfig(hqRoot)).toEqual({
37
+ someOtherSetting: true,
38
+ activeCompany: "boring-ecom",
39
+ });
40
+ });
41
+ it("creates a missing config file", () => {
42
+ hqRoot = makeHqRoot();
43
+ expect(fs.existsSync(configPath(hqRoot))).toBe(false);
44
+ expect(seedActiveCompany({ hqRoot, companySlugs: ["boring-ecom"] })).toEqual({
45
+ written: true,
46
+ activeCompany: "boring-ecom",
47
+ });
48
+ expect(readConfig(hqRoot)).toEqual({ activeCompany: "boring-ecom" });
49
+ });
50
+ it("no-ops on zero companies", () => {
51
+ hqRoot = makeHqRoot();
52
+ const before = writeConfig(hqRoot, JSON.stringify({ keep: 1 }));
53
+ const raw = fs.readFileSync(before, "utf8");
54
+ expect(seedActiveCompany({ hqRoot, companySlugs: [] })).toEqual({
55
+ written: false,
56
+ skippedReason: "no-companies",
57
+ });
58
+ expect(fs.readFileSync(before, "utf8")).toBe(raw);
59
+ });
60
+ // Two companies is a real choice. Guessing would route the user somewhere
61
+ // arbitrary, which is indistinguishable from a bug to the person it happens to.
62
+ it("no-ops on two or more companies rather than guessing", () => {
63
+ hqRoot = makeHqRoot();
64
+ const p = writeConfig(hqRoot, JSON.stringify({ keep: 1 }));
65
+ const raw = fs.readFileSync(p, "utf8");
66
+ expect(seedActiveCompany({ hqRoot, companySlugs: ["boring-ecom", "acme"] })).toEqual({ written: false, skippedReason: "ambiguous" });
67
+ expect(fs.readFileSync(p, "utf8")).toBe(raw);
68
+ });
69
+ it("never overwrites an existing activeCompany", () => {
70
+ hqRoot = makeHqRoot();
71
+ const p = writeConfig(hqRoot, JSON.stringify({ activeCompany: "chosen-by-user" }));
72
+ const raw = fs.readFileSync(p, "utf8");
73
+ expect(seedActiveCompany({ hqRoot, companySlugs: ["boring-ecom"] })).toEqual({
74
+ written: false,
75
+ skippedReason: "already-set",
76
+ });
77
+ expect(fs.readFileSync(p, "utf8")).toBe(raw);
78
+ });
79
+ // Even when the existing value names a company they have since left. Repairing
80
+ // that is a separate, user-visible decision — not something a sync should do
81
+ // silently underneath them.
82
+ it("never overwrites activeCompany even for a company the user has left", () => {
83
+ hqRoot = makeHqRoot();
84
+ writeConfig(hqRoot, JSON.stringify({ activeCompany: "former-employer" }));
85
+ expect(seedActiveCompany({ hqRoot, companySlugs: ["boring-ecom"] })).toEqual({
86
+ written: false,
87
+ skippedReason: "already-set",
88
+ });
89
+ expect(readConfig(hqRoot).activeCompany).toBe("former-employer");
90
+ });
91
+ // sync-core's resolveActiveCompany falls back to `companySlug`, so a config
92
+ // carrying only that already HAS an effective answer. Writing activeCompany
93
+ // would silently re-route the user.
94
+ it("treats a legacy companySlug as already-set", () => {
95
+ hqRoot = makeHqRoot();
96
+ const p = writeConfig(hqRoot, JSON.stringify({ companySlug: "legacy-choice" }));
97
+ const raw = fs.readFileSync(p, "utf8");
98
+ expect(seedActiveCompany({ hqRoot, companySlugs: ["boring-ecom"] })).toEqual({
99
+ written: false,
100
+ skippedReason: "already-set",
101
+ });
102
+ expect(fs.readFileSync(p, "utf8")).toBe(raw);
103
+ });
104
+ it("leaves malformed JSON untouched and reports a diagnostic", () => {
105
+ hqRoot = makeHqRoot();
106
+ const p = writeConfig(hqRoot, '{"activeCompany": "half-edited"');
107
+ const raw = fs.readFileSync(p, "utf8");
108
+ const diagnostics = [];
109
+ expect(seedActiveCompany({
110
+ hqRoot,
111
+ companySlugs: ["boring-ecom"],
112
+ reportDiagnostic: (d) => diagnostics.push(d),
113
+ })).toEqual({ written: false, skippedReason: "unreadable-config" });
114
+ expect(fs.readFileSync(p, "utf8")).toBe(raw);
115
+ expect(diagnostics).toHaveLength(1);
116
+ expect(diagnostics[0].event).toBe("runner.active_company.config_malformed");
117
+ });
118
+ it("leaves a non-object config untouched", () => {
119
+ hqRoot = makeHqRoot();
120
+ const p = writeConfig(hqRoot, "[1, 2, 3]");
121
+ const raw = fs.readFileSync(p, "utf8");
122
+ expect(seedActiveCompany({ hqRoot, companySlugs: ["boring-ecom"] })).toEqual({
123
+ written: false,
124
+ skippedReason: "unreadable-config",
125
+ });
126
+ expect(fs.readFileSync(p, "utf8")).toBe(raw);
127
+ });
128
+ it("preserves unknown keys and leaves no temp files behind", () => {
129
+ hqRoot = makeHqRoot();
130
+ writeConfig(hqRoot, JSON.stringify({ nested: { a: 1 }, list: [1, 2], flag: false }));
131
+ expect(seedActiveCompany({ hqRoot, companySlugs: ["boring-ecom"] }).written).toBe(true);
132
+ expect(readConfig(hqRoot)).toEqual({
133
+ nested: { a: 1 },
134
+ list: [1, 2],
135
+ flag: false,
136
+ activeCompany: "boring-ecom",
137
+ });
138
+ const strays = fs
139
+ .readdirSync(path.join(hqRoot, ".hq"))
140
+ .filter((f) => f.startsWith(".config-"));
141
+ expect(strays).toEqual([]);
142
+ });
143
+ it("collapses duplicate slugs — one company named twice is not ambiguous", () => {
144
+ hqRoot = makeHqRoot();
145
+ expect(seedActiveCompany({ hqRoot, companySlugs: ["boring-ecom", "boring-ecom"] })).toEqual({ written: true, activeCompany: "boring-ecom" });
146
+ expect(readConfig(hqRoot).activeCompany).toBe("boring-ecom");
147
+ });
148
+ });
149
+ //# sourceMappingURL=active-company.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-company.test.js","sourceRoot":"","sources":["../src/active-company.test.ts"],"names":[],"mappings":"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"}
@@ -22,11 +22,35 @@ export interface RunnerTarget {
22
22
  slug: string;
23
23
  name?: string;
24
24
  bucketName?: string;
25
+ /**
26
+ * Entity liveness as observed AT PLAN TIME, copied off the `entity.get`
27
+ * result this plan was built from. Carried so a downstream consumer can make
28
+ * a liveness decision without paying a second round trip.
29
+ *
30
+ * These deliberately do NOT gate SYNC. A suspended company still syncs — that
31
+ * is user-visible behavior and not ours to change here. They exist so the
32
+ * MANIFEST decision (see `manifest-reconcile.ts`) can be as strict as it was
33
+ * when it re-fetched the entity itself. Absent fields mean "unknown", and
34
+ * every consumer of them is required to fail closed.
35
+ */
36
+ entityType?: string;
37
+ entityStatus?: string;
25
38
  personalMode?: boolean;
26
39
  journalSlug?: string;
27
40
  }
41
+ /**
42
+ * Why a run cannot proceed to a fanout.
43
+ *
44
+ * - `no-memberships` — the caller resolved fine but belongs to no company.
45
+ * Usually a joiner whose invite is still pending acceptance.
46
+ * - `no-person-entity` — the caller is signed in but has no personal entity to
47
+ * sync into, so even `--personal` has no target.
48
+ */
49
+ export type SetupNeededReason = "no-memberships" | "no-person-entity";
28
50
  export type MembershipResolution = {
29
51
  status: "setup-needed";
52
+ reason: SetupNeededReason;
53
+ pendingInviteCount?: number;
30
54
  } | {
31
55
  status: "memberships";
32
56
  memberships: Pick<Membership, "companyUid">[];
@@ -40,9 +64,10 @@ export declare function resolveMembershipsForRun(options: {
40
64
  stderr: {
41
65
  write: (chunk: string) => boolean | void;
42
66
  };
67
+ /** Returns how many invites were still pending after the claim attempt. */
43
68
  runClaimDance: (client: VaultClientSurface, claims: IdentityClaims, stderr: {
44
69
  write: (chunk: string) => boolean | void;
45
- }) => Promise<void>;
70
+ }) => Promise<number>;
46
71
  listMemberships: (client: VaultClientSurface) => Promise<Pick<Membership, "companyUid">[]>;
47
72
  }): Promise<MembershipResolution>;
48
73
  export declare function buildFanoutPlan(options: {
@@ -55,6 +80,7 @@ export declare function buildFanoutPlan(options: {
55
80
  resolveSkipPersonal: (flag: boolean) => boolean;
56
81
  }): Promise<{
57
82
  status: "setup-needed";
83
+ reason: SetupNeededReason;
58
84
  } | {
59
85
  status: "plan";
60
86
  plan: RunnerTarget[];
@@ -1 +1 @@
1
- {"version":3,"file":"sync-runner-planning.d.ts","sourceRoot":"","sources":["../../src/bin/sync-runner-planning.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAMlF,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAExE,UAAU,cAAc;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,oBAAoB,GAC5B;IAAE,MAAM,EAAE,cAAc,CAAA;CAAE,GAC1B;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,WAAW,EAAE,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,CAAA;CAAE,CAAC;AAE7E,wBAAsB,wBAAwB,CAAC,OAAO,EAAE;IACtD,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE;QAAE,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC;IACrD,aAAa,EAAE,CACb,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE;QAAE,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAA;KAAE,KACjD,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,eAAe,EAAE,CACf,MAAM,EAAE,kBAAkB,KACvB,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC;CAChD,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAyBhC;AAED,wBAAsB,eAAe,CAAC,OAAO,EAAE;IAC7C,WAAW,EAAE,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,CAAC;IAC9C,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,mBAAmB,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;CACjD,GAAG,OAAO,CACP;IAAE,MAAM,EAAE,cAAc,CAAA;CAAE,GAC1B;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,YAAY,EAAE,CAAA;CAAE,CAC3C,CAoEA;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,EAClC,IAAI,EAAE,YAAY,EAAE,GACnB,IAAI,CAEN"}
1
+ {"version":3,"file":"sync-runner-planning.d.ts","sourceRoot":"","sources":["../../src/bin/sync-runner-planning.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAMlF,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAExE,UAAU,cAAc;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;;;OAUG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;AAEtE,MAAM,MAAM,oBAAoB,GAC5B;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAAE,GAClF;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,WAAW,EAAE,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,CAAA;CAAE,CAAC;AAE7E,wBAAsB,wBAAwB,CAAC,OAAO,EAAE;IACtD,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE;QAAE,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC;IACrD,2EAA2E;IAC3E,aAAa,EAAE,CACb,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE;QAAE,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAA;KAAE,KACjD,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,eAAe,EAAE,CACf,MAAM,EAAE,kBAAkB,KACvB,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC;CAChD,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAkChC;AAED,wBAAsB,eAAe,CAAC,OAAO,EAAE;IAC7C,WAAW,EAAE,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,CAAC;IAC9C,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,mBAAmB,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;CACjD,GAAG,OAAO,CACP;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAA;CAAE,GACrD;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,YAAY,EAAE,CAAA;CAAE,CAC3C,CAwFA;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,EAClC,IAAI,EAAE,YAAY,EAAE,GACnB,IAAI,CAEN"}
@@ -6,12 +6,21 @@ export async function resolveMembershipsForRun(options) {
6
6
  return { status: "memberships", memberships: [] };
7
7
  }
8
8
  if (options.companies) {
9
+ let pendingInviteCount = 0;
9
10
  if (options.claims) {
10
- await options.runClaimDance(options.client, options.claims, options.stderr);
11
+ pendingInviteCount = await options.runClaimDance(options.client, options.claims, options.stderr);
11
12
  }
12
13
  const memberships = await options.listMemberships(options.client);
13
14
  if (memberships.length === 0) {
14
- return { status: "setup-needed" };
15
+ // Pending invites that did NOT become memberships are the single most
16
+ // useful thing we can tell this user: they are not "solo", they have an
17
+ // invite waiting to be accepted. Reported only on the empty path, where
18
+ // it explains the emptiness.
19
+ return {
20
+ status: "setup-needed",
21
+ reason: "no-memberships",
22
+ ...(pendingInviteCount > 0 ? { pendingInviteCount } : {}),
23
+ };
15
24
  }
16
25
  return { status: "memberships", memberships };
17
26
  }
@@ -25,12 +34,25 @@ export async function buildFanoutPlan(options) {
25
34
  for (const m of options.memberships) {
26
35
  let slug = m.companyUid;
27
36
  let name;
37
+ // Carried onto the target so downstream consumers (manifest reconciliation)
38
+ // can use the bucket, and judge liveness, without re-fetching the entity we
39
+ // already resolved here. On the degraded catch path below these stay
40
+ // undefined, which every consumer must read as "unknown" and reject.
41
+ let bucketName;
42
+ let entityType;
43
+ let entityStatus;
28
44
  try {
29
45
  const info = await options.client.entity.get(m.companyUid);
30
46
  if (!info)
31
47
  continue;
32
48
  slug = info.slug || m.companyUid;
33
49
  name = info.name;
50
+ bucketName = info.bucketName;
51
+ // Recorded, never acted on here: a suspended or unexpectedly-typed
52
+ // company still gets a sync leg exactly as before. Only the manifest
53
+ // decision consumes these.
54
+ entityType = info.type;
55
+ entityStatus = info.status;
34
56
  }
35
57
  catch (err) {
36
58
  if (err instanceof VaultAuthError)
@@ -48,7 +70,14 @@ export async function buildFanoutPlan(options) {
48
70
  }
49
71
  // Best-effort — keep UID as the display identifier.
50
72
  }
51
- plan.push({ uid: m.companyUid, slug, ...(name ? { name } : {}) });
73
+ plan.push({
74
+ uid: m.companyUid,
75
+ slug,
76
+ ...(name ? { name } : {}),
77
+ ...(bucketName ? { bucketName } : {}),
78
+ ...(entityType ? { entityType } : {}),
79
+ ...(entityStatus ? { entityStatus } : {}),
80
+ });
52
81
  }
53
82
  if ((options.companies || options.personal) &&
54
83
  !options.resolveSkipPersonal(options.skipPersonal)) {
@@ -81,7 +110,7 @@ export async function buildFanoutPlan(options) {
81
110
  });
82
111
  }
83
112
  else if (options.personal) {
84
- return { status: "setup-needed" };
113
+ return { status: "setup-needed", reason: "no-person-entity" };
85
114
  }
86
115
  }
87
116
  return { status: "plan", plan };
@@ -1 +1 @@
1
- {"version":3,"file":"sync-runner-planning.js","sourceRoot":"","sources":["../../src/bin/sync-runner-planning.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sync-runner-planning.js","sourceRoot":"","sources":["../../src/bin/sync-runner-planning.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sync-runner-planning.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-runner-planning.test.d.ts","sourceRoot":"","sources":["../../src/bin/sync-runner-planning.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,115 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { buildFanoutPlan, resolveMembershipsForRun } from "./sync-runner-planning.js";
3
+ const noopStderr = { write: () => true };
4
+ function clientStub(overrides = {}) {
5
+ return {
6
+ entity: {
7
+ get: () => Promise.resolve(undefined),
8
+ listByType: () => Promise.resolve([]),
9
+ },
10
+ ...overrides,
11
+ };
12
+ }
13
+ describe("resolveMembershipsForRun — setup-needed reasons", () => {
14
+ // Each reason must come from its OWN trigger. Collapsing them back into a
15
+ // bare `setup-needed` is what left joiners unable to tell "you have no team"
16
+ // apart from "you have an invite waiting".
17
+ it("reports no-memberships when the membership list came back empty", async () => {
18
+ const result = await resolveMembershipsForRun({
19
+ personal: false,
20
+ companies: true,
21
+ client: clientStub(),
22
+ claims: null,
23
+ stderr: noopStderr,
24
+ runClaimDance: () => Promise.resolve(0),
25
+ listMemberships: () => Promise.resolve([]),
26
+ });
27
+ expect(result).toEqual({ status: "setup-needed", reason: "no-memberships" });
28
+ });
29
+ it("attaches pendingInviteCount when the claim dance left invites unconverted", async () => {
30
+ const result = await resolveMembershipsForRun({
31
+ personal: false,
32
+ companies: true,
33
+ client: clientStub(),
34
+ claims: { sub: "sub-1" },
35
+ stderr: noopStderr,
36
+ runClaimDance: () => Promise.resolve(3),
37
+ listMemberships: () => Promise.resolve([]),
38
+ });
39
+ expect(result).toEqual({
40
+ status: "setup-needed",
41
+ reason: "no-memberships",
42
+ pendingInviteCount: 3,
43
+ });
44
+ });
45
+ it("omits pendingInviteCount rather than reporting zero", async () => {
46
+ const result = await resolveMembershipsForRun({
47
+ personal: false,
48
+ companies: true,
49
+ client: clientStub(),
50
+ claims: { sub: "sub-1" },
51
+ stderr: noopStderr,
52
+ runClaimDance: () => Promise.resolve(0),
53
+ listMemberships: () => Promise.resolve([]),
54
+ });
55
+ expect(result).toEqual({ status: "setup-needed", reason: "no-memberships" });
56
+ expect("pendingInviteCount" in result).toBe(false);
57
+ });
58
+ it("does not report setup-needed at all when memberships exist", async () => {
59
+ const runClaimDance = vi.fn().mockResolvedValue(2);
60
+ const result = await resolveMembershipsForRun({
61
+ personal: false,
62
+ companies: true,
63
+ client: clientStub(),
64
+ claims: { sub: "sub-1" },
65
+ stderr: noopStderr,
66
+ runClaimDance,
67
+ listMemberships: () => Promise.resolve([{ companyUid: "cmp_a" }]),
68
+ });
69
+ // Pending invites are only meaningful as an explanation for emptiness.
70
+ expect(result).toEqual({
71
+ status: "memberships",
72
+ memberships: [{ companyUid: "cmp_a" }],
73
+ });
74
+ });
75
+ });
76
+ describe("buildFanoutPlan — setup-needed reasons", () => {
77
+ it("reports no-person-entity when --personal has no resolvable personal target", async () => {
78
+ const result = await buildFanoutPlan({
79
+ memberships: [],
80
+ companies: false,
81
+ personal: true,
82
+ skipPersonal: false,
83
+ client: clientStub(),
84
+ claims: null,
85
+ resolveSkipPersonal: () => false,
86
+ });
87
+ expect(result).toEqual({ status: "setup-needed", reason: "no-person-entity" });
88
+ });
89
+ // A --companies run does not need a personal target to be useful, so an
90
+ // unresolvable one must not abort the whole run.
91
+ it("does not report no-person-entity for a --companies run", async () => {
92
+ const result = await buildFanoutPlan({
93
+ memberships: [{ companyUid: "cmp_a" }],
94
+ companies: true,
95
+ personal: false,
96
+ skipPersonal: false,
97
+ client: clientStub({
98
+ entity: {
99
+ get: () => Promise.resolve({
100
+ uid: "cmp_a",
101
+ slug: "acme",
102
+ type: "company",
103
+ status: "active",
104
+ bucketName: "bucket-a",
105
+ }),
106
+ listByType: () => Promise.resolve([]),
107
+ },
108
+ }),
109
+ claims: null,
110
+ resolveSkipPersonal: () => false,
111
+ });
112
+ expect(result.status).toBe("plan");
113
+ });
114
+ });
115
+ //# sourceMappingURL=sync-runner-planning.test.js.map