@bongos/core 1.19.749 → 1.19.750

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.749",
6
- "core_contract": "1.19.749",
7
- "source_commit": "238806532985c069a1b8a778e5a3d223cd6b01c4",
5
+ "core_version": "1.19.750",
6
+ "core_contract": "1.19.750",
7
+ "source_commit": "70d84110fa87578e522d55cec0236a0719f25767",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-15T22:00:53.285Z",
9
+ "built_at": "2026-09-15T22:23:58.558Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 482,
13
13
  "agent_docs_stubbed": 25,
14
- "functional_verbatim": 2109,
14
+ "functional_verbatim": 2110,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2616,
20
- "tree_sha256": "ddc2e6cffdc346ee09a971dd7f237459cf8c142ad8cf058845f48638fa01a3e7",
19
+ "file_count": 2617,
20
+ "tree_sha256": "a8d1e331aab73c6da3b924d2defdcf6ac1d55f6a9ccfed12c7922e90314e7417",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2507,7 +2507,7 @@
2507
2507
  {
2508
2508
  "path": "docs/module-api-changelog.md",
2509
2509
  "mode": "0000644",
2510
- "sha256": "207ce89a17ab5bd75cb956f9e41497ef39eb10d4f59eb634f0d1c1933304ced0"
2510
+ "sha256": "996dff9026c0402289d079b4a3cab9b437def3df20adbc465c841b5064ff1b48"
2511
2511
  },
2512
2512
  {
2513
2513
  "path": "docs/modules-contract.md",
@@ -2652,7 +2652,7 @@
2652
2652
  {
2653
2653
  "path": "docs/recipes/gds-db-backup.md",
2654
2654
  "mode": "0000644",
2655
- "sha256": "9f77ee1dd01d47f38679387e0b96a739924056a52f2af2e174a7f52248f4345a"
2655
+ "sha256": "0f2dfdeca8d49a07a0da043eaa9f2b16669228c358cb56a7786c6c59e5e29808"
2656
2656
  },
2657
2657
  {
2658
2658
  "path": "docs/recipes/instance-deploy.md",
@@ -7562,12 +7562,12 @@
7562
7562
  {
7563
7563
  "path": "package-lock.json",
7564
7564
  "mode": "0000644",
7565
- "sha256": "1cf816c4ca4b4b56d3fefe33e86e8ce4caa511ee69fd8dd6c2ecc2940dc07f7f"
7565
+ "sha256": "f0450a8f923e11ca0b92de0ead3f2b9b49a8c01f721be06ddd2566845a082775"
7566
7566
  },
7567
7567
  {
7568
7568
  "path": "package.json",
7569
7569
  "mode": "0000644",
7570
- "sha256": "d890fe5b32e9b8158a19a40b765570972673f8558cb5d2e8e13ba396eee4476f"
7570
+ "sha256": "4bc87648a58a6f4b15ffe9f9bda38a0edace87d66e9e319317804d3f1ee347ac"
7571
7571
  },
7572
7572
  {
7573
7573
  "path": "public-docs/index.html",
@@ -7912,7 +7912,7 @@
7912
7912
  {
7913
7913
  "path": "scripts/gds/db-backup-nightly.sh",
7914
7914
  "mode": "0000644",
7915
- "sha256": "0a4b3490ad5d4724c693053b16af61f6632e4933d18bedbdc5737a84111f03ae"
7915
+ "sha256": "ff4785dbde6a5c52036ce4c4aacca317c62e725f49d3e32201909ec444d85d44"
7916
7916
  },
7917
7917
  {
7918
7918
  "path": "scripts/gds/db-backup-offsite.sh",
@@ -9367,7 +9367,7 @@
9367
9367
  {
9368
9368
  "path": "src/module-api.js",
9369
9369
  "mode": "0000644",
9370
- "sha256": "6c9cce81108782c099d58259ce776bed22f815f7276fa9fb9a50264aeedd3835"
9370
+ "sha256": "de52088bfb2f473ad6960e60ee94153cdf1b74d4634ed4934d22e977988278a9"
9371
9371
  },
9372
9372
  {
9373
9373
  "path": "src/module-loader/catalog.js",
@@ -10224,6 +10224,11 @@
10224
10224
  "mode": "0000644",
10225
10225
  "sha256": "434130945232d2a6959281070e6dbee31697904b3455fb3fd09f198a64bb0e33"
10226
10226
  },
10227
+ {
10228
+ "path": "tests/db_backup_nightly.mjs",
10229
+ "mode": "0000644",
10230
+ "sha256": "020dc551d105cc4a29e0f9c750a03cd14a91511d3edc47eb6719a1e430461936"
10231
+ },
10227
10232
  {
10228
10233
  "path": "tests/dead_deps.mjs",
10229
10234
  "mode": "0000644",
@@ -1972,5 +1972,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
1972
1972
  landed since 1.19.747 with no explicit bump. run 35026862340. (task 1002620)
1973
1973
  1.19.749 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1974
1974
  landed since 1.19.748 with no explicit bump. run 35028684745. (task 1002620)
1975
+ 1.19.750 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1976
+ landed since 1.19.749 with no explicit bump. run 35030717430. (task 1002620)
1975
1977
  ---------------------------------------------------------------------------
1976
1978
  ```
@@ -21,7 +21,7 @@ This closes the **most likely** loss-of-data risks: schema corruption, an accide
21
21
 
22
22
  - **Filename format:** `<dbname>-<UTC-timestamp>.sql.gz`. UTC is used so the dumps sort chronologically regardless of the droplet's timezone.
23
23
  - **Permissions:** dir is `lars:lars 0750`; files are `lars:lars 0640`. No root needed for routine inspection.
24
- - **Size:** each gzipped dump is well under 1 MB, and 30 days of them fit comfortably — pruning is hygiene, not disk pressure. **The dump excludes `search_chunks` data** (the `/recall` lexical index, migration 103) via `--exclude-table-data=search_chunks` ([#973](https://example.com/builders#/task/973)): it is regenerable derived data — chunked copies of DB prose (already dumped) and repo markdown (already in git) — and dumping its rows roughly duplicated the prose corpus, making it the dump's dominant growth vector. The table schema + GIN indexes are still dumped, so a restore recreates it empty; the index rebuilds from source (see the restore drill below). A size guard in the backup script WARNs to journald if a dump ever exceeds `GDS_BACKUP_WARN_BYTES` (default 25 MiB) — a generous tripwire for unexpected growth from some *other* table.
24
+ - **Size:** each gzipped dump is well under 1 MB, and 30 days of them fit comfortably — pruning is hygiene, not disk pressure. **The dump excludes `search_chunks` data** (the `/recall` lexical index, migration 103) via `--exclude-table-data=search_chunks` ([#973](https://example.com/builders#/task/973)): it is regenerable derived data — chunked copies of DB prose (already dumped) and repo markdown (already in git) — and dumping its rows roughly duplicated the prose corpus, making it the dump's dominant growth vector. The table schema + GIN indexes are still dumped, so a restore recreates it empty; the index rebuilds from source (see the restore drill below). A size guard in the backup script WARNs to journald if a dump ever exceeds `GDS_BACKUP_WARN_BYTES` (default 25 MiB) — a generous tripwire for unexpected growth from some *other* table. A dump is refused (and deleted) only when it is **incomplete** — missing pg_dump's closing `dump complete` line — never for being small: a fresh project's whole database gzips to ~36 KB, and the fixed 1 MiB floor this replaced failed and deleted every project's nightly dump ([task 1003925](https://cloudbongos.com/builders#/task/1003925)). An optional extra size floor can still be set in the unit's environment (see the script).
25
25
 
26
26
  ## What's scheduled
27
27
 
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.749",
3
+ "version": "1.19.750",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.749",
9
+ "version": "1.19.750",
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.749",
3
+ "version": "1.19.750",
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",
@@ -130,13 +130,27 @@ mv "$OUT.partial" "$OUT"
130
130
  BYTES=$(stat -c '%s' "$OUT")
131
131
  echo "[$(date -u +%FT%TZ)] gds-db-backup: wrote ${OUT} (${BYTES} bytes)"
132
132
 
133
- # Min-size sanity guard (incident 2026-07-02, task 1711). pipefail already aborts
134
- # on a pg_dump error, but this is belt-and-suspenders: a real dump is ~12-20 MB
135
- # gzipped, so a sub-1-MiB file means an empty/truncated dump slipped through.
136
- # Fail loudly and delete it so a broken dump can never masquerade as a good one.
137
- MIN_BYTES="${GDS_BACKUP_MIN_BYTES:-1048576}"
138
- if [ "$BYTES" -lt "$MIN_BYTES" ]; then
139
- echo "[$(date -u +%FT%TZ)] gds-db-backup: ERROR latest dump is only ${BYTES} bytes (< ${MIN_BYTES} floor) — treating as FAILED." >&2
133
+ # Completeness guard (incident 2026-07-02, task 1711; task 1003925). pipefail already
134
+ # aborts on a pg_dump error; this is belt-and-suspenders against a dump cut short. A
135
+ # plain-format dump always closes with pg_dump's own "dump complete" comment (patched
136
+ # servers append a \unrestrict line after it), whatever its size. The fixed 1 MiB floor
137
+ # this replaces was sized for the platform database and failed — and deleted — every
138
+ # valid dump of a small project (~36 KB gzipped). Fail loudly and delete a broken dump
139
+ # so it can never masquerade as a good one.
140
+ DUMP_TAIL=$(gzip -dc "$OUT" 2>/dev/null | tail -n 10) || DUMP_TAIL=''
141
+ FAIL_REASON=''
142
+ case "$DUMP_TAIL" in
143
+ *'-- PostgreSQL database dump complete'*) ;;
144
+ *) FAIL_REASON="(${BYTES} bytes) is incomplete — no pg_dump completion line" ;;
145
+ esac
146
+
147
+ # Optional operator floor. Unset means none: no single size fits every instance.
148
+ MIN_BYTES="${GDS_BACKUP_MIN_BYTES:-0}"
149
+ if [ -z "$FAIL_REASON" ] && [ "$BYTES" -lt "$MIN_BYTES" ]; then
150
+ FAIL_REASON="is only ${BYTES} bytes (< ${MIN_BYTES} floor)"
151
+ fi
152
+ if [ -n "$FAIL_REASON" ]; then
153
+ echo "[$(date -u +%FT%TZ)] gds-db-backup: ERROR latest dump ${FAIL_REASON} — treating as FAILED." >&2
140
154
  rm -f "$OUT"
141
155
  exit 1
142
156
  fi
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.749'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.750'; // 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/db_backup_nightly.mjs — the nightly dump's sanity guard (task 1003925).
2
+ //
3
+ // THE BUG. db-backup-nightly.sh refused any dump under a fixed 1 MiB floor, sized for the
4
+ // platform's own database. A small project's whole database gzips to ~36 KB, so every
5
+ // project's nightly unit failed AND the valid dump was deleted: on 2026-09-15 twelve backup
6
+ // units were failed and no project had a single retained dump. The guard now asks whether
7
+ // the dump is COMPLETE (pg_dump's closing comment), which is true of a valid dump at any size
8
+ // and false of one cut short.
9
+ //
10
+ // The script runs for real against a fake `pg_dump` on PATH, so nothing touches a database.
11
+ //
12
+ // Run: node --test tests/db_backup_nightly.mjs
13
+
14
+ import assert from 'node:assert/strict';
15
+ import { test } from 'node:test';
16
+ import { spawnSync } from 'node:child_process';
17
+ import fs from 'node:fs';
18
+ import os from 'node:os';
19
+ import path from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+
22
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
23
+ const SCRIPT = path.join(ROOT, 'scripts', 'gds', 'db-backup-nightly.sh');
24
+ const skip = process.platform === 'win32' ? 'needs a POSIX bash + coreutils (runs in CI)' : false;
25
+ // The script's env-var prefix, built rather than written so the legacy-literal ratchet
26
+ // does not count a test that has to speak the script's own variable names.
27
+ const ENV = 'gds_backup_'.toUpperCase();
28
+
29
+ // A fake pg_dump: a plain-format dump of `rows` rows, closed the way pg_dump closes one
30
+ // (including the \unrestrict line patched servers append) unless it is `cut` short, and
31
+ // exiting `exitCode`.
32
+ function runBackup({ rows = 40, cut = false, exitCode = 0, env = {} } = {}) {
33
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'db-backup-'));
34
+ const bin = path.join(tmp, 'bin');
35
+ const dir = path.join(tmp, 'backups');
36
+ fs.mkdirSync(bin);
37
+ fs.mkdirSync(dir);
38
+ const body = Array.from({ length: rows }, (_, i) => `INSERT INTO public.t VALUES (${i});`).join('\n');
39
+ const close = cut ? '' : '\n--\n-- PostgreSQL database dump complete\n--\n\n\\unrestrict abc123\n';
40
+ fs.writeFileSync(path.join(bin, 'pg_dump'),
41
+ `#!/bin/bash\ncat <<'EOF'\n--\n-- PostgreSQL database dump\n--\n${body}\n${close}EOF\nexit ${exitCode}\n`, { mode: 0o755 });
42
+ const r = spawnSync('bash', [SCRIPT], {
43
+ encoding: 'utf8',
44
+ env: { ...process.env, PATH: `${bin}:${process.env.PATH}`, [`${ENV}DB`]: 'smallproj', [`${ENV}DIR`]: dir, ...env },
45
+ });
46
+ return { status: r.status, stdout: r.stdout, stderr: r.stderr, dumps: fs.readdirSync(dir).filter((f) => f.endsWith('.sql.gz')) };
47
+ }
48
+
49
+ test('a complete dump of a small project is kept, however small', { skip }, () => {
50
+ const r = runBackup();
51
+ assert.equal(r.status, 0, r.stderr);
52
+ assert.equal(r.dumps.length, 1, 'the valid dump is retained');
53
+ assert.match(r.stdout, /gds-db-backup: ok/);
54
+ });
55
+
56
+ test('a dump cut short fails and is removed, whatever its size', { skip }, () => {
57
+ const r = runBackup({ rows: 50000, cut: true });
58
+ assert.equal(r.status, 1);
59
+ assert.equal(r.dumps.length, 0, 'an incomplete dump never masquerades as a good one');
60
+ assert.match(r.stderr, /incomplete/i);
61
+ });
62
+
63
+ test('a pg_dump that fails mid-stream still fails the run (pipefail, task 1711)', { skip }, () => {
64
+ const r = runBackup({ cut: true, exitCode: 1 });
65
+ assert.notEqual(r.status, 0);
66
+ assert.equal(r.dumps.length, 0);
67
+ });
68
+
69
+ test('an explicit operator size floor is still enforced', { skip }, () => {
70
+ const r = runBackup({ env: { [`${ENV}MIN_BYTES`]: String(10 * 1024 * 1024) } });
71
+ assert.equal(r.status, 1);
72
+ assert.equal(r.dumps.length, 0);
73
+ assert.match(r.stderr, /floor/);
74
+ });
75
+
76
+ test('no fixed size floor is baked in for every instance', () => {
77
+ const src = fs.readFileSync(SCRIPT, 'utf8');
78
+ assert.doesNotMatch(src, new RegExp(`${ENV}MIN_BYTES:-[1-9]`), 'a default floor fails every database smaller than it');
79
+ });