@bongos/core 1.19.261 → 1.19.262

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.bongos-core.json CHANGED
@@ -2,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.261",
6
- "core_contract": "1.19.261",
7
- "source_commit": "089106b0f948c6e344f142a4a4f117b4a4f24590",
5
+ "core_version": "1.19.262",
6
+ "core_contract": "1.19.262",
7
+ "source_commit": "5c66bd4246a027582b292909499f6d31009157c6",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-19T19:56:21.487Z",
9
+ "built_at": "2026-08-19T20:21:59.469Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 318,
13
13
  "agent_docs_stubbed": 22,
14
- "functional_verbatim": 1361,
14
+ "functional_verbatim": 1362,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1701,
20
- "tree_sha256": "63c8c6aa4f2f56b3ebd9849547e4403cbf3a56e0c3484f50738f4b679f30baf6",
19
+ "file_count": 1702,
20
+ "tree_sha256": "6c446d9f84793d24dace5316025f4a09517fd85c5260884cb2885e445dcfb2d1",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -382,7 +382,7 @@
382
382
  {
383
383
  "path": "config/branding.neutral.json",
384
384
  "mode": "0000644",
385
- "sha256": "c3d785e319252965f8b5f338908d1dd66bd56985dafacb5d2ab8e5095d2515b3"
385
+ "sha256": "d1d2622e002edaed8a98deb8b69ff5a77c6eeab7993e3b0e7f1513f32ff027c6"
386
386
  },
387
387
  {
388
388
  "path": "config/design-tokens.neutral.json",
@@ -4152,7 +4152,7 @@
4152
4152
  {
4153
4153
  "path": "modules/provisioning/provisioning.js",
4154
4154
  "mode": "0000644",
4155
- "sha256": "051074db6bf131f61a39c236728c65bd74177d9410029b24f5118c9028d64aa5"
4155
+ "sha256": "862fa78c7272a28cdc644debfd8ec364b80b635e8b9a6f41afb76b93312f27dd"
4156
4156
  },
4157
4157
  {
4158
4158
  "path": "modules/provisioning/rate-limit.js",
@@ -4462,12 +4462,12 @@
4462
4462
  {
4463
4463
  "path": "package-lock.json",
4464
4464
  "mode": "0000644",
4465
- "sha256": "296a8f9184663f878f29d9b64dde95ed99bcfd3622f7c7a00592576563922a59"
4465
+ "sha256": "687fe7e65dc80ea4688cbb7e530324b70b97e512f3b786d0264c810e47629388"
4466
4466
  },
4467
4467
  {
4468
4468
  "path": "package.json",
4469
4469
  "mode": "0000644",
4470
- "sha256": "bca956b01dbda32a00d431aee683a77ef8fa70200e3aa244c949da4219b0d692"
4470
+ "sha256": "bdb5fabfd35c8e1402b7a68bfd9033ab7bb8072e83f3cd181a835aed180eab8a"
4471
4471
  },
4472
4472
  {
4473
4473
  "path": "public-docs/index.html",
@@ -5402,7 +5402,7 @@
5402
5402
  {
5403
5403
  "path": "scripts/gds/provision.js",
5404
5404
  "mode": "0000644",
5405
- "sha256": "7fe0dfd805c6974a3782d388809e767578eeab1a8aa0acf728e495341b9a2503"
5405
+ "sha256": "1b1e4ca721a9052206265736712ea17e96b2b724c2926faba49839def36a1818"
5406
5406
  },
5407
5407
  {
5408
5408
  "path": "scripts/gds/publish-credential-check.js",
@@ -6092,7 +6092,7 @@
6092
6092
  {
6093
6093
  "path": "src/branding.js",
6094
6094
  "mode": "0000644",
6095
- "sha256": "a374d2625e09050948c7f619af1937e9c0b315fe7c25691d6b1667147386a04e"
6095
+ "sha256": "04c7bd3146abc16cb51d81fa6550520cdd169cd527fbe2de13dffd2c6fcc8ef5"
6096
6096
  },
6097
6097
  {
6098
6098
  "path": "src/build-info.js",
@@ -6112,7 +6112,7 @@
6112
6112
  {
6113
6113
  "path": "src/module-api.js",
6114
6114
  "mode": "0000644",
6115
- "sha256": "2e7369e55f232a28a03122d9284ef889af7217b2335800da54881f3ff64c88b3"
6115
+ "sha256": "1c68d692ed6e52924b04af09f9d816514b8b1ccd795984960f1c9cddefb6eba5"
6116
6116
  },
6117
6117
  {
6118
6118
  "path": "src/module-loader/catalog.js",
@@ -7889,6 +7889,11 @@
7889
7889
  "mode": "0000644",
7890
7890
  "sha256": "a525fdcfa2fde8edd2be3198ca7213b9b6bc43a12f748a718a570b7f993972b6"
7891
7891
  },
7892
+ {
7893
+ "path": "tests/provisioning_settings_env.mjs",
7894
+ "mode": "0000644",
7895
+ "sha256": "ee73d6af28f68bc6c197aae847663e9db2afb45e5073e2886a28c2a15925ad53"
7896
+ },
7892
7897
  {
7893
7898
  "path": "tests/provisioning_slug_available.mjs",
7894
7899
  "mode": "0000644",
@@ -3,7 +3,10 @@
3
3
  "identity": {
4
4
  "productName": "Cloud Bongos",
5
5
  "worldName": "Cloud Bongos",
6
- "company": { "name": "", "site": "" }
6
+ "company": {
7
+ "name": "",
8
+ "site": ""
9
+ }
7
10
  },
8
11
  "llmProjectName": "this project",
9
12
  "domains": {
@@ -23,33 +26,70 @@
23
26
  "_note": "Cross-site identity (ADR 0141). idp=false → this instance owns its auth outright (its own GitHub app; the default + the self-hosted path). A HOSTED instance sets idp=true and gets its hub origin + client credentials from env (<PREFIX>_HUB_ORIGIN / _HUB_CLIENT_ID / _HUB_CLIENT_SECRET, written by provisioning) so web + CLI sign-in federate through the Cloud Bongos hub with no GitHub app of its own. Identity is federated; authority stays local.",
24
27
  "idp": false
25
28
  },
26
- "repo": { "owner": "", "name": "" },
27
- "db": { "database": null },
29
+ "project": {
30
+ "_note": "Per-project policy the PLATFORM decides and this instance OBEYS (task 1003139, goal 1000058). Stored on the platform's provisioning_instances.settings; written here as <PREFIX>_PLATFORM_VISIBILITY / <PREFIX>_JOINABILITY by the provisioning runner, which re-writes web.env and restarts the service when the owner changes one. The defaults are TODAY's behavior, so an instance that never receives a push — a self-hosted one, or one provisioned before this channel — keeps behaving exactly as it does now. platformVisibility: public | gated. joinability: open | apply | invite_only.",
31
+ "platformVisibility": "public",
32
+ "joinability": "apply"
33
+ },
34
+ "repo": {
35
+ "owner": "",
36
+ "name": ""
37
+ },
38
+ "db": {
39
+ "database": null
40
+ },
28
41
  "configDir": "cloudbongos",
29
42
  "firstAdmin": "",
30
- "currency": { "label": "credits", "symbol": "" },
31
- "reward": { "marginPct": 20, "usdPeg": 1, "mode": "cost-plus-and-estimate" },
32
- "models": { "main": "opus", "subagentDefault": "opus", "subagentRoutine": "haiku" },
43
+ "currency": {
44
+ "label": "credits",
45
+ "symbol": ""
46
+ },
47
+ "reward": {
48
+ "marginPct": 20,
49
+ "usdPeg": 1,
50
+ "mode": "cost-plus-and-estimate"
51
+ },
52
+ "models": {
53
+ "main": "opus",
54
+ "subagentDefault": "opus",
55
+ "subagentRoutine": "haiku"
56
+ },
33
57
  "theme": {
34
58
  "palette": "",
35
59
  "mode": "cloud-bongos",
36
60
  "ui": {
37
61
  "colors": {
38
- "bg": "#f9d9a8", "bgCard": "#fffaf2", "bgDeep": "#f6d8ae", "bgFrame": "#f0c489",
39
- "ink": "#2b170e", "inkSoft": "#6d4a33", "inkFaint": "#785a41",
40
- "rule": "#e8a463", "ruleSoft": "#f6d8ae",
41
- "accent": "#ce4806", "accentDeep": "#9e3406", "sea": "#0f7a96", "terracotta": "#855b3d"
62
+ "bg": "#f9d9a8",
63
+ "bgCard": "#fffaf2",
64
+ "bgDeep": "#f6d8ae",
65
+ "bgFrame": "#f0c489",
66
+ "ink": "#2b170e",
67
+ "inkSoft": "#6d4a33",
68
+ "inkFaint": "#785a41",
69
+ "rule": "#e8a463",
70
+ "ruleSoft": "#f6d8ae",
71
+ "accent": "#ce4806",
72
+ "accentDeep": "#9e3406",
73
+ "sea": "#0f7a96",
74
+ "terracotta": "#855b3d"
42
75
  },
43
76
  "fonts": {
44
77
  "display": "\"Baloo 2\", \"Quicksand\", system-ui, sans-serif",
45
78
  "body": "\"Quicksand\", system-ui, -apple-system, \"Segoe UI\", sans-serif",
46
- "links": ["https://fonts.googleapis.com/css2?family=Baloo+2:wght@500;600;700&family=Quicksand:wght@400;500;600;700&family=Fira+Code:wght@400;500&display=swap"]
79
+ "links": [
80
+ "https://fonts.googleapis.com/css2?family=Baloo+2:wght@500;600;700&family=Quicksand:wght@400;500;600;700&family=Fira+Code:wght@400;500&display=swap"
81
+ ]
47
82
  },
48
83
  "favicon": "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><circle cx='12' cy='17' r='8' fill='%23ce4806'/><circle cx='22' cy='17' r='6' fill='%23855b3d'/><circle cx='12' cy='17' r='3.5' fill='%23fffaf2'/><circle cx='22' cy='17' r='2.5' fill='%23fffaf2'/></svg>",
49
84
  "backdrop": "#f9d9a8"
50
85
  }
51
86
  },
52
- "rankLabels": { "xenos": "Newcomer", "thetes": "Contributor", "metic": "Trusted", "archon": "Owner" },
87
+ "rankLabels": {
88
+ "xenos": "Newcomer",
89
+ "thetes": "Contributor",
90
+ "metic": "Trusted",
91
+ "archon": "Owner"
92
+ },
53
93
  "copy": {
54
94
  "tagline": "Building Digitally, as simple as Bongos.",
55
95
  "hallName": "Builders' Hall",
@@ -235,6 +235,28 @@ function publicInstance(row) {
235
235
  // The instance's EFFECTIVE settings: the defaults overlaid with the stored,
236
236
  // on-vocabulary keys. An off-vocabulary or unknown stored value falls back to
237
237
  // its default rather than leaking into the read surface. PURE.
238
+ // The web.env lines that carry this instance's settings, as the runner writes them
239
+ // at standup (task 1003139). PURE (row + prefix in, string out) and owned HERE, next
240
+ // to SETTINGS_VOCAB, rather than in provision.js — the vocabulary and the thing that
241
+ // transmits it must not be able to drift. Values come from effectiveSettings, so a
242
+ // row with no stored settings emits today's behavior EXPLICITLY rather than nothing:
243
+ // an instance must never have to infer a policy from an absent variable.
244
+ //
245
+ // This is the standup leg only. Changing a setting on an ALREADY-RUNNING instance
246
+ // needs a push (re-write web.env + restart, since the branding pack is memoized for
247
+ // the process lifetime) — that is the 'settings-apply' intent, still to be built.
248
+ function settingsEnvLines(row, prefix) {
249
+ const s = effectiveSettings(row);
250
+ return `# Per-project policy the platform decides and this instance obeys (task
251
+ # 1003139). Written at standup from the platform's provisioning_instances.settings;
252
+ # src/branding.js ENV_OVERRIDES maps these onto project.*. Changing one on a RUNNING
253
+ # instance still needs a push (re-write + restart) — not built yet, so today a change
254
+ # takes effect on the next provision run.
255
+ ${prefix}_PLATFORM_VISIBILITY=${s.platform_visibility}
256
+ ${prefix}_JOINABILITY=${s.joinability}
257
+ `;
258
+ }
259
+
238
260
  function effectiveSettings(row) {
239
261
  const stored = row && row.settings && typeof row.settings === 'object' && !Array.isArray(row.settings)
240
262
  ? row.settings : {};
@@ -783,6 +805,7 @@ async function costLedger(db, { limit = 500 } = {}) {
783
805
  module.exports = {
784
806
  // constants
785
807
  INSTANCE_STATES, HOSTING_SHAPES, ONBOARD_MODES, PROJECT_TYPES, INTENT_ACTIONS, TEARDOWN_FROM,
808
+ settingsEnvLines,
786
809
  PROVISION_ENQUEUE_FROM, DOMAIN_ATTACH_FROM, TIER_MONTHLY_USD, CERT_ELIGIBLE_STATES,
787
810
  SETTINGS_VOCAB, DEFAULT_SETTINGS,
788
811
  // pure
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.261",
3
+ "version": "1.19.262",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.261",
9
+ "version": "1.19.262",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.261",
3
+ "version": "1.19.262",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -1157,7 +1157,7 @@ function provisionedEnvPrefix() {
1157
1157
  // stays at neutral localhost and the hall never loads (task 1972). No domain yet
1158
1158
  // (bring-your-own deferred, ADR 0111 §4) ⇒ origins stay neutral until one is set.
1159
1159
  function webEnvBody(inst, { clientId = '', clientSecret = '', appId = null, appPem = null, federation = null } = {}) {
1160
- const prefix = provisionedEnvPrefix();
1160
+ const prefix = provisionedEnvPrefix(), { settingsEnvLines } = require(path.join(REPO_ROOT, 'modules/provisioning/provisioning')); // eslint-disable-line global-require
1161
1161
  const origin = inst.domain ? `https://${inst.domain}` : '';
1162
1162
  const originLines = origin
1163
1163
  ? `# Single-host origins (task 1972): the hall serves on THIS host + OAuth/redirects
@@ -1234,7 +1234,7 @@ ${prefix}_GITHUB_CLIENT_SECRET=${clientSecret || ''}
1234
1234
  ${firstAdminLine}GDS_BRANDING_FILE=${PROVISIONED_BRANDING_FILE}
1235
1235
  ${originLines}${appLines}PORT=${inst.port}
1236
1236
  PGDATABASE=${dbName(inst)}
1237
- ${fedLines}`;
1237
+ ${fedLines}${settingsEnvLines(inst, prefix)}`;
1238
1238
  }
1239
1239
  function webEnvPath(inst, { etcBase = CONFIG.etcBase } = {}) {
1240
1240
  return path.posix.join(etcBase, inst.slug, 'web.env');
package/src/branding.js CHANGED
@@ -54,6 +54,12 @@ const ENV_OVERRIDES = [
54
54
  // front door with one env var and no instance branding.json — cloudbongos.com
55
55
  // sets <PREFIX>_POST_LOGIN_LANDING=/gate/ (the orbs). Task 1002240.
56
56
  ['POST_LOGIN_LANDING', ['landing', 'postLogin']],
57
+ // Per-project policy pushed by the provisioning runner (task 1003139). The pack
58
+ // ships today's behavior, so an instance that never receives a push is unchanged.
59
+ // Read through branding, which is memoized + deep-frozen for the process lifetime
60
+ // — which is exactly why applying a change RESTARTS the instance.
61
+ ['PLATFORM_VISIBILITY', ['project', 'platformVisibility']],
62
+ ['JOINABILITY', ['project', 'joinability']],
57
63
  ];
58
64
 
59
65
  const isObj = (v) => v && typeof v === 'object' && !Array.isArray(v);
package/src/module-api.js CHANGED
@@ -949,8 +949,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
949
949
  // landed since 1.19.259 with no explicit bump. run 32289043893. (task 1002620)
950
950
  // 1.19.261 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
951
951
  // landed since 1.19.260 with no explicit bump. run 32295699354. (task 1002620)
952
+ // 1.19.262 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
953
+ // landed since 1.19.261 with no explicit bump. run 32298063660. (task 1002620)
952
954
  // ---------------------------------------------------------------------------
953
- const CORE_VERSION = '1.19.261'; // CI auto-patch carrier (ADR 0161); see changelog above
955
+ const CORE_VERSION = '1.19.262'; // CI auto-patch carrier (ADR 0161); see changelog above
954
956
 
955
957
  // A namespaced logger so a module's log lines are attributable + consistent.
956
958
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -0,0 +1,121 @@
1
+ // tests/provisioning_settings_env.mjs
2
+ //
3
+ // The standup leg of the platform->instance settings channel (task 1003139, goal
4
+ // 1000058): a per-project setting the PLATFORM stores must reach the INSTANCE that
5
+ // has to obey it.
6
+ //
7
+ // The gap this closes: PATCH /provisioning/instances/:id/settings wrote
8
+ // platform_visibility / joinability to the platform's jsonb and pushed nothing, and a
9
+ // standalone instance never federates — it holds no hub credential and has no
10
+ // authenticated path back to the platform to pull one. The settings were write-only
11
+ // state. The transport that DOES exist is /etc/<slug>/web.env, read through the
12
+ // branding pack, so the runner now writes them there at standup and src/branding.js
13
+ // resolves them onto project.*.
14
+ //
15
+ // NOT covered here because it is NOT built: pushing a CHANGED setting to an
16
+ // already-running instance (the branding pack is memoized + deep-frozen for the
17
+ // process lifetime, so that needs a re-write + restart — the deferred
18
+ // 'settings-apply' intent). These tests assert the standup leg only, and one of them
19
+ // pins that the generated file does not CLAIM the push exists.
20
+ //
21
+ // DB-free and hermetic: pure builders + a child process with env set. No network.
22
+ //
23
+ // Run: node tests/provisioning_settings_env.mjs
24
+
25
+ import { strict as assert } from 'node:assert';
26
+ import { createRequire } from 'node:module';
27
+ import { execFileSync } from 'node:child_process';
28
+ import path from 'node:path';
29
+ import { fileURLToPath } from 'node:url';
30
+
31
+ const require = createRequire(import.meta.url);
32
+ const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
33
+ const provisioning = require('../modules/provisioning/provisioning.js');
34
+
35
+ let passed = 0, failed = 0;
36
+ function t(name, fn) {
37
+ try { fn(); passed++; console.log(` PASS ${name}`); }
38
+ catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
39
+ }
40
+
41
+ const linesOf = (s) => s.split('\n').filter((l) => l && !l.startsWith('#'));
42
+ const varsOf = (s) => Object.fromEntries(linesOf(s).map((l) => l.split('=')));
43
+
44
+ console.log('settingsEnvLines — the platform\'s stored settings, as the instance reads them:');
45
+
46
+ t('a stored setting is carried verbatim under the pack\'s env prefix', () => {
47
+ const v = varsOf(provisioning.settingsEnvLines(
48
+ { settings: { platform_visibility: 'gated', joinability: 'invite_only' } }, 'CLOUDBONGOS'));
49
+ assert.equal(v.CLOUDBONGOS_PLATFORM_VISIBILITY, 'gated');
50
+ assert.equal(v.CLOUDBONGOS_JOINABILITY, 'invite_only');
51
+ });
52
+
53
+ t('a row with NO stored settings emits today\'s behavior EXPLICITLY, never nothing', () => {
54
+ const v = varsOf(provisioning.settingsEnvLines({}, 'CLOUDBONGOS'));
55
+ // an instance must never have to infer a policy from an absent variable
56
+ assert.equal(v.CLOUDBONGOS_PLATFORM_VISIBILITY, 'public');
57
+ assert.equal(v.CLOUDBONGOS_JOINABILITY, 'apply');
58
+ assert.equal(linesOf(provisioning.settingsEnvLines({}, 'X')).length, 2, 'both keys always present');
59
+ });
60
+
61
+ t('an off-vocabulary stored value falls back to the default, never reaches the instance', () => {
62
+ const v = varsOf(provisioning.settingsEnvLines(
63
+ { settings: { platform_visibility: 'sort-of-public', joinability: 'whenever' } }, 'CLOUDBONGOS'));
64
+ assert.equal(v.CLOUDBONGOS_PLATFORM_VISIBILITY, 'public');
65
+ assert.equal(v.CLOUDBONGOS_JOINABILITY, 'apply');
66
+ });
67
+
68
+ t('the prefix is the pack\'s, not a guess — a wrong prefix silently never fires', () => {
69
+ const v = varsOf(provisioning.settingsEnvLines({}, 'MERCURY'));
70
+ assert.ok('MERCURY_PLATFORM_VISIBILITY' in v);
71
+ assert.ok(!('CLOUDBONGOS_PLATFORM_VISIBILITY' in v));
72
+ });
73
+
74
+ t('every emitted key is in SETTINGS_VOCAB — the two lists cannot drift', () => {
75
+ const keys = linesOf(provisioning.settingsEnvLines({}, 'X')).map((l) => l.split('=')[0].replace(/^X_/, '').toLowerCase());
76
+ assert.deepEqual(keys.sort(), Object.keys(provisioning.SETTINGS_VOCAB).sort(),
77
+ 'a key added to the vocabulary must be carried, or the instance never learns it');
78
+ });
79
+
80
+ console.log('\nThe generated file tells the truth about what it is:');
81
+
82
+ t('it does NOT claim a live push exists — that intent is not built', () => {
83
+ const s = provisioning.settingsEnvLines({}, 'X');
84
+ assert.doesNotMatch(s, /settings-apply/,
85
+ 'no comment may name an intent action the runner cannot dispatch');
86
+ assert.match(s, /next provision run/i, 'it says when a change actually takes effect');
87
+ });
88
+
89
+ console.log('\nwebEnvBody — the settings ride the real per-instance env file:');
90
+
91
+ t('the runner writes them into web.env alongside PORT and PGDATABASE', () => {
92
+ const provision = require('../scripts/gds/provision.js');
93
+ const body = provision.webEnvBody(
94
+ { slug: 'spike-harbor', port: 3010, db_name: 'spike-harbor', target_ref: 'jaxri/spike-harbor',
95
+ hosting_shape: 'standalone', settings: { platform_visibility: 'gated' } }, {});
96
+ assert.match(body, /_PLATFORM_VISIBILITY=gated/);
97
+ assert.match(body, /_JOINABILITY=apply/);
98
+ assert.match(body, /PORT=3010/, 'the rest of the file is untouched');
99
+ });
100
+
101
+ console.log('\nsrc/branding.js — the value actually reaches code:');
102
+
103
+ t('the neutral pack ships today\'s behavior, so an un-pushed instance is unchanged', () => {
104
+ const pack = require('../config/branding.neutral.json');
105
+ assert.equal(pack.project.platformVisibility, 'public');
106
+ assert.equal(pack.project.joinability, 'apply');
107
+ });
108
+
109
+ t('the env override resolves onto project.* in a real process', () => {
110
+ const read = (env) => JSON.parse(execFileSync(process.execPath, ['-e',
111
+ "const b=require('./src/branding.js');const r=(b.branding||b)();console.log(JSON.stringify({v:r.project.platformVisibility,j:r.project.joinability}))"],
112
+ { cwd: ROOT, encoding: 'utf8', env: { ...process.env, ...env } }));
113
+ assert.deepEqual(read({}), { v: 'public', j: 'apply' }, 'no env → the pack default');
114
+ assert.deepEqual(
115
+ read({ CLOUDBONGOS_PLATFORM_VISIBILITY: 'gated', CLOUDBONGOS_JOINABILITY: 'invite_only' }),
116
+ { v: 'gated', j: 'invite_only' },
117
+ 'the web.env the runner writes is what the instance reads');
118
+ });
119
+
120
+ console.log(`\nprovisioning_settings_env: ${passed} passed, ${failed} failed`);
121
+ if (failed) process.exit(1);