@indigoai-us/hq-cloud 6.11.20 → 6.12.0

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.
@@ -1,14 +1,17 @@
1
1
  /**
2
2
  * Unit tests for the personal-vault path-discovery helpers.
3
3
  *
4
- * These cover the column-anchored `cloud: false` marker regex and the
5
- * `companies/{slug}/` enumeration filter — both are pure functions over
4
+ * These cover the column-anchored `cloud: true` exclusion-marker regex and
5
+ * the `companies/{slug}/` enumeration filter — both are pure functions over
6
6
  * disk + caller-supplied option sets, so we exercise them against tmp
7
- * directories rather than mocking fs.
7
+ * directories rather than mocking fs. Local (non-cloud) companies sync to
8
+ * the personal vault by DEFAULT: a missing/markerless `company.yaml` is
9
+ * INCLUDED; only an explicit `cloud: true` marker (or membership via
10
+ * `teamSyncedSlugs`) opts a company out.
8
11
  *
9
- * The sync-runner-level integration (env-var gate, team-synced slug
10
- * derivation, end-to-end share() invocation) lives in
11
- * `bin/sync-runner.test.ts` — tests G, H, I.
12
+ * The sync-runner-level integration (team-synced slug derivation,
13
+ * decommission of cloud-promoted companies, end-to-end share() invocation)
14
+ * lives in `bin/sync-runner.test.ts` — tests G, H, I.
12
15
  */
13
16
 
14
17
  import { describe, it, expect, beforeEach, afterEach } from "vitest";
@@ -63,108 +66,125 @@ describe("personal-vault helpers", () => {
63
66
  expect(PERSONAL_VAULT_COMPANY_EXCLUDED_SLUGS).toContain("_template");
64
67
  });
65
68
 
66
- // ── computePersonalCompanySubdirs: marker-regex acceptance ─────────────
67
- it("marker: accepts canonical `cloud: false` at column 0", () => {
68
- writeCompany("foo", "cloud: false\nname: Foo\n");
69
+ // ── computePersonalCompanySubdirs: default inclusion ───────────────────
70
+ // Local companies sync to the personal vault by default. A missing or
71
+ // markerless company.yaml is INCLUDED — only an explicit `cloud: true`
72
+ // marker (or membership) opts a company OUT.
73
+ it("default: includes a company with NO company.yaml", () => {
74
+ writeCompany("foo", null);
69
75
  expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
70
76
  path.join("companies", "foo"),
71
77
  ]);
72
78
  });
73
79
 
74
- it("marker: accepts `cloud:false` (no space after colon)", () => {
75
- writeCompany("foo", "cloud:false\n");
80
+ it("default: includes a company with an empty company.yaml", () => {
81
+ writeCompany("foo", "");
76
82
  expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
77
83
  path.join("companies", "foo"),
78
84
  ]);
79
85
  });
80
86
 
81
- it("marker: accepts trailing comment after `cloud: false`", () => {
82
- writeCompany("foo", "cloud: false # local-only, never upload to team\n");
87
+ it("default: includes a company explicitly marked `cloud: false`", () => {
88
+ writeCompany("foo", "cloud: false\nname: Foo\n");
83
89
  expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
84
90
  path.join("companies", "foo"),
85
91
  ]);
86
92
  });
87
93
 
88
- it("marker: accepts CRLF line endings", () => {
89
- writeCompany("foo", "cloud: false\r\nname: Foo\r\n");
94
+ it("default: includes a company.yaml with no `cloud:` key at all", () => {
95
+ writeCompany("foo", "name: Foo\nslug: foo\n");
90
96
  expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
91
97
  path.join("companies", "foo"),
92
98
  ]);
93
99
  });
94
100
 
95
- it("marker: accepts `cloud: false` even when it is NOT the first line", () => {
96
- writeCompany("foo", "name: Foo\nslug: foo\ncloud: false\n");
97
- expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
98
- path.join("companies", "foo"),
99
- ]);
101
+ // ── computePersonalCompanySubdirs: `cloud: true` exclusion-marker ──────
102
+ it("marker: excludes canonical `cloud: true` at column 0", () => {
103
+ writeCompany("foo", "cloud: true\nname: Foo\n");
104
+ expect(computePersonalCompanySubdirs(hqRoot)).toEqual([]);
100
105
  });
101
106
 
102
- // ── computePersonalCompanySubdirs: marker-regex rejection ──────────────
103
- it("marker: rejects `cloud: true`", () => {
104
- writeCompany("foo", "cloud: true\n");
107
+ it("marker: excludes `cloud:true` (no space after colon)", () => {
108
+ writeCompany("foo", "cloud:true\n");
105
109
  expect(computePersonalCompanySubdirs(hqRoot)).toEqual([]);
106
110
  });
107
111
 
108
- it("marker: rejects missing company.yaml entirely", () => {
109
- writeCompany("foo", null);
112
+ it("marker: excludes with a trailing comment after `cloud: true`", () => {
113
+ writeCompany("foo", "cloud: true # cloud-backed, synced to team bucket\n");
110
114
  expect(computePersonalCompanySubdirs(hqRoot)).toEqual([]);
111
115
  });
112
116
 
113
- it("marker: rejects empty company.yaml", () => {
114
- writeCompany("foo", "");
117
+ it("marker: excludes with CRLF line endings", () => {
118
+ writeCompany("foo", "cloud: true\r\nname: Foo\r\n");
115
119
  expect(computePersonalCompanySubdirs(hqRoot)).toEqual([]);
116
120
  });
117
121
 
118
- it("marker: rejects nested `cloud: false` (column-0 anchor false-positive guard)", () => {
119
- // Regression for an audit finding: an earlier `^[ \t]*cloud:` regex
120
- // allowed arbitrary leading whitespace, which silently matched
121
- // nested keys like `meta.cloud = false`. The tightened `^cloud:` regex
122
- // rejects this, matching the canonical column-0 shape that
123
- // `/designate-team`'s awk emits.
124
- writeCompany("foo", "meta:\n cloud: false\n");
122
+ it("marker: excludes `cloud: true` even when it is NOT the first line", () => {
123
+ writeCompany("foo", "name: Foo\nslug: foo\ncloud: true\n");
125
124
  expect(computePersonalCompanySubdirs(hqRoot)).toEqual([]);
126
125
  });
127
126
 
128
- it("marker: rejects list-item style `- cloud: false`", () => {
129
- writeCompany("foo", "tags:\n- cloud: false\n");
130
- expect(computePersonalCompanySubdirs(hqRoot)).toEqual([]);
127
+ // ── computePersonalCompanySubdirs: marker fails OPEN (still included) ───
128
+ // A marker that isn't the canonical column-0 lowercase `cloud: true`
129
+ // shape `/designate-team` emits does NOT exclude — the company stays in
130
+ // the personal vault, which is the safe default.
131
+ it("marker: nested `cloud: true` does NOT exclude (column-0 anchor)", () => {
132
+ writeCompany("foo", "meta:\n cloud: true\n");
133
+ expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
134
+ path.join("companies", "foo"),
135
+ ]);
131
136
  });
132
137
 
133
- it("marker: rejects flow-mapping `{ cloud: false }`", () => {
134
- writeCompany("foo", "{ cloud: false }\n");
135
- expect(computePersonalCompanySubdirs(hqRoot)).toEqual([]);
138
+ it("marker: list-item style `- cloud: true` does NOT exclude", () => {
139
+ writeCompany("foo", "tags:\n- cloud: true\n");
140
+ expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
141
+ path.join("companies", "foo"),
142
+ ]);
143
+ });
144
+
145
+ it("marker: flow-mapping `{ cloud: true }` does NOT exclude", () => {
146
+ writeCompany("foo", "{ cloud: true }\n");
147
+ expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
148
+ path.join("companies", "foo"),
149
+ ]);
136
150
  });
137
151
 
138
- it("marker: rejects YAML-alt boolean spellings (False, FALSE, no, off)", () => {
139
- for (const variant of ["cloud: False\n", "cloud: FALSE\n", "cloud: no\n", "cloud: off\n"]) {
152
+ it("marker: YAML-alt boolean spellings (True, TRUE, yes, on) do NOT exclude", () => {
153
+ for (const variant of ["cloud: True\n", "cloud: TRUE\n", "cloud: yes\n", "cloud: on\n"]) {
140
154
  writeCompany("foo", variant);
141
- expect(computePersonalCompanySubdirs(hqRoot)).toEqual([]);
155
+ expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
156
+ path.join("companies", "foo"),
157
+ ]);
142
158
  fs.rmSync(path.join(hqRoot, "companies", "foo"), { recursive: true });
143
159
  }
144
160
  });
145
161
 
146
- it("marker: rejects quoted `cloud: \"false\"` (the quote breaks the literal-false match)", () => {
147
- writeCompany("foo", 'cloud: "false"\n');
148
- expect(computePersonalCompanySubdirs(hqRoot)).toEqual([]);
162
+ it("marker: quoted `cloud: \"true\"` does NOT exclude (quote breaks the literal-true match)", () => {
163
+ writeCompany("foo", 'cloud: "true"\n');
164
+ expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
165
+ path.join("companies", "foo"),
166
+ ]);
149
167
  });
150
168
 
151
- it("marker: rejects `cloud: falsehood` (word-boundary guard)", () => {
152
- writeCompany("foo", "cloud: falsehood\n");
153
- expect(computePersonalCompanySubdirs(hqRoot)).toEqual([]);
169
+ it("marker: `cloud: truthy` does NOT exclude (word-boundary guard)", () => {
170
+ writeCompany("foo", "cloud: truthy\n");
171
+ expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
172
+ path.join("companies", "foo"),
173
+ ]);
154
174
  });
155
175
 
156
176
  // ── Subdir-level filters ───────────────────────────────────────────────
157
- it("filter: skips _template even with cloud:false marker", () => {
158
- writeCompany("_template", "cloud: false\n");
159
- writeCompany("real", "cloud: false\n");
177
+ it("filter: skips _template even when it would otherwise be included", () => {
178
+ writeCompany("_template", null);
179
+ writeCompany("real", null);
160
180
  expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
161
181
  path.join("companies", "real"),
162
182
  ]);
163
183
  });
164
184
 
165
- it("filter: skips slugs in the teamSyncedSlugs set", () => {
166
- writeCompany("free", "cloud: false\n");
167
- writeCompany("team-acme", "cloud: false\n");
185
+ it("filter: skips slugs in the teamSyncedSlugs set (cloud-backed)", () => {
186
+ writeCompany("free", null);
187
+ writeCompany("team-acme", null);
168
188
  expect(
169
189
  rel(computePersonalCompanySubdirs(hqRoot, new Set(["team-acme"]))),
170
190
  ).toEqual([path.join("companies", "free")]);
@@ -175,7 +195,7 @@ describe("personal-vault helpers", () => {
175
195
  // crash, should not be enumerated as a company subdir.
176
196
  fs.mkdirSync(path.join(hqRoot, "companies"), { recursive: true });
177
197
  fs.writeFileSync(path.join(hqRoot, "companies", "README.md"), "# README");
178
- writeCompany("real", "cloud: false\n");
198
+ writeCompany("real", null);
179
199
  expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
180
200
  path.join("companies", "real"),
181
201
  ]);
@@ -187,40 +207,31 @@ describe("personal-vault helpers", () => {
187
207
  });
188
208
 
189
209
  // ── computePersonalVaultPaths: top-level + company-subdir composition ──
190
- it("composition: includeLocalCompanies=false skips companies/ entirely", () => {
210
+ it("composition: local company subdirs are included by default (no opt-in flag)", () => {
191
211
  fs.mkdirSync(path.join(hqRoot, ".claude"));
192
212
  fs.mkdirSync(path.join(hqRoot, "knowledge"));
193
- writeCompany("foo", "cloud: false\n");
213
+ writeCompany("foo", null);
214
+ writeCompany("bar", "cloud: true\n");
194
215
 
195
216
  const out = rel(computePersonalVaultPaths(hqRoot));
196
- // Top-level entries present, no companies/* subdir.
217
+ // Top-level entries present.
197
218
  expect(out).toContain(".claude");
198
219
  expect(out).toContain("knowledge");
199
- expect(out.some((p) => p.startsWith("companies"))).toBe(false);
200
- });
201
-
202
- it("composition: includeLocalCompanies=true adds opt-in company subdirs", () => {
203
- fs.mkdirSync(path.join(hqRoot, "knowledge"));
204
- writeCompany("foo", "cloud: false\n");
205
- writeCompany("bar", "cloud: true\n");
206
-
207
- const out = rel(computePersonalVaultPaths(hqRoot, { includeLocalCompanies: true }));
208
- expect(out).toContain("knowledge");
220
+ // Local (non-cloud) company included; cloud-marked one excluded.
209
221
  expect(out).toContain(path.join("companies", "foo"));
210
222
  expect(out).not.toContain(path.join("companies", "bar"));
211
223
  // The companies/ top-level entry itself is never present — only specific
212
- // opted-in subdirs. Preserves the invariant that share() never walks
224
+ // eligible subdirs. Preserves the invariant that share() never walks
213
225
  // the whole companies/ tree under personalMode.
214
226
  expect(out).not.toContain("companies");
215
227
  });
216
228
 
217
- it("composition: includeLocalCompanies=true + teamSyncedSlugs filter combine correctly", () => {
218
- writeCompany("foo", "cloud: false\n");
219
- writeCompany("synced", "cloud: false\n");
229
+ it("composition: teamSyncedSlugs filter excludes cloud-backed companies", () => {
230
+ writeCompany("foo", null);
231
+ writeCompany("synced", null);
220
232
 
221
233
  const out = rel(
222
234
  computePersonalVaultPaths(hqRoot, {
223
- includeLocalCompanies: true,
224
235
  teamSyncedSlugs: new Set(["synced"]),
225
236
  }),
226
237
  );
@@ -230,7 +241,7 @@ describe("personal-vault helpers", () => {
230
241
 
231
242
  it("composition: missing hqRoot returns []", () => {
232
243
  fs.rmSync(hqRoot, { recursive: true });
233
- expect(computePersonalVaultPaths(hqRoot, { includeLocalCompanies: true })).toEqual([]);
244
+ expect(computePersonalVaultPaths(hqRoot)).toEqual([]);
234
245
  });
235
246
 
236
247
  // ── companies/manifest.yaml inclusion ─────────────────────────────────
@@ -254,16 +265,14 @@ describe("personal-vault helpers", () => {
254
265
  expect(out).toContain(".claude");
255
266
  });
256
267
 
257
- it("manifest: included even when includeLocalCompanies=false (manifest is unconditional)", () => {
268
+ it("manifest: included even when there are no eligible company subdirs", () => {
258
269
  fs.mkdirSync(path.join(hqRoot, "companies"));
259
270
  fs.writeFileSync(
260
271
  path.join(hqRoot, "companies", "manifest.yaml"),
261
272
  "companies: {}\n",
262
273
  );
263
274
 
264
- const out = rel(
265
- computePersonalVaultPaths(hqRoot, { includeLocalCompanies: false }),
266
- );
275
+ const out = rel(computePersonalVaultPaths(hqRoot));
267
276
  expect(out).toContain(path.join("companies", "manifest.yaml"));
268
277
  });
269
278
 
@@ -303,18 +312,17 @@ describe("personal-vault helpers", () => {
303
312
  expect(out).not.toContain(path.join("companies", "README.md"));
304
313
  });
305
314
 
306
- it("manifest: composes with includeLocalCompanies=true + local company subdirs", () => {
315
+ it("manifest: composes with local company subdirs", () => {
307
316
  fs.mkdirSync(path.join(hqRoot, "companies"));
308
317
  fs.writeFileSync(
309
318
  path.join(hqRoot, "companies", "manifest.yaml"),
310
319
  "companies:\n foo: {cloud_uid: null}\n team-acme: {cloud_uid: cmp_01XYZ}\n",
311
320
  );
312
- writeCompany("foo", "cloud: false\n");
321
+ writeCompany("foo", null);
313
322
  writeCompany("team-acme", "cloud: true\n");
314
323
 
315
324
  const out = rel(
316
325
  computePersonalVaultPaths(hqRoot, {
317
- includeLocalCompanies: true,
318
326
  teamSyncedSlugs: new Set(["team-acme"]),
319
327
  }),
320
328
  );
@@ -344,7 +352,7 @@ describe("personal-vault helpers", () => {
344
352
  expect(computePersonalCompanySubdirs(hqRoot)).toEqual([]);
345
353
 
346
354
  // Push set carries the real manifest, never the conflict litter.
347
- const out = rel(computePersonalVaultPaths(hqRoot, { includeLocalCompanies: true }));
355
+ const out = rel(computePersonalVaultPaths(hqRoot));
348
356
  expect(out).toContain(path.join("companies", "manifest.yaml"));
349
357
  expect(
350
358
  out.some((p) => p.includes("manifest.yaml.conflict-")),
@@ -17,11 +17,13 @@
17
17
  * - `companies/`: the top-level `companies/` directory is never enumerated
18
18
  * wholesale. Team-backed companies are synced by the runner's
19
19
  * per-membership fanout (one bucket per company). Individual
20
- * `companies/{slug}/` subdirs MAY be added back via
21
- * `computePersonalCompanySubdirs()` for companies that explicitly
22
- * declare `cloud: false` in `company.yaml` and are not in the operator's
23
- * team-synced membership set those land under the personal bucket as
24
- * `companies/{slug}/...` keys.
20
+ * `companies/{slug}/` subdirs are added back via
21
+ * `computePersonalCompanySubdirs()` for every LOCAL (non-cloud) company
22
+ * i.e. any company that is not in the operator's team-synced membership
23
+ * set and is not explicitly marked `cloud: true` in `company.yaml`. Those
24
+ * land under the personal bucket as `companies/{slug}/...` keys. (The
25
+ * single `companies/manifest.yaml` is also carved in unconditionally as
26
+ * the routing source-of-truth.)
25
27
  * - `repos/`, `workspace/`: per user directive — heavy local-only
26
28
  * content (cloned remotes, session threads) that has no business in
27
29
  * the personal vault.
@@ -63,19 +65,12 @@ export interface PersonalVaultOptions {
63
65
  * Slugs of companies that already have their own team bucket (i.e. the
64
66
  * operator has an active Membership row for them). These are excluded
65
67
  * from the personal-bucket fallback so a single company's content never
66
- * ends up in two buckets.
68
+ * ends up in two buckets. This membership-derived set is the single
69
+ * "cloud-backed" signal: when a company becomes cloud-true (gains an
70
+ * active membership), its slug lands here and the personal vault both
71
+ * stops pushing it and decommissions its stale copy.
67
72
  */
68
73
  teamSyncedSlugs?: ReadonlySet<string>;
69
- /**
70
- * When true, walk `companies/` and include subdirs that explicitly opt in
71
- * via `cloud: false` in their `company.yaml` (after applying the standard
72
- * filter: exclude `_template`, exclude team-synced slugs). When false
73
- * (the default), `companies/` is never enumerated — same as the legacy
74
- * personal-vault scope. Gated behind a runtime flag so the new behavior
75
- * stays opt-in until operators explicitly request it via
76
- * `HQ_SYNC_LOCAL_COMPANIES_TO_PERSONAL=1`.
77
- */
78
- includeLocalCompanies?: boolean;
79
74
  }
80
75
 
81
76
  /**
@@ -84,10 +79,12 @@ export interface PersonalVaultOptions {
84
79
  * Two sources are concatenated:
85
80
  * 1. Every top-level entry under `hqRoot` whose basename is NOT in
86
81
  * `PERSONAL_VAULT_EXCLUDED_TOP_LEVEL`.
87
- * 2. Every `companies/{slug}/` subdir that opts into the personal
88
- * bucket via `company.yaml: cloud: false`, after excluding (a)
89
- * `_template`, (b) slugs already in `opts.teamSyncedSlugs`, and
90
- * (c) any subdir without an explicit `cloud: false` marker.
82
+ * 2. Every local (non-cloud) `companies/{slug}/` subdir, after excluding
83
+ * (a) `_template`, (b) slugs already in `opts.teamSyncedSlugs`
84
+ * (cloud-backed they sync to their own team bucket), and (c) any
85
+ * subdir explicitly marked `cloud: true` in its `company.yaml`.
86
+ * A company with no `company.yaml` (or one without a `cloud:` marker)
87
+ * is included — local companies sync to the personal vault by default.
91
88
  *
92
89
  * Order is whatever `fs.readdirSync` returns — share() doesn't care, and
93
90
  * the per-file walk inside share() handles recursion uniformly. Missing
@@ -131,9 +128,10 @@ export function computePersonalVaultPaths(
131
128
  } catch {
132
129
  // Missing or unreadable — silently omit. Callers tolerate empty arrays.
133
130
  }
134
- const companySubdirs = opts.includeLocalCompanies === true
135
- ? computePersonalCompanySubdirs(hqRoot, opts.teamSyncedSlugs)
136
- : [];
131
+ const companySubdirs = computePersonalCompanySubdirs(
132
+ hqRoot,
133
+ opts.teamSyncedSlugs,
134
+ );
137
135
  const continuity = computeContinuityPointerPaths(hqRoot);
138
136
  const agency = computeAgencySyncPaths(hqRoot);
139
137
  return [...topLevel, ...manifest, ...companySubdirs, ...continuity, ...agency];
@@ -273,10 +271,12 @@ export function computeAgencySyncPaths(hqRoot: string): string[] {
273
271
  * (currently just `_template`).
274
272
  * 3. The slug is NOT in `teamSyncedSlugs` (membership-backed slugs sync
275
273
  * to their own bucket and must not be double-written).
276
- * 4. `companies/{slug}/company.yaml` exists and contains a
277
- * `cloud: false` line. A missing file or `cloud: true` opts the
278
- * directory OUT of the personal vault silent inclusion would
279
- * capture scratch/dead dirs the user never intended to ship.
274
+ * 4. `companies/{slug}/company.yaml` does NOT contain an explicit
275
+ * `cloud: true` line. Local companies sync to the personal vault by
276
+ * default, so a missing/markerless `company.yaml` is INCLUDED; only an
277
+ * explicit `cloud: true` marker opts the directory OUT (belt-and-braces
278
+ * with `teamSyncedSlugs` for a company designated cloud-backed before
279
+ * its membership has resolved).
280
280
  *
281
281
  * Returns absolute paths.
282
282
  */
@@ -303,7 +303,7 @@ export function computePersonalCompanySubdirs(
303
303
  continue;
304
304
  }
305
305
  if (!isDir) continue;
306
- if (!companyHasCloudFalseMarker(subdir)) continue;
306
+ if (companyHasCloudTrueMarker(subdir)) continue;
307
307
  eligible.push(subdir);
308
308
  }
309
309
  return eligible;
@@ -311,25 +311,29 @@ export function computePersonalCompanySubdirs(
311
311
 
312
312
  /**
313
313
  * True iff `{subdir}/company.yaml` exists and contains an explicit
314
- * top-level `cloud: false` line. Matches the canonical shape
315
- * `/designate-team` writes that command's awk is the only producer of
316
- * this key, and it always emits `cloud: <bool>` at column zero with no
317
- * indentation, no quoting, lowercase boolean, optional trailing comment.
314
+ * top-level `cloud: true` line the marker `/designate-team` writes when a
315
+ * company is promoted to cloud-backed. That command's awk is the only
316
+ * producer of this key, and it always emits `cloud: <bool>` at column zero
317
+ * with no indentation, no quoting, lowercase boolean, optional trailing
318
+ * comment. A `true` marker excludes the directory from the personal-vault
319
+ * fallback; everything else (missing file, no marker, `cloud: false`)
320
+ * leaves it eligible — local companies sync to the personal vault by default.
318
321
  *
319
322
  * Anchored at column 0 deliberately:
320
- * - rejects `meta:\n cloud: false` (nested key, would be `meta.cloud`
323
+ * - rejects `meta:\n cloud: true` (nested key, would be `meta.cloud`
321
324
  * in a real YAML parser — false positive for us if we allowed
322
325
  * arbitrary leading whitespace)
323
- * - rejects `- cloud: false` (list item, also a nested key)
324
- * - rejects flow mappings like `{ cloud: false }`
326
+ * - rejects `- cloud: true` (list item, also a nested key)
327
+ * - rejects flow mappings like `{ cloud: true }`
325
328
  *
326
- * Other intentional rejections (lowercase `false` only, no YAML-alt
327
- * boolean spellings): `cloud: False`, `cloud: FALSE`, `cloud: no`,
328
- * `cloud: off`, `cloud: "false"`, `cloud: 'false'`. None of these are
329
+ * Other intentional rejections (lowercase `true` only, no YAML-alt
330
+ * boolean spellings): `cloud: True`, `cloud: TRUE`, `cloud: yes`,
331
+ * `cloud: on`, `cloud: "true"`, `cloud: 'true'`. None of these are
329
332
  * produced by `/designate-team`; matching them risks confusing
330
- * round-trips with hand-edited YAML.
333
+ * round-trips with hand-edited YAML. (A misspelled marker fails OPEN —
334
+ * the company stays in the personal vault — which is the safe default.)
331
335
  */
332
- function companyHasCloudFalseMarker(subdir: string): boolean {
336
+ function companyHasCloudTrueMarker(subdir: string): boolean {
333
337
  const yamlPath = path.join(subdir, "company.yaml");
334
338
  let text: string;
335
339
  try {
@@ -337,5 +341,5 @@ function companyHasCloudFalseMarker(subdir: string): boolean {
337
341
  } catch {
338
342
  return false;
339
343
  }
340
- return /^cloud:\s*false\b/m.test(text);
344
+ return /^cloud:\s*true\b/m.test(text);
341
345
  }