@bongos/core 1.19.926 → 1.19.927

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,11 +2,11 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.926",
6
- "core_contract": "1.19.926",
7
- "source_commit": "e29c4e2c4c22fc015eaaa56174f643b557051564",
5
+ "core_version": "1.19.927",
6
+ "core_contract": "1.19.927",
7
+ "source_commit": "8a6b09d4e2f48071fe35cfa4147ffdb779753896",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-20T23:12:12.395Z",
9
+ "built_at": "2026-09-20T23:34:18.483Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 511,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 2851,
20
- "tree_sha256": "7760b472bbc2c5cd345c6c4becda6c92d06d09c727c967667d7794740b51b8d6",
20
+ "tree_sha256": "2c2b24754d790a0405bb73c8c5381d97a364f5c4a544b31d471df58b3458cf49",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2677,7 +2677,7 @@
2677
2677
  {
2678
2678
  "path": "docs/module-api-changelog.md",
2679
2679
  "mode": "0000644",
2680
- "sha256": "b656ffb12e3d62e7042fbde0a6dc547d9b8b480ce2acacc21da0795443b117ac"
2680
+ "sha256": "3bc7467a01b53a0d28388f5df4f6028fc71a764e8155280e754ff2b17edd5e4e"
2681
2681
  },
2682
2682
  {
2683
2683
  "path": "docs/modules-contract.md",
@@ -6412,12 +6412,12 @@
6412
6412
  {
6413
6413
  "path": "modules/provisioning/control-plane-guard.js",
6414
6414
  "mode": "0000644",
6415
- "sha256": "b70ad687c7699a3023e8be973b67ceeb7ff67afb38ff47665c7089614901c200"
6415
+ "sha256": "e1d060477287884f878748da4c2b7cfe164c782538728ffc7efc473c8b6a4997"
6416
6416
  },
6417
6417
  {
6418
6418
  "path": "modules/provisioning/core-upgrade.js",
6419
6419
  "mode": "0000644",
6420
- "sha256": "bd0109f1e270c868cc1c1d27d1c3b51ccb48c8b3d54c8274ffedbf2a4bbce051"
6420
+ "sha256": "291a3765197f9429c70dd240fe5cdfa72a245ad3698c822032e71bb88999d533"
6421
6421
  },
6422
6422
  {
6423
6423
  "path": "modules/provisioning/credential-preflight.js",
@@ -7922,12 +7922,12 @@
7922
7922
  {
7923
7923
  "path": "package-lock.json",
7924
7924
  "mode": "0000644",
7925
- "sha256": "3ec884fff4704eceb947c709b0143b4428c435f8a7cad753d4c758028cf13bfd"
7925
+ "sha256": "52b5b2136a302b5f6f0d855f3b6b9b55db2412a60766c05f857a313e52cd451e"
7926
7926
  },
7927
7927
  {
7928
7928
  "path": "package.json",
7929
7929
  "mode": "0000644",
7930
- "sha256": "2e4530f1e38ef785e735ae6fc924451aab9d22beecdf2bb99af943cdfb470453"
7930
+ "sha256": "33d35abf35aa8693f2b36cf95227a052986b760bd7de71eaee002c7d450f4eaa"
7931
7931
  },
7932
7932
  {
7933
7933
  "path": "public-docs/index.html",
@@ -9927,7 +9927,7 @@
9927
9927
  {
9928
9928
  "path": "src/module-api.js",
9929
9929
  "mode": "0000644",
9930
- "sha256": "938fdfedf2e8b99ae44c637371144016754b82cb8b96e254e47f8766e945a3b5"
9930
+ "sha256": "e0b786da5becfbcb6f79530193b0424db2c1164e26173f9218ff330d9aba079d"
9931
9931
  },
9932
9932
  {
9933
9933
  "path": "src/module-loader/catalog.js",
@@ -10837,7 +10837,7 @@
10837
10837
  {
10838
10838
  "path": "tests/core_upgrade_door.mjs",
10839
10839
  "mode": "0000644",
10840
- "sha256": "c0457b6d1b0b0b3ee4117544e9203c28306a99772e2b085f0611132cf3d358d9"
10840
+ "sha256": "bb2a39d645141a15b7391580069bf05b6f812e40f20d10fd55dbc564eb8f00ec"
10841
10841
  },
10842
10842
  {
10843
10843
  "path": "tests/core_upgrade_runner.mjs",
@@ -2326,5 +2326,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
2326
2326
  landed since 1.19.924 with no explicit bump. run 35542154729. (task 1002620)
2327
2327
  1.19.926 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2328
2328
  landed since 1.19.925 with no explicit bump. run 35543911646. (task 1002620)
2329
+ 1.19.927 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2330
+ landed since 1.19.926 with no explicit bump. run 35545006099. (task 1002620)
2329
2331
  ---------------------------------------------------------------------------
2330
2332
  ```
@@ -30,10 +30,18 @@
30
30
  // the door task 1003159 builds.
31
31
  // restart — the liveness remedy (task 1003238). Restarting the platform is
32
32
  // recoverable and already owner-initiated elsewhere.
33
+ // core-upgrade-preflight
34
+ // — `upgrade.js --dry-run`. It RUNS NOTHING: it returns at pre-flight
35
+ // before the pin moves, before npm, before any restart. Refusing it
36
+ // here protected the box from nothing and removed the feature from
37
+ // everyone, because the control plane is the ONLY instance that runs
38
+ // the provisioning module — so its hall is the only one with an
39
+ // Upgrade page at all. A preview that cannot run where the button
40
+ // lives is not a preview (task 1004125).
33
41
  //
34
42
  // Adding to this list is a deliberate edit someone has to argue for. It is pinned
35
43
  // by name in tests/core_upgrade_door.mjs so it cannot grow as a side effect.
36
- const CONTROL_PLANE_ACTIONS = Object.freeze(['core-upgrade', 'restart']);
44
+ const CONTROL_PLANE_ACTIONS = Object.freeze(['core-upgrade', 'core-upgrade-preflight', 'restart']);
37
45
 
38
46
  /**
39
47
  * May this intent action be aimed at an instance of this hosting shape? PURE.
@@ -209,6 +209,17 @@ function makeCoreUpgradeHandler({ provisioning, pool, parseId, validateOrRespond
209
209
  });
210
210
  } catch (err) {
211
211
  log.error('[provisioning] POST /provisioning/instances/:id/core-upgrade', err);
212
+ // A REFUSAL IS NOT AN OUTAGE. The control-plane guard throws an error carrying
213
+ // `code`, `status` and a `publicMessage` it wrote for exactly this moment — and
214
+ // this catch used to discard all three, so every guard refusal reached the owner
215
+ // as a 500 "internal error" while the real sentence sat in the journal. Nothing
216
+ // in the repo honoured `publicMessage`; it was assigned once and read never
217
+ // (task 1004125). Only an error that has already declared itself public is
218
+ // forwarded — anything else still gets the opaque 500, because an unexpected
219
+ // throw may carry anything.
220
+ if (err && err.publicMessage && err.code && err.status) {
221
+ return res.fail(err.code, { status: err.status, message: err.publicMessage });
222
+ }
212
223
  return res.fail('core_upgrade_failed', { status: 500, message: 'internal error' });
213
224
  }
214
225
  };
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.926",
3
+ "version": "1.19.927",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.926",
9
+ "version": "1.19.927",
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.926",
3
+ "version": "1.19.927",
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",
package/src/module-api.js CHANGED
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
71
71
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
72
72
  // the entry to that file. Look for a version's history there, not here.
73
73
  // ---------------------------------------------------------------------------
74
- const CORE_VERSION = '1.19.926'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.927'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
75
75
 
76
76
  // A namespaced logger so a module's log lines are attributable + consistent.
77
77
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -93,10 +93,29 @@ await t('every action not explicitly allowed is refused on the self row', () =>
93
93
  }
94
94
  });
95
95
 
96
- await t('the allow-list is exactly the two acts that cannot destroy the box', () => {
96
+ await t('the allow-list is exactly the acts that cannot destroy the box', () => {
97
97
  // Pinned by NAME so widening it is a deliberate edit someone has to argue for,
98
- // not a side effect of adding an action to INTENT_ACTIONS.
99
- assert.deepEqual([...G.CONTROL_PLANE_ACTIONS].sort(), ['core-upgrade', 'restart']);
98
+ // not a side effect of adding an action to INTENT_ACTIONS. It did its job: adding
99
+ // 'core-upgrade-preflight' to INTENT_ACTIONS in task 1004121 did NOT reach here, and
100
+ // this line is what forced the argument to be made (task 1004125). The argument: a
101
+ // dry run returns at pre-flight having moved no pin, run no npm and restarted
102
+ // nothing, and the control plane is the only instance whose hall HAS an Upgrade
103
+ // page — so refusing the preview there protected nothing and removed everything.
104
+ assert.deepEqual([...G.CONTROL_PLANE_ACTIONS].sort(),
105
+ ['core-upgrade', 'core-upgrade-preflight', 'restart']);
106
+ });
107
+
108
+ await t('the preview is permitted on the control plane — the one row that can use it', () => {
109
+ assert.equal(G.intentAllowedForShape('core-upgrade-preflight', 'control-plane'), true);
110
+ });
111
+
112
+ await t('and widening it did NOT let the destructive acts through', () => {
113
+ // The whole risk of arguing one action onto an allow-list is arguing four.
114
+ for (const a of P.INTENT_ACTIONS) {
115
+ if (['core-upgrade', 'core-upgrade-preflight', 'restart'].includes(a)) continue;
116
+ assert.equal(G.intentAllowedForShape(a, 'control-plane'), false,
117
+ `'${a}' must still be refused against the box the platform runs on`);
118
+ }
100
119
  });
101
120
 
102
121
  await t('an ordinary instance is untouched by the guard', () => {
@@ -489,7 +508,7 @@ const validatorHarness = () => {
489
508
  fail: (code, o) => { body = { code, ...(typeof o === 'object' ? o : { status: o }) }; status = (o && o.status) || o || 400; return res; },
490
509
  status: (c) => { status = c; return res; },
491
510
  };
492
- return { calls, handler, res, read: () => ({ body, status }) };
511
+ return { calls, handler, res, provisioning, read: () => ({ body, status }) };
493
512
  };
494
513
 
495
514
  await t('the preview payload the page posts is ACCEPTED, not refused as an unknown field', async () => {
@@ -503,6 +522,38 @@ await t('the preview payload the page posts is ACCEPTED, not refused as an unkno
503
522
  assert.ok(queued, 'and it reaches the queue as a PREVIEW, not as a move');
504
523
  });
505
524
 
525
+ await t('a guard refusal reaches the owner as ITS OWN sentence, not "internal error"', async () => {
526
+ // The guard writes `code`, `status: 409` and a publicMessage for exactly this moment.
527
+ // The catch discarded all three, so every control-plane refusal this route ever made
528
+ // was shown as a 500 "internal error" while the real sentence sat in the journal —
529
+ // which is how the preview refusal stayed invisible until it was pressed live.
530
+ // Elsewhere in this file we already assert that sentence is well-formed; nothing
531
+ // asserted it ARRIVED.
532
+ const h = validatorHarness();
533
+ const refusal = Object.assign(new Error('blocked'), {
534
+ code: 'control_plane_protected', status: 409,
535
+ publicMessage: 'That action cannot be aimed at the machine the platform itself runs on.',
536
+ });
537
+ h.provisioning.enqueueIntent = async () => { throw refusal; };
538
+ await h.handler({ builder: { id: 42 }, params: { id: '7' }, body: { to: '1.19.926' } }, h.res);
539
+ const { body } = h.read();
540
+ assert.equal(body && body.code, 'control_plane_protected', 'the guard\'s own code, not core_upgrade_failed');
541
+ assert.equal(body && body.status, 409, 'a refusal is not a 500');
542
+ assert.match((body && body.message) || '', /cannot be aimed at the machine/);
543
+ });
544
+
545
+ await t('an UNEXPECTED throw is still opaque — this is not a blanket error passthrough', async () => {
546
+ // Forwarding every error would leak internals. Only an error that declared itself
547
+ // public (code + status + publicMessage) is forwarded.
548
+ const h = validatorHarness();
549
+ h.provisioning.enqueueIntent = async () => { throw new Error('connection terminated: relation "secrets" does not exist'); };
550
+ await h.handler({ builder: { id: 42 }, params: { id: '7' }, body: { to: '1.19.926' } }, h.res);
551
+ const { body } = h.read();
552
+ assert.equal(body && body.code, 'core_upgrade_failed');
553
+ assert.equal(body && body.message, 'internal error');
554
+ assert.ok(!/secrets|relation/.test(JSON.stringify(body)), 'no internals reach the client');
555
+ });
556
+
506
557
  await t('the move payload still validates, and still queues a move', async () => {
507
558
  const h = validatorHarness();
508
559
  await h.handler({ builder: { id: 42 }, params: { id: '7' }, body: { to: '1.19.925' } }, h.res);