@bongos/core 1.19.1052 → 1.19.1054

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.
@@ -315,6 +315,90 @@ await ta('the shape refusal: dedicated → ok:false with guidance naming the val
315
315
  assert.equal(writes.length, 0);
316
316
  });
317
317
 
318
+ console.log('\nthe control plane\'s own row — the platform pushes a setting to itself (task 1004334):');
319
+
320
+ const U = require('../scripts/gds/provision-units.js');
321
+ const { FALLBACK_ENV_PREFIX } = require('../src/instance-config.js');
322
+
323
+ t('selfEnvPrefix: the web.env BRANDING_FILE line wins, then the instance pack, then the neutral pack, then the fallback', () => {
324
+ const packs = (map) => (p) => {
325
+ const hit = Object.keys(map).find((k) => p.replace(/\\/g, '/').endsWith(k));
326
+ if (!hit) { const e = new Error('ENOENT'); e.code = 'ENOENT'; throw e; }
327
+ return JSON.stringify(map[hit]);
328
+ };
329
+ const neutral = { 'config/branding.neutral.json': { envPrefix: 'NEUTRALX' } };
330
+ const withOwn = { ...neutral, 'config/branding.json': { envPrefix: 'OWNPACK' }, 'packs/site.json': { envPrefix: 'OVERRIDE' } };
331
+ const root = '/srv/instance';
332
+ assert.equal(U.selfEnvPrefix({ envBody: 'PORT=1\nBONGOS_BRANDING_FILE=packs/site.json\n', instanceRoot: root, readFile: packs(withOwn) }), 'OVERRIDE');
333
+ assert.equal(U.selfEnvPrefix({ envBody: 'GDS_BRANDING_FILE=packs/site.json\r\n', instanceRoot: root, readFile: packs(withOwn) }), 'OVERRIDE', 'a legacy spelling still names the pack, as loadBranding reads it');
334
+ assert.equal(U.selfEnvPrefix({ envBody: 'PORT=1\n', instanceRoot: root, readFile: packs(withOwn) }), 'OWNPACK', 'no override → the instance root\'s config/branding.json');
335
+ assert.equal(U.selfEnvPrefix({ envBody: 'FOO_BRANDING_FILE=packs/site.json\n', instanceRoot: root, readFile: packs(withOwn) }), 'OWNPACK', 'an unknown prefix is not the branding knob');
336
+ assert.equal(U.selfEnvPrefix({ envBody: '# BONGOS_BRANDING_FILE=packs/site.json\n', instanceRoot: root, readFile: packs(withOwn) }), 'OWNPACK', 'a commented line is not set');
337
+ // loadBranding's own rule: an override that names a missing file loads NO instance pack,
338
+ // so the neutral one answers — config/branding.json is not consulted as a second chance.
339
+ assert.equal(U.selfEnvPrefix({ envBody: 'BONGOS_BRANDING_FILE=packs/gone.json\n', instanceRoot: root, readFile: packs(withOwn) }), 'NEUTRALX');
340
+ assert.equal(U.selfEnvPrefix({ envBody: '', instanceRoot: root, readFile: packs({ ...neutral, 'config/branding.json': { name: 'no prefix here' } }) }), 'NEUTRALX', 'a pack that names no envPrefix inherits the neutral one');
341
+ assert.equal(U.selfEnvPrefix({ envBody: '', instanceRoot: root, readFile: packs({}) }), FALLBACK_ENV_PREFIX, 'nothing readable → the fallback');
342
+ });
343
+
344
+ t('settingsEnvVarsFor: only the control-plane row reads the self prefix — every other shape is byte-identical to before', () => {
345
+ const body = 'BONGOS_BRANDING_FILE=/nowhere/at/all.json\n';
346
+ for (const shape of ['cloud-host', 'co-tenant']) {
347
+ assert.deepEqual(P.settingsEnvVarsFor({ ...activeStandalone, hosting_shape: shape }, body), P.settingsEnvVarsFor({ ...activeStandalone, hosting_shape: shape }), shape);
348
+ assert.ok(Object.keys(P.settingsEnvVarsFor({ ...activeStandalone, hosting_shape: shape }, body)).every((k) => k.startsWith(`${NEUTRAL_PREFIX}_`)), shape);
349
+ }
350
+ });
351
+
352
+ const selfRow = {
353
+ id: 41, slug: 'cloudbongos', hosting_shape: 'control-plane', status: 'active', owner_builder_id: 3,
354
+ port: 3002, domain: 'cloudbongos.com', settings: { artist_gate: 'off' },
355
+ };
356
+
357
+ await ta('--apply, the control-plane row: its OWN /etc/<slug>/web.env on the control-plane pair, under ITS pack\'s prefix, then restart <slug>; nothing else moves', async () => {
358
+ const fs = await import('node:fs');
359
+ const os = await import('node:os');
360
+ const path = await import('node:path');
361
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cp-pack-'));
362
+ const pack = path.join(dir, 'site.json');
363
+ fs.writeFileSync(pack, JSON.stringify({ envPrefix: 'CPSELF' }));
364
+ const selfEnv = `CPSELF_GITHUB_CLIENT_SECRET=keepme0123456789\nBONGOS_BRANDING_FILE=${pack}\nPORT=3002\nCPSELF_ARTIST_GATE=strict\nGITHUB_PUSH_TOKEN=keepme-too\n`;
365
+ const seen = { box: [], control: [] };
366
+ const mk = (bucket) => (cmd) => {
367
+ seen[bucket].push(cmd);
368
+ if (/\bcat /.test(cmd)) return { ok: true, stdout: selfEnv };
369
+ if (/api\/bongos\/instance/.test(cmd)) return { ok: true, stdout: manifestWith({ platformVisibility: 'public', joinability: 'apply', visibility: 'public', joinGrant: 'full', artistGate: 'off', spendPayer: 'none' }) };
370
+ return { ok: true, stdout: '{"ok":true}' };
371
+ };
372
+ try {
373
+ const { deps, writes, events } = recorder({ apply: true });
374
+ deps.exec = mk('box'); deps.controlExec = mk('control');
375
+ const r = await P.settingsApplyInstance(selfRow, deps);
376
+ assert.equal(r.ok, true, JSON.stringify(r));
377
+ assert.equal(r.consumed, true, 'the read-back saw the live site report the new value');
378
+ assert.equal(seen.box.length, 0, 'nothing runs on the co-hosting box executor — the self row IS the control plane');
379
+ assert.ok(seen.control.some((c) => /^cat \/etc\/cloudbongos\/web\.env$/.test(c)), seen.control.join(' | '));
380
+ assert.ok(seen.control.some((c) => /^systemctl restart cloudbongos$/.test(c)), 'the service the core-upgrade door already restarts');
381
+ assert.deepEqual(writes.map((w) => [w.via, w.path]), [['control', '/etc/cloudbongos/web.env']]);
382
+ const out = writes[0].body;
383
+ assert.match(out, /^CPSELF_ARTIST_GATE=off$/m, 'replaced IN PLACE under the prefix the site resolves');
384
+ assert.doesNotMatch(out, /^BONGOS_ARTIST_GATE=/m, 'not written under the provisioned floor, which this site would never read');
385
+ assert.match(out, /^CPSELF_GITHUB_CLIENT_SECRET=keepme0123456789$/m, 'creds untouched');
386
+ assert.match(out, /^GITHUB_PUSH_TOKEN=keepme-too$/m);
387
+ assert.match(out, /^PORT=3002$/m);
388
+ assert.ok(![...seen.control].some((c) => /systemctl (stop|disable)|caddy|box-dns|rm -|upgrade\.js|npm/.test(c)), 'settings only — no teardown, DNS, Caddy or pin legs');
389
+ assert.match(events[0].detail, /reads the new values/);
390
+ } finally { fs.rmSync(dir, { recursive: true, force: true }); }
391
+ });
392
+
393
+ await ta('the control-plane row keeps every refusal of the leg: an unreadable web.env writes nothing and restarts nothing', async () => {
394
+ const { deps, cmds, writes } = recorder({ apply: true, readFails: true });
395
+ const r = await P.settingsApplyInstance(selfRow, deps);
396
+ assert.equal(r.ok, false);
397
+ assert.match(r.error, /cannot read \/etc\/cloudbongos\/web\.env/);
398
+ assert.equal(writes.length, 0);
399
+ assert.ok(!cmds.some((c) => /restart/.test(c)), 'the platform is never restarted over a file it could not read');
400
+ });
401
+
318
402
  console.log('\nthe verdict — recorded only when earned:');
319
403
 
320
404
  await ta('--apply success, manifest matches: consumed=true, event says it reads the new values, F19 latch released, domain-bearing PASS records no liveness verdict', async () => {
@@ -72,19 +72,30 @@ t('SETTINGS_APPLY_FROM is exactly {active} — the provision intent cannot stand
72
72
  assert.ok(!provisioning.PROVISION_ENQUEUE_FROM.has('active'), 'the premise the gate rests on');
73
73
  });
74
74
 
75
- t('SETTINGS_APPLY_SHAPES is the reachable pair, and the set is a subset of HOSTING_SHAPES', () => {
76
- assert.deepEqual([...provisioning.SETTINGS_APPLY_SHAPES].sort(), ['cloud-host', 'co-tenant']);
75
+ t('SETTINGS_APPLY_SHAPES is the reachable three, and the set is a subset of HOSTING_SHAPES', () => {
76
+ // 'control-plane' joined in task 1004334: the runner's OWN box, admitted by name in
77
+ // control-plane-guard.js, so a setting saved on the platform's row reaches the live site.
78
+ assert.deepEqual([...provisioning.SETTINGS_APPLY_SHAPES].sort(), ['cloud-host', 'co-tenant', 'control-plane']);
77
79
  for (const s of provisioning.SETTINGS_APPLY_SHAPES) assert.ok(provisioning.HOSTING_SHAPES.includes(s), s);
78
80
  // The exclusion this used to assert was 'dedicated', which task 1004182 removed from
79
81
  // HOSTING_SHAPES entirely — so that assertion became vacuously true and stopped testing
80
- // anything. Name the shapes that DO exist and are still deliberately unreachable: nothing
81
- // of ours runs a byo-host, and the control plane is not pushed settings by this path.
82
- for (const unreachable of ['byo-host', 'control-plane']) {
82
+ // anything. Name the shape that DOES exist and is still deliberately unreachable:
83
+ // nothing of ours runs a byo-host.
84
+ for (const unreachable of ['byo-host']) {
83
85
  assert.ok(provisioning.HOSTING_SHAPES.includes(unreachable), `${unreachable} must be a real shape or this assertion is vacuous`);
84
86
  assert.ok(!provisioning.SETTINGS_APPLY_SHAPES.has(unreachable), unreachable);
85
87
  }
86
88
  });
87
89
 
90
+ t('the two gates agree: every settings-apply shape is one the self-row guard lets a settings-apply through', () => {
91
+ // A shape on SETTINGS_APPLY_SHAPES that the guard refuses would make the route say
92
+ // "a restart is coming" and then answer the owner with control_plane_protected.
93
+ const G = require('../modules/provisioning/control-plane-guard.js');
94
+ for (const s of provisioning.SETTINGS_APPLY_SHAPES) assert.equal(G.intentAllowedForShape('settings-apply', s), true, s);
95
+ // and the restart door was NOT opened on the self row as a side effect
96
+ assert.ok(!provisioning.RESTART_SHAPES.has('control-plane'), 'task 1004334 widened the settings push only');
97
+ });
98
+
88
99
  await ta('enqueueSettingsApply: a fresh intent → created:true; the INSERT carries the settings-apply action', async () => {
89
100
  const calls = [];
90
101
  const db = { query: async (sql, params) => { calls.push({ sql, params }); return { rows: [{ id: 1, action: params[1], state: 'pending' }] }; } };
@@ -104,6 +115,26 @@ await ta('enqueueSettingsApply: a fresh intent → created:true; the INSERT carr
104
115
  assert.deepEqual(insert.params, [18, 'settings-apply', 'api:self', null]);
105
116
  });
106
117
 
118
+ await ta('enqueueSettingsApply on the CONTROL-PLANE row passes the self-row guard and INSERTs; a teardown on the same row is still refused (task 1004334)', async () => {
119
+ const selfRow = (calls) => ({ query: async (sql, params) => {
120
+ calls.push({ sql, params });
121
+ if (/SELECT hosting_shape FROM provisioning_instances/.test(sql)) return { rows: [{ hosting_shape: 'control-plane' }] };
122
+ return { rows: [{ id: 3, action: params[1], state: 'pending' }] };
123
+ } });
124
+ const calls = [];
125
+ const r = await provisioning.enqueueSettingsApply(selfRow(calls), 41, 'api:self');
126
+ assert.equal(r.created, true, 'the real guard (not a stub) admits the push onto the self row');
127
+ const insert = calls.find((c) => /INSERT INTO provisioning_intents/.test(c.sql));
128
+ assert.deepEqual(insert.params, [41, 'settings-apply', 'api:self', null]);
129
+ // Everything the guard existed to refuse is still refused through the same door.
130
+ for (const destructive of ['teardown', 'disconnect', 'provision', 'domain-attach', 'repo-private']) {
131
+ const seen = [];
132
+ await assert.rejects(provisioning.enqueueIntent(selfRow(seen), 41, destructive, 'api:self'),
133
+ (e) => e.code === 'control_plane_protected' && e.status === 409, destructive);
134
+ assert.ok(!seen.some((c) => /INSERT/.test(c.sql)), `${destructive}: nothing written`);
135
+ }
136
+ });
137
+
107
138
  await ta('enqueueSettingsApply: an identical PENDING push already open → created:false, no conflict (it carries this change)', async () => {
108
139
  const db = { query: async (sql) => {
109
140
  if (/INSERT/.test(sql)) { const e = new Error('dup'); e.code = '23505'; throw e; }
@@ -260,6 +291,20 @@ await ta('ACTIVE co-tenant queues too; the Archon override names WHICH archon on
260
291
  assert.equal(events[0].actor, 'api:archon:999');
261
292
  });
262
293
 
294
+ await ta('ACTIVE control-plane row (the platform itself): artist_gate off QUEUES a push and says a restart is coming (task 1004334)', async () => {
295
+ // The case TW01 hit: this save used to answer "runs on a machine the platform doesn't
296
+ // manage", stored 'off', and left the live site reading 'strict' with no way to apply it.
297
+ reset({ inst: { owner_builder_id: 7, slug: 'cloudbongos', status: 'active', hosting_shape: 'control-plane', domain: 'cloudbongos.com', port: 3002, settings: {} } });
298
+ const r = await patch('/provisioning/instances/41/settings', { artist_gate: 'off' });
299
+ assert.equal(r.status, 200, r.raw);
300
+ assert.equal(r.body.saved.artist_gate, 'off');
301
+ assert.deepEqual(updateCalls.map((c) => c.patch), [{ artist_gate: 'off' }]);
302
+ assert.equal(updateCalls[0].enqueuedSoFar, 0, 'the row is written before the intent the runner reads it through');
303
+ assert.deepEqual(enqueueCalls, [{ id: 41, requestedBy: 'api:self' }]);
304
+ receiptShape(r.body.push, { queued: true, restart: true, reachable: true });
305
+ assert.doesNotMatch(r.body.push.message, /doesn.t manage/, 'no longer the unreachable-machine sentence');
306
+ });
307
+
263
308
  await ta('an identical PENDING push already open → 200, queued:false, "already queued … will carry this change"', async () => {
264
309
  reset();
265
310
  enqueueResult = { intent: { id: 5, action: 'settings-apply', state: 'pending' }, created: false };