@bongos/core 1.19.765 → 1.19.766

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.765",
6
- "core_contract": "1.19.765",
7
- "source_commit": "3b3d14502e395142e33386df046a8b1d89b05608",
5
+ "core_version": "1.19.766",
6
+ "core_contract": "1.19.766",
7
+ "source_commit": "02a01e26d1d999916c8c67e1e02b06d0db40b694",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-16T13:30:48.350Z",
9
+ "built_at": "2026-09-16T13:48:32.669Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 483,
13
13
  "agent_docs_stubbed": 25,
14
- "functional_verbatim": 2121,
14
+ "functional_verbatim": 2122,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2629,
20
- "tree_sha256": "39fa9398befe5e5189039c4da72c998c4449f48299694a1c6a9d30d10723d49d",
19
+ "file_count": 2630,
20
+ "tree_sha256": "56708a327bb43ff2237f2ef0cdd0fac079947882699533e825262e7a16bcf724",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2512,7 +2512,7 @@
2512
2512
  {
2513
2513
  "path": "docs/module-api-changelog.md",
2514
2514
  "mode": "0000644",
2515
- "sha256": "650ec2daa3a256932ccb33c3d25a4364565d0a2cf9ff8717493144f48f4738b4"
2515
+ "sha256": "10fb4078df266a5663d8079411fdf7698adb1aa8e20a19ca63ff722e1ecb8134"
2516
2516
  },
2517
2517
  {
2518
2518
  "path": "docs/modules-contract.md",
@@ -7577,12 +7577,12 @@
7577
7577
  {
7578
7578
  "path": "package-lock.json",
7579
7579
  "mode": "0000644",
7580
- "sha256": "025da0b521b730f0ef2a24421607242c01cbc6d23a501c9a3f25d88a0a386b46"
7580
+ "sha256": "6e4cfa58c41e8fbe29dc94bbe4bd7a4eafedb88034b7d1ba3334d4a0764f5b56"
7581
7581
  },
7582
7582
  {
7583
7583
  "path": "package.json",
7584
7584
  "mode": "0000644",
7585
- "sha256": "69ee9c8d4847dabb4965d646a635dbe39a539070a2966df5d6c277101cb62019"
7585
+ "sha256": "b61349c8315ac531b941ca35f84a7323b2192e0ab7507c4d76e1c592c2a9f2e7"
7586
7586
  },
7587
7587
  {
7588
7588
  "path": "public-docs/index.html",
@@ -8412,7 +8412,7 @@
8412
8412
  {
8413
8413
  "path": "scripts/gds/provision-config.js",
8414
8414
  "mode": "0000644",
8415
- "sha256": "cda8e1022a26dce2252954351c70aa3827067ca300ebfe86b9fc7fa0ae05abeb"
8415
+ "sha256": "c870abebd21411e1fec744281130d116f3b9e8f255ddb6823aae4072a420cc15"
8416
8416
  },
8417
8417
  {
8418
8418
  "path": "scripts/gds/provision-net.js",
@@ -8432,7 +8432,7 @@
8432
8432
  {
8433
8433
  "path": "scripts/gds/provision-units.js",
8434
8434
  "mode": "0000644",
8435
- "sha256": "f6a26c5a6e6cb57fbcef1cff93738d4dc0ce955486535fca7796d72c5a280cad"
8435
+ "sha256": "e299687a54305e45ea57ff0a439f5ff099cb5d51be64bb0a48056c93309a67e3"
8436
8436
  },
8437
8437
  {
8438
8438
  "path": "scripts/gds/provision.js",
@@ -9237,7 +9237,7 @@
9237
9237
  {
9238
9238
  "path": "src/bongos/pool.js",
9239
9239
  "mode": "0000644",
9240
- "sha256": "96d41f41c4e93358c66e5101b2fc43d4ff8a1c6301a1fd3ec7020ebff6029c8c"
9240
+ "sha256": "f3fa3e0360c1bd7fb9bdf5756edccb8c5f5cad4d9ef2b89b7f0c6382c5240294"
9241
9241
  },
9242
9242
  {
9243
9243
  "path": "src/bongos/prelaunch-gate.js",
@@ -9397,7 +9397,7 @@
9397
9397
  {
9398
9398
  "path": "src/module-api.js",
9399
9399
  "mode": "0000644",
9400
- "sha256": "ad1c50e46ad096b7ba3032b8e225fec5fa74b8c50640cc577e9323a439f5b8f0"
9400
+ "sha256": "9c4c56d73c466a19a699f6c37e439d54d88211a363a869f9d87e3e840f493a2a"
9401
9401
  },
9402
9402
  {
9403
9403
  "path": "src/module-loader/catalog.js",
@@ -11874,6 +11874,11 @@
11874
11874
  "mode": "0000644",
11875
11875
  "sha256": "ff9d438a33a32fecc04caa6e652eeaaded6394438bcec0d508ae7c516fa62333"
11876
11876
  },
11877
+ {
11878
+ "path": "tests/pool_max.mjs",
11879
+ "mode": "0000644",
11880
+ "sha256": "68e5b941a1889ddc44fd548e10bb1fdb33822628b0da392592f747a32f7ccd1a"
11881
+ },
11877
11882
  {
11878
11883
  "path": "tests/post_login_landing.mjs",
11879
11884
  "mode": "0000644",
@@ -12052,7 +12057,7 @@
12052
12057
  {
12053
12058
  "path": "tests/provision.mjs",
12054
12059
  "mode": "0000644",
12055
- "sha256": "25fbd2adccf79e921c642f8f57b082f5f19fa01cd981cd5b813dc1070511aadb"
12060
+ "sha256": "245deb7b090f528180dde1457b821a8c8c2bd74a643524f441519859659643e8"
12056
12061
  },
12057
12062
  {
12058
12063
  "path": "tests/provision_restart.mjs",
@@ -2004,5 +2004,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
2004
2004
  landed since 1.19.763 with no explicit bump. run 35059447999. (task 1002620)
2005
2005
  1.19.765 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2006
2006
  landed since 1.19.764 with no explicit bump. run 35102327521. (task 1002620)
2007
+ 1.19.766 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2008
+ landed since 1.19.765 with no explicit bump. run 35104246900. (task 1002620)
2007
2009
  ---------------------------------------------------------------------------
2008
2010
  ```
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.765",
3
+ "version": "1.19.766",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.765",
9
+ "version": "1.19.766",
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.765",
3
+ "version": "1.19.766",
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",
@@ -134,6 +134,14 @@ const CONFIG = {
134
134
  // mirror an override into web.env so the fleet API reports the same number.
135
135
  boxCeiling: numEnv('PROVISION_BOX_CEILING', null),
136
136
  memFloorMb: numEnv('PROVISION_MEM_FLOOR_MB', null),
137
+ // The Postgres pool cap written into a CO-LOCATED instance's web.env as PGPOOL_MAX
138
+ // (task 1003928, ADR 0285 §3). pg's default is 10 per process and the shared server
139
+ // allows 97 non-superuser backends, so nine busy projects exhaust it whatever the
140
+ // RAM headroom. Idle projects hold zero backends, so this bounds bursts, not the
141
+ // steady state: 3 × 15 slots + the control plane's own 10 stays under the 97. The
142
+ // control plane keeps pg's default (it is not provisioned by this runner), and a
143
+ // dedicated droplet has its own server, so neither is written.
144
+ instancePoolMax: numEnv('PROVISION_INSTANCE_PGPOOL_MAX', 3),
137
145
  };
138
146
 
139
147
  // task 1002757 — the provisioner's git identity resolves from the instance's own
@@ -267,13 +267,18 @@ GITHUB_APP_PRIVATE_KEY=${String(appPem).replace(/\r?\n/g, '\\n')}
267
267
  const dbLines = dbPassword
268
268
  ? `PGUSER=${instanceDbRole(inst)}\nPGPASSWORD=${dbPassword}\n`
269
269
  : '';
270
+ // The pool cap (task 1003928, ADR 0285 §3): a co-located instance shares the box's
271
+ // Postgres with every sibling and the control plane, so its pool may not take pg's
272
+ // default 10 of the server's 97. src/bongos/pool.js reads it bare. A dedicated
273
+ // droplet has its own server and keeps the default — the line is not written.
274
+ const poolLine = inst.hosting_shape === 'dedicated' ? '' : `PGPOOL_MAX=${CONFIG.instancePoolMax}\n`;
270
275
  return `# /etc/${inst.slug}/web.env — per-instance secrets (chmod 600, OUTSIDE the repo).
271
276
  ${credComment}
272
277
  ${prefix}_GITHUB_CLIENT_ID=${clientId || ''}
273
278
  ${prefix}_GITHUB_CLIENT_SECRET=${clientSecret || ''}
274
279
  ${firstAdminLine}${originLines}${appLines}PORT=${inst.port}
275
280
  PGDATABASE=${dbName(inst)}
276
- ${dbLines}${fedLines}${settingsEnvLines(inst, prefix)}`;
281
+ ${poolLine}${dbLines}${fedLines}${settingsEnvLines(inst, prefix)}`;
277
282
  }
278
283
  function webEnvPath(inst, { etcBase = CONFIG.etcBase } = {}) {
279
284
  return path.posix.join(etcBase, inst.slug, 'web.env');
@@ -37,11 +37,27 @@ function instanceDbName() {
37
37
  return undefined;
38
38
  }
39
39
 
40
+ // The pool's connection cap (task 1003928, ADR 0285 §3). pg's default `max` is 10 and
41
+ // the shared server allows 97 non-superuser backends, so nine busy co-located
42
+ // instances exhaust it whatever the RAM headroom — connections are the second wall
43
+ // after memory. A provisioned instance's web.env carries PGPOOL_MAX (the runner
44
+ // writes a small cap, PROVISION_INSTANCE_PGPOOL_MAX); UNSET — self-host, dev, the
45
+ // control plane itself — returns undefined so pg keeps its own default and nothing
46
+ // outside a co-located box changes. Read bare like the PG* family beside it: it is
47
+ // a pg option, not a branding one, so it does not ride resolveEnv's prefix.
48
+ // Anything that is not a positive integer is ignored rather than passed to pg.
49
+ function poolMax(env = process.env) {
50
+ const n = Number(env.PGPOOL_MAX);
51
+ return Number.isInteger(n) && n > 0 ? n : undefined;
52
+ }
53
+
40
54
  let pool;
55
+ const max = poolMax();
41
56
  if (process.env.DATABASE_URL) {
42
- pool = new Pool({ connectionString: process.env.DATABASE_URL });
57
+ pool = new Pool({ connectionString: process.env.DATABASE_URL, ...(max ? { max } : {}) });
43
58
  } else {
44
59
  pool = new Pool({
60
+ ...(max ? { max } : {}),
45
61
  // Peer auth via the local Unix socket on the droplet; PG* env overrides for other environments.
46
62
  host: process.env.PGHOST || '/var/run/postgresql',
47
63
  database: process.env.PGDATABASE || instanceDbName(),
@@ -66,4 +82,4 @@ pool.on('error', (err) => {
66
82
  console.error('[gds/pool] unexpected pool error', err);
67
83
  });
68
84
 
69
- module.exports = { pool, instanceDbName };
85
+ module.exports = { pool, instanceDbName, poolMax };
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.765'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.766'; // 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');
@@ -0,0 +1,79 @@
1
+ // tests/pool_max.mjs
2
+ //
3
+ // The Postgres pool cap (task 1003928, ADR 0285 §3). Every co-located instance used
4
+ // to open pg's default 10 connections against a shared server that allows 97, so
5
+ // nine busy projects exhausted it whatever the RAM headroom. src/bongos/pool.js now
6
+ // honours PGPOOL_MAX; the runner writes it into a co-located instance's web.env
7
+ // (tests/provision.mjs pins that half). These pin the pool half:
8
+ //
9
+ // • the env reaches pg's `max` — asserted on a REAL Pool built by requiring the
10
+ // module in a child process with the env set (pool.js builds its Pool at require
11
+ // time, so the assertion has to cross a process boundary; no connection is ever
12
+ // opened — a Pool connects lazily);
13
+ // • absence leaves pg's default (10), so self-host, dev and the control plane are
14
+ // unchanged;
15
+ // • garbage is ignored, never handed to pg.
16
+ //
17
+ // DB-free. Run: node tests/pool_max.mjs
18
+
19
+ import { strict as assert } from 'node:assert';
20
+ import { execFileSync } from 'node:child_process';
21
+ import path from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
23
+ import { poolMax } from '../src/bongos/pool.js';
24
+
25
+ let passed = 0, failed = 0;
26
+ function t(name, fn) {
27
+ try { fn(); passed++; console.log(` PASS ${name}`); }
28
+ catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
29
+ }
30
+
31
+ const ROOT = path.resolve(fileURLToPath(new URL('..', import.meta.url)));
32
+ const POOL = path.join(ROOT, 'src', 'bongos', 'pool.js');
33
+
34
+ // Build the pool in a child with a controlled env and report what pg was handed.
35
+ // The DB name is pinned so instanceDbName() never reads a pack; nothing connects.
36
+ function poolMaxInChild(extraEnv) {
37
+ const env = { ...process.env, PGDATABASE: 'pool_max_probe' };
38
+ delete env.PGPOOL_MAX; delete env.DATABASE_URL; delete env.NODE_TEST_CONTEXT;
39
+ Object.assign(env, extraEnv);
40
+ const out = execFileSync(process.execPath, [
41
+ '-e', 'const { pool } = require(process.argv[1]); console.log(JSON.stringify({ max: pool.options.max })); pool.end();',
42
+ POOL,
43
+ ], { env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
44
+ return JSON.parse(out.trim().split('\n').pop()).max;
45
+ }
46
+
47
+ console.log('poolMax (pure):');
48
+
49
+ t('a positive integer is the cap; absent/blank/garbage/zero/negative/fractional → undefined (pg keeps its default)', () => {
50
+ assert.equal(poolMax({ PGPOOL_MAX: '3' }), 3);
51
+ assert.equal(poolMax({ PGPOOL_MAX: '10' }), 10);
52
+ assert.equal(poolMax({}), undefined);
53
+ assert.equal(poolMax({ PGPOOL_MAX: '' }), undefined);
54
+ assert.equal(poolMax({ PGPOOL_MAX: 'three' }), undefined);
55
+ assert.equal(poolMax({ PGPOOL_MAX: '0' }), undefined);
56
+ assert.equal(poolMax({ PGPOOL_MAX: '-2' }), undefined);
57
+ assert.equal(poolMax({ PGPOOL_MAX: '2.5' }), undefined);
58
+ });
59
+
60
+ console.log('\nthe env reaches the real Pool (child process, no connection):');
61
+
62
+ t('PGPOOL_MAX=3 → pool.options.max is 3 (the co-located instance shape)', () => {
63
+ assert.equal(poolMaxInChild({ PGPOOL_MAX: '3' }), 3);
64
+ });
65
+
66
+ t('unset → pg\'s default 10 (self-host, dev, the control plane: unchanged)', () => {
67
+ assert.equal(poolMaxInChild({}), 10);
68
+ });
69
+
70
+ t('garbage → pg\'s default, never a NaN handed to pg', () => {
71
+ assert.equal(poolMaxInChild({ PGPOOL_MAX: 'lots' }), 10);
72
+ });
73
+
74
+ t('the cap applies on the DATABASE_URL branch too', () => {
75
+ assert.equal(poolMaxInChild({ PGPOOL_MAX: '4', DATABASE_URL: 'postgres://u:p@127.0.0.1:1/pool_max_probe' }), 4);
76
+ });
77
+
78
+ console.log(`\npool_max: ${passed} passed, ${failed} failed`);
79
+ process.exit(failed ? 1 : 0);
@@ -2618,6 +2618,27 @@ t('webEnvBody: NO password ⇒ no PG credential lines — an instance provisione
2618
2618
  assert.match(b, /^PGDATABASE=mercury$/m, 'the DB name is unchanged either way');
2619
2619
  });
2620
2620
 
2621
+ // ── the Postgres pool cap (task 1003928, ADR 0285 §3) ─────────────────────────
2622
+ t('webEnvBody: a CO-LOCATED instance (co-tenant or standalone) carries PGPOOL_MAX at the runner\'s cap, beside PGDATABASE', () => {
2623
+ for (const shape of ['co-tenant', 'standalone']) {
2624
+ const b = P.webEnvBody({ ...coTenant, hosting_shape: shape });
2625
+ assert.match(b, new RegExp(`^PGPOOL_MAX=${P.CONFIG.instancePoolMax}$`, 'm'), `${shape} carries the cap`);
2626
+ const lines = b.split('\n');
2627
+ assert.equal(lines[lines.indexOf('PGDATABASE=mercury') + 1], `PGPOOL_MAX=${P.CONFIG.instancePoolMax}`, 'written right after the DB name, with or without a credential');
2628
+ }
2629
+ assert.equal(P.CONFIG.instancePoolMax, 3, 'the default cap: 3 × 15 slots + the control plane\'s 10 stays under the server\'s 97');
2630
+ // …and it rides along with the credential lines, not instead of them.
2631
+ const withCred = P.webEnvBody(coTenant, { dbPassword: 'pw_ABC-123' });
2632
+ assert.match(withCred, /^PGPOOL_MAX=3$/m);
2633
+ assert.match(withCred, /^PGUSER=bongos-mercury$/m);
2634
+ });
2635
+
2636
+ t('webEnvBody: a DEDICATED droplet has its own Postgres — no PGPOOL_MAX line, pg keeps its default', () => {
2637
+ const b = P.webEnvBody({ ...coTenant, hosting_shape: 'dedicated' });
2638
+ assert.doesNotMatch(b, /^PGPOOL_MAX=/m);
2639
+ assert.match(b, /^PGDATABASE=mercury$/m);
2640
+ });
2641
+
2621
2642
  t('dedicatedUserData: the droplet runs the SAME account + role steps, in the same order around the migrate', () => {
2622
2643
  const ud = P.dedicatedUserData(coTenant);
2623
2644
  const at = (needle) => ud.indexOf(needle);