@bongos/core 1.19.940 → 1.19.941
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 +17 -17
- package/docs/module-api-changelog.md +2 -0
- package/docs/recipes/upgrading-the-core.md +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/deploy-divergence.js +3 -3
- package/scripts/gds/run-unit-tests.js +1 -1
- package/src/bongos/schema-lag.js +34 -14
- package/src/bongos/serve-internal.js +1 -1
- package/src/module-api.js +1 -1
- package/tests/deploy_divergence_line.mjs +5 -5
- package/tests/schema_lag.mjs +53 -9
- package/tests/schema_lag_db.mjs +1 -1
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.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.941",
|
|
6
|
+
"core_contract": "1.19.941",
|
|
7
|
+
"source_commit": "4063c1c70284ec0850ace02a6ac26e9ecf0ecc20",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-21T20:39:58.651Z",
|
|
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": 2876,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "ede1bb5b48026ef02ab4df39fae783c3eb2e648c7bafec28010174f4ab8ed076",
|
|
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": "
|
|
2680
|
+
"sha256": "57bcf700f7f794da9b36f6cd6a88d5c44ac153a6ef246c7be7841ace8e6908ce"
|
|
2681
2681
|
},
|
|
2682
2682
|
{
|
|
2683
2683
|
"path": "docs/modules-contract.md",
|
|
@@ -2927,7 +2927,7 @@
|
|
|
2927
2927
|
{
|
|
2928
2928
|
"path": "docs/recipes/upgrading-the-core.md",
|
|
2929
2929
|
"mode": "0000644",
|
|
2930
|
-
"sha256": "
|
|
2930
|
+
"sha256": "f16df23bfcb5e1ce02813222bf9a8b190b458749939f5074fbe142d9430b1bad"
|
|
2931
2931
|
},
|
|
2932
2932
|
{
|
|
2933
2933
|
"path": "docs/recipes/windows-builders.md",
|
|
@@ -8002,12 +8002,12 @@
|
|
|
8002
8002
|
{
|
|
8003
8003
|
"path": "package-lock.json",
|
|
8004
8004
|
"mode": "0000644",
|
|
8005
|
-
"sha256": "
|
|
8005
|
+
"sha256": "7930ba5349243c9641adef8865a4d868521f9c841c174dda327942a599304ae8"
|
|
8006
8006
|
},
|
|
8007
8007
|
{
|
|
8008
8008
|
"path": "package.json",
|
|
8009
8009
|
"mode": "0000644",
|
|
8010
|
-
"sha256": "
|
|
8010
|
+
"sha256": "f19114e6c03c1eb79f70855f08efae1b58167aa4939cb149bcd50894f2ca7c36"
|
|
8011
8011
|
},
|
|
8012
8012
|
{
|
|
8013
8013
|
"path": "public-docs/index.html",
|
|
@@ -8442,7 +8442,7 @@
|
|
|
8442
8442
|
{
|
|
8443
8443
|
"path": "scripts/gds/deploy-divergence.js",
|
|
8444
8444
|
"mode": "0000644",
|
|
8445
|
-
"sha256": "
|
|
8445
|
+
"sha256": "bffaceec8b47b238f4a7911aee10a651b45cd75e0c5a67b2b8e3daa90e3cd54a"
|
|
8446
8446
|
},
|
|
8447
8447
|
{
|
|
8448
8448
|
"path": "scripts/gds/deploy-main-audit-gate.sh",
|
|
@@ -9107,7 +9107,7 @@
|
|
|
9107
9107
|
{
|
|
9108
9108
|
"path": "scripts/gds/run-unit-tests.js",
|
|
9109
9109
|
"mode": "0000644",
|
|
9110
|
-
"sha256": "
|
|
9110
|
+
"sha256": "40d1b5bf4c82536cac2a666e81c4909f99bc26796db0c9e9c928678032948c91"
|
|
9111
9111
|
},
|
|
9112
9112
|
{
|
|
9113
9113
|
"path": "scripts/gds/runner-drift.js",
|
|
@@ -9972,7 +9972,7 @@
|
|
|
9972
9972
|
{
|
|
9973
9973
|
"path": "src/bongos/schema-lag.js",
|
|
9974
9974
|
"mode": "0000644",
|
|
9975
|
-
"sha256": "
|
|
9975
|
+
"sha256": "b6443663bc60287242164c530f864504514050b648b6c9736454fd0e9ab4319f"
|
|
9976
9976
|
},
|
|
9977
9977
|
{
|
|
9978
9978
|
"path": "src/bongos/secret-box.js",
|
|
@@ -9987,7 +9987,7 @@
|
|
|
9987
9987
|
{
|
|
9988
9988
|
"path": "src/bongos/serve-internal.js",
|
|
9989
9989
|
"mode": "0000644",
|
|
9990
|
-
"sha256": "
|
|
9990
|
+
"sha256": "989a1bef0e179efe32fc3edb11f7f46b1732920ca026b36a841c8e0e344a175a"
|
|
9991
9991
|
},
|
|
9992
9992
|
{
|
|
9993
9993
|
"path": "src/bongos/uptime-poller.js",
|
|
@@ -10012,7 +10012,7 @@
|
|
|
10012
10012
|
{
|
|
10013
10013
|
"path": "src/module-api.js",
|
|
10014
10014
|
"mode": "0000644",
|
|
10015
|
-
"sha256": "
|
|
10015
|
+
"sha256": "168b1b8002437b7a7bc5b42ae2f68af01c31b007473a09cdc0f1bb98338f662a"
|
|
10016
10016
|
},
|
|
10017
10017
|
{
|
|
10018
10018
|
"path": "src/module-loader/catalog.js",
|
|
@@ -11062,7 +11062,7 @@
|
|
|
11062
11062
|
{
|
|
11063
11063
|
"path": "tests/deploy_divergence_line.mjs",
|
|
11064
11064
|
"mode": "0000644",
|
|
11065
|
-
"sha256": "
|
|
11065
|
+
"sha256": "fd62561dcf5fb935116ec5933393e75cef25eaa807a6d39fc55d9c981e6fe289"
|
|
11066
11066
|
},
|
|
11067
11067
|
{
|
|
11068
11068
|
"path": "tests/deploy_instance.mjs",
|
|
@@ -13452,12 +13452,12 @@
|
|
|
13452
13452
|
{
|
|
13453
13453
|
"path": "tests/schema_lag.mjs",
|
|
13454
13454
|
"mode": "0000644",
|
|
13455
|
-
"sha256": "
|
|
13455
|
+
"sha256": "d592f20f08695dfbe2806e195c4d2a6e81bc0ea6cec4f1c2168609280a197c09"
|
|
13456
13456
|
},
|
|
13457
13457
|
{
|
|
13458
13458
|
"path": "tests/schema_lag_db.mjs",
|
|
13459
13459
|
"mode": "0000644",
|
|
13460
|
-
"sha256": "
|
|
13460
|
+
"sha256": "5e3ada8619ea87eea3026a2ccf3069926d5825a06fe11b5e435020034b2eb7e1"
|
|
13461
13461
|
},
|
|
13462
13462
|
{
|
|
13463
13463
|
"path": "tests/scope_map_guard.mjs",
|
|
@@ -2354,5 +2354,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2354
2354
|
landed since 1.19.938 with no explicit bump. run 35641614084. (task 1002620)
|
|
2355
2355
|
1.19.940 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2356
2356
|
landed since 1.19.939 with no explicit bump. run 35647628385. (task 1002620)
|
|
2357
|
+
1.19.941 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2358
|
+
landed since 1.19.940 with no explicit bump. run 35652433730. (task 1002620)
|
|
2357
2359
|
---------------------------------------------------------------------------
|
|
2358
2360
|
```
|
|
@@ -167,7 +167,7 @@ running a go-live.
|
|
|
167
167
|
- **Survey co-tenant git state as `lars`, never as root.** `ssh cloudbongos` lands you as root, where `git config --get remote.origin.url` on a `lars`-owned repo returns **nothing** — which reads exactly like "this instance has no remote" and is wrong. They do: `git@github-instances:example-owner/<slug>.git`. Prefix every probe with `sudo -u lars`.
|
|
168
168
|
- **A scaffold's `migrate` script is not guaranteed.** 21 of the 22 co-tenants have `scripts.migrate`; `mercury` has **no `scripts` block at all** and is started directly by its systemd `ExecStart`, so the normal lane cannot complete on it. `--skip-migrate` gets the install through but silently skips every schema change in the window — check what that window carries before reaching for it, rather than after.
|
|
169
169
|
- **An instance's database moves with its code, on every path** (task 1002002). `bongos upgrade` migrates (step 4) before it restarts; the control-plane runner behind the Deploy door drives it with `PGDATABASE` set to the target instance's own database; provisioning migrates at stand-up. A pinned co-tenant needs nothing extra, because its code only moves through `bongos upgrade`. `--skip-migrate` is the one way out of lockstep. Pinned by `tests/instance_db_migrated_on_code_move.mjs`.
|
|
170
|
-
- **`/version` reports `schemaPending`** (task 1002002): how many
|
|
170
|
+
- **`/version` reports `schemaPending`** (task 1002002): how many migrations the running code ships — core, plus each module this process has enabled (DB overrides included, task 1004136) — that this instance's database has not recorded — `null` when unknown (database unreachable, or a core older than the field). Above 0 is the 2026-07-04 outage shape, where the first query touching those tables fails. Fix: `migrate` against that instance DB with `PGDATABASE` set. Exception: `migrate` does not read DB overrides yet ([task 1004137](https://cloudbongos.com/builders#/task/1004137)), so a module switched on only by the hall's Modules toggle stays pending. Until that lands, also switch it on in `config/modules.json` (or the `<PREFIX>_MODULE_<KEY>` env) before migrating. The SessionStart `deploy-divergence` line alarms on it for the instance a checkout talks to; `--skip-migrate` above is the usual way to cause one.
|
|
171
171
|
- **The health check polls the restart** (fixed in task [#1984](https://example.com/builders#/task/1984)). The service binds its port ~1s after `systemctl restart`, so `--health-url` now polls up to ~30s for a 2xx before reporting a warning — an immediate `fetch failed` no longer means a failed upgrade. (Pre-1984 cores fetched once and spuriously reported `fetch failed`; there, confirm with `is-active` + a manual `curl`.)
|
|
172
172
|
- **The ledger auto-inserts via `PGDATABASE` too** (fixed in task [#1984](https://example.com/builders#/task/1984)). A consumer on `PGDATABASE` + peer auth now gets its `core_upgrades` row written automatically — the channel delegates the insert to `psql -d "$PGDATABASE"`, exactly like `migrate.sh`, so no manual `psql INSERT` is needed. (`DATABASE_URL` still works, via the `pg` driver.) Pre-1984 cores only auto-wrote with `DATABASE_URL`; on those, record the row manually.
|
|
173
173
|
- **No deploy key on the droplet** → deploys/upgrades ride a `git bundle`, not `git pull` (idea 509).
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.941",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.941",
|
|
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.
|
|
3
|
+
"version": "1.19.941",
|
|
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",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// 2. UNDEPLOYED — the live instance's coreVersion is behind the CORE_VERSION
|
|
17
17
|
// on main. A release exists; this instance has not moved its pin to it.
|
|
18
18
|
// 3. SCHEMA BEHIND — live's /version reports schemaPending > 0: its database
|
|
19
|
-
// lacks
|
|
19
|
+
// lacks migrations its running code ships (the 2026-07-04 502 outage
|
|
20
20
|
// shape; task 1002002). An instance too old to report it is not flagged.
|
|
21
21
|
//
|
|
22
22
|
// Usage:
|
|
@@ -267,7 +267,7 @@ function report(r, { quiet = false } = {}) {
|
|
|
267
267
|
}
|
|
268
268
|
if (r.schemaBehind) {
|
|
269
269
|
const n = r.live.schemaPending;
|
|
270
|
-
say(` ⚠ SCHEMA BEHIND — live's database is missing ${n}
|
|
270
|
+
say(` ⚠ SCHEMA BEHIND — live's database is missing ${n} migration${n === 1 ? '' : 's'} its running code ships.`);
|
|
271
271
|
say(' The first query that touches them fails (the 2026-07-04 502 was this shape).');
|
|
272
272
|
say(' Fix: run migrate against that instance DB with PGDATABASE set (see docs/recipes/upgrading-the-core.md).');
|
|
273
273
|
}
|
|
@@ -319,7 +319,7 @@ function line(r) {
|
|
|
319
319
|
const versionParts = parts.length;
|
|
320
320
|
const pending = r.schemaBehind ? safeCount(r.live?.schemaPending) : null;
|
|
321
321
|
if (pending) {
|
|
322
|
-
parts.push(`its database is MISSING ${pending}
|
|
322
|
+
parts.push(`its database is MISSING ${pending} migration${pending === 1 ? '' : 's'} the running code ships, so the first query that touches ${pending === 1 ? 'it' : 'them'} fails`);
|
|
323
323
|
}
|
|
324
324
|
if (!parts.length) return null;
|
|
325
325
|
|
|
@@ -181,7 +181,7 @@ const INTEGRATION = new Set([
|
|
|
181
181
|
'session_token_hash_db',
|
|
182
182
|
// task 1002002: the no-false-alarm half of the schema-lag detector. The DB-free
|
|
183
183
|
// sibling (schema_lag) covers the arithmetic against a stub; only a database that
|
|
184
|
-
// migrate.sh has just run can show the expected core
|
|
184
|
+
// migrate.sh has just run can show the expected stems (core + enabled modules) equal the recorded ones,
|
|
185
185
|
// so a healthy instance reads zero rather than a permanent SCHEMA BEHIND.
|
|
186
186
|
'schema_lag_db',
|
|
187
187
|
'scouting_rollup_fanout', 'session_records',
|
package/src/bongos/schema-lag.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
// src/bongos/schema-lag.js — does this instance's database carry every
|
|
3
|
-
//
|
|
2
|
+
// src/bongos/schema-lag.js — does this instance's database carry every migration the running
|
|
3
|
+
// code ships? (task 1002002; modules added in task 1004136)
|
|
4
4
|
//
|
|
5
5
|
// WHY. On 2026-07-04 cloudbongos.com returned 502 on every /me: the deploy had moved the code
|
|
6
6
|
// 22 core migrations past the instance's own database, and nothing said so — /healthz answered
|
|
@@ -8,11 +8,16 @@
|
|
|
8
8
|
// moves an instance's core now migrates that instance's own DB first (bongos upgrade, the runner
|
|
9
9
|
// behind the Deploy door, provisioning — pinned by tests/instance_db_migrated_on_code_move.mjs),
|
|
10
10
|
// but a skipped or failed step would recreate that outage just as silently. This is the detector:
|
|
11
|
-
// the
|
|
11
|
+
// the migration stems the running code expects that schema_migrations does not record.
|
|
12
12
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
13
|
+
// WHAT IS EXPECTED is every migration the code this process actually serves depends on: every
|
|
14
|
+
// core migrations/*.sql, plus modules/<key>/migrations/*.sql for each module isModuleEnabled
|
|
15
|
+
// says is on — the same gate its routes mount behind, DB overrides included. A module that is
|
|
16
|
+
// off is expected nothing, so it can never alarm. Instance seeds (migrations/instance/) are an
|
|
17
|
+
// opt-in no running code depends on, so they are never counted.
|
|
18
|
+
//
|
|
19
|
+
// KNOWN GAP: migrate.sh does not read DB overrides (task 1004137), so for a module on ONLY by
|
|
20
|
+
// override this reports a true gap that running migrate does not yet close.
|
|
16
21
|
//
|
|
17
22
|
// Surfaced as `schemaPending` on /version (a count, never the names) and logged at boot;
|
|
18
23
|
// scripts/gds/deploy-divergence.js turns a non-zero count into the SessionStart alarm.
|
|
@@ -20,6 +25,8 @@
|
|
|
20
25
|
const fs = require('node:fs');
|
|
21
26
|
const path = require('node:path');
|
|
22
27
|
const { resolveCoreRoot } = require('../instance-config');
|
|
28
|
+
const { isModuleEnabled } = require('../modules');
|
|
29
|
+
const { discovered } = require('../module-loader/loader');
|
|
23
30
|
const { pool } = require('./pool');
|
|
24
31
|
const log = require('./logger').logger.child({ src: 'schema-lag' });
|
|
25
32
|
|
|
@@ -28,14 +35,23 @@ const CORE_MIGRATIONS_DIR = path.join(resolveCoreRoot(), 'migrations');
|
|
|
28
35
|
// query runs at most this often; between refreshes the last answer is served.
|
|
29
36
|
const REFRESH_MS = 60 * 1000;
|
|
30
37
|
|
|
31
|
-
/** The stems migrate.sh keys schema_migrations by, for the
|
|
32
|
-
function
|
|
38
|
+
/** The stems migrate.sh keys schema_migrations by, for the *.sql files directly in `dir`. */
|
|
39
|
+
function migrationStems(dir = CORE_MIGRATIONS_DIR) {
|
|
33
40
|
return fs.readdirSync(dir, { withFileTypes: true })
|
|
34
41
|
.filter((e) => e.isFile() && e.name.endsWith('.sql'))
|
|
35
42
|
.map((e) => e.name.slice(0, -'.sql'.length))
|
|
36
43
|
.sort();
|
|
37
44
|
}
|
|
38
45
|
|
|
46
|
+
/** Migration stems of every module this process serves, from the dir it was discovered in. */
|
|
47
|
+
function enabledModuleMigrationStems({ loaded = discovered().loaded, isEnabled = isModuleEnabled } = {}) {
|
|
48
|
+
return loaded
|
|
49
|
+
.filter((m) => isEnabled(m.key))
|
|
50
|
+
.map((m) => path.join(m.dir, 'migrations'))
|
|
51
|
+
.filter((dir) => fs.existsSync(dir))
|
|
52
|
+
.flatMap((dir) => migrationStems(dir));
|
|
53
|
+
}
|
|
54
|
+
|
|
39
55
|
/** Shipped stems the database has not recorded. PURE. */
|
|
40
56
|
function pendingStems(shipped, applied) {
|
|
41
57
|
const have = new Set(applied);
|
|
@@ -49,25 +65,27 @@ let inflight = null;
|
|
|
49
65
|
* Re-read schema_migrations and cache the gap. Never throws: an unreadable database or
|
|
50
66
|
* migrations dir caches null — "unknown" — which no reader treats as an alarm.
|
|
51
67
|
*/
|
|
52
|
-
async function refreshSchemaLag({
|
|
68
|
+
async function refreshSchemaLag({
|
|
69
|
+
db = pool, dir = CORE_MIGRATIONS_DIR, moduleStems = enabledModuleMigrationStems, now = Date.now,
|
|
70
|
+
} = {}) {
|
|
53
71
|
const before = last.pending;
|
|
54
72
|
let pending = null;
|
|
55
73
|
try {
|
|
56
74
|
const { rows } = await db.query('SELECT version FROM schema_migrations');
|
|
57
|
-
pending = pendingStems(
|
|
75
|
+
pending = pendingStems([...migrationStems(dir), ...moduleStems()], rows.map((r) => r.version));
|
|
58
76
|
} catch (err) {
|
|
59
|
-
log.warn({ err: err && err.message }, 'could not compare schema_migrations with the
|
|
77
|
+
log.warn({ err: err && err.message }, 'could not compare schema_migrations with the migrations the running code ships');
|
|
60
78
|
}
|
|
61
79
|
last = { pending, checkedAt: now() };
|
|
62
80
|
if (pending && pending.length && (!before || before.length !== pending.length)) {
|
|
63
81
|
log.error({ pending: pending.length, first: pending.slice(0, 5) },
|
|
64
|
-
'this database is missing
|
|
82
|
+
'this database is missing migrations the running code ships (core, or an enabled module) — run migrate against this instance DB before its next query touches them');
|
|
65
83
|
}
|
|
66
84
|
return pending;
|
|
67
85
|
}
|
|
68
86
|
|
|
69
87
|
/**
|
|
70
|
-
* The last-known count of missing
|
|
88
|
+
* The last-known count of missing migrations, or null when unknown. Kicks a background
|
|
71
89
|
* refresh when the answer is older than REFRESH_MS, and never waits for it.
|
|
72
90
|
*/
|
|
73
91
|
function schemaPending({ now = Date.now, refresh = refreshSchemaLag } = {}) {
|
|
@@ -77,4 +95,6 @@ function schemaPending({ now = Date.now, refresh = refreshSchemaLag } = {}) {
|
|
|
77
95
|
return last.pending ? last.pending.length : null;
|
|
78
96
|
}
|
|
79
97
|
|
|
80
|
-
module.exports = {
|
|
98
|
+
module.exports = {
|
|
99
|
+
migrationStems, enabledModuleMigrationStems, pendingStems, refreshSchemaLag, schemaPending, REFRESH_MS,
|
|
100
|
+
};
|
|
@@ -583,7 +583,7 @@ function mountInternalSurfaces(app) {
|
|
|
583
583
|
// the throwaway `updateCanary` proof marker was removed in phase E.)
|
|
584
584
|
let coreVersion = null;
|
|
585
585
|
try { coreVersion = require('../module-api').CORE_VERSION; } catch { /* fail-soft: version unknown */ }
|
|
586
|
-
// schemaPending —
|
|
586
|
+
// schemaPending — migrations this code ships (core + enabled modules') that the instance DB has not applied
|
|
587
587
|
// (null = unknown). Non-zero is the 2026-07-04 outage shape (task 1002002).
|
|
588
588
|
res.json({ ...buildInfo(), coreVersion, schemaPending: schemaPending() });
|
|
589
589
|
});
|
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.
|
|
74
|
+
const CORE_VERSION = '1.19.941'; // 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');
|
|
@@ -260,7 +260,7 @@ test('settings.json stays OUT of the published package', () => {
|
|
|
260
260
|
});
|
|
261
261
|
|
|
262
262
|
// ── schema behind (task 1002002) ────────────────────────────────────────────
|
|
263
|
-
// /version's schemaPending: core
|
|
263
|
+
// /version's schemaPending: migrations (core + enabled modules') the running code ships that live's DB
|
|
264
264
|
// lacks. The 2026-07-04 502 was this shape, and nothing reported it.
|
|
265
265
|
|
|
266
266
|
const schemaOnly = {
|
|
@@ -269,17 +269,17 @@ const schemaOnly = {
|
|
|
269
269
|
behind: false, hasUnreleased: false, unreleased: { count: 0 }, schemaBehind: true,
|
|
270
270
|
};
|
|
271
271
|
|
|
272
|
-
test('a database missing
|
|
272
|
+
test('a database missing migrations is named on the one line, with its own advice', () => {
|
|
273
273
|
const s = line(schemaOnly);
|
|
274
|
-
assert.match(s, /MISSING 3
|
|
274
|
+
assert.match(s, /MISSING 3 migrations the running code ships/);
|
|
275
275
|
assert.match(s, /Run migrate against that instance DB/);
|
|
276
276
|
assert.doesNotMatch(s, /Merging to main deploys nothing/, 'the version advice is for version gaps only');
|
|
277
277
|
assert.equal(s.split('\n').length, 1);
|
|
278
|
-
assert.match(line({ ...schemaOnly, live: { ...schemaOnly.live, schemaPending: 1 } }), /MISSING 1
|
|
278
|
+
assert.match(line({ ...schemaOnly, live: { ...schemaOnly.live, schemaPending: 1 } }), /MISSING 1 migration the/);
|
|
279
279
|
|
|
280
280
|
const both = line({ ...schemaOnly, behind: true, live: { coreVersion: '1.19.56', schemaPending: 2 } });
|
|
281
281
|
assert.match(both, /1\.19\.56/);
|
|
282
|
-
assert.match(both, /MISSING 2
|
|
282
|
+
assert.match(both, /MISSING 2 migrations/);
|
|
283
283
|
assert.match(both, /Merging to main deploys nothing/);
|
|
284
284
|
});
|
|
285
285
|
|
package/tests/schema_lag.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// tests/schema_lag.mjs — the schema-lag detector behind /version's schemaPending (task 1002002
|
|
1
|
+
// tests/schema_lag.mjs — the schema-lag detector behind /version's schemaPending (task 1002002;
|
|
2
|
+
// enabled modules' migrations, task 1004136).
|
|
2
3
|
//
|
|
3
4
|
// DB-free: every database read goes through an injected stub. The real-SQL half — that a
|
|
4
5
|
// database migrate.sh has just run reports ZERO pending, i.e. the stems this module expects
|
|
@@ -13,10 +14,23 @@ import os from 'node:os';
|
|
|
13
14
|
import path from 'node:path';
|
|
14
15
|
import { fileURLToPath } from 'node:url';
|
|
15
16
|
import {
|
|
16
|
-
|
|
17
|
+
migrationStems, enabledModuleMigrationStems, pendingStems, refreshSchemaLag, schemaPending, REFRESH_MS,
|
|
17
18
|
} from '../src/bongos/schema-lag.js';
|
|
19
|
+
import { setModuleOverrides } from '../src/modules.js';
|
|
18
20
|
|
|
19
21
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
22
|
+
const none = () => [];
|
|
23
|
+
|
|
24
|
+
/** A modules/ root: `on` and `off` each ship a migration; `bare` has no migrations/ dir. */
|
|
25
|
+
function tmpModules() {
|
|
26
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'schema-lag-mod-'));
|
|
27
|
+
for (const [key, files] of [['on', ['on_001_a.sql', 'on_002_b.sql']], ['off', ['off_001_a.sql']]]) {
|
|
28
|
+
fs.mkdirSync(path.join(root, key, 'migrations'), { recursive: true });
|
|
29
|
+
for (const f of files) fs.writeFileSync(path.join(root, key, 'migrations', f), '-- x\n');
|
|
30
|
+
}
|
|
31
|
+
fs.mkdirSync(path.join(root, 'bare'));
|
|
32
|
+
return ['on', 'off', 'bare'].map((key) => ({ key, dir: path.join(root, key) }));
|
|
33
|
+
}
|
|
20
34
|
|
|
21
35
|
function tmpMigrations() {
|
|
22
36
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'schema-lag-'));
|
|
@@ -36,12 +50,12 @@ test('pendingStems lists shipped stems the database has not recorded', () => {
|
|
|
36
50
|
assert.deepEqual(pendingStems(['a'], ['a']), []);
|
|
37
51
|
});
|
|
38
52
|
|
|
39
|
-
test('
|
|
40
|
-
assert.deepEqual(
|
|
53
|
+
test('migrationStems reads top-level .sql stems only, sorted', () => {
|
|
54
|
+
assert.deepEqual(migrationStems(tmpMigrations()), ['001_init', 'core_002_add', 'core_003_more']);
|
|
41
55
|
});
|
|
42
56
|
|
|
43
57
|
test('the default directory is the installed core migrations', () => {
|
|
44
|
-
const stems =
|
|
58
|
+
const stems = migrationStems();
|
|
45
59
|
const onDisk = fs.readdirSync(path.join(ROOT, 'migrations')).filter((f) => f.endsWith('.sql'));
|
|
46
60
|
assert.equal(stems.length, onDisk.length);
|
|
47
61
|
assert.ok(stems.includes('003_pms'), 'legacy bare stems are core migrations too');
|
|
@@ -49,19 +63,49 @@ test('the default directory is the installed core migrations', () => {
|
|
|
49
63
|
|
|
50
64
|
test('a database missing core migrations reports them; a current one reports an empty list', async () => {
|
|
51
65
|
const dir = tmpMigrations();
|
|
52
|
-
assert.deepEqual(await refreshSchemaLag({ db: dbWith(['001_init']), dir }), ['core_002_add', 'core_003_more']);
|
|
53
|
-
assert.deepEqual(await refreshSchemaLag({ db: dbWith(['001_init', 'core_002_add', 'core_003_more']), dir }), []);
|
|
66
|
+
assert.deepEqual(await refreshSchemaLag({ db: dbWith(['001_init']), dir, moduleStems: none }), ['core_002_add', 'core_003_more']);
|
|
67
|
+
assert.deepEqual(await refreshSchemaLag({ db: dbWith(['001_init', 'core_002_add', 'core_003_more']), dir, moduleStems: none }), []);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('only enabled modules are expected to carry their migrations; a module that is off never alarms', () => {
|
|
71
|
+
const loaded = tmpModules();
|
|
72
|
+
const isEnabled = (key) => key !== 'off';
|
|
73
|
+
assert.deepEqual(enabledModuleMigrationStems({ loaded, isEnabled }), ['on_001_a', 'on_002_b'],
|
|
74
|
+
'an enabled module with no migrations/ dir contributes nothing and does not throw');
|
|
75
|
+
assert.deepEqual(enabledModuleMigrationStems({ loaded, isEnabled: () => false }), []);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('an enabled module whose migrations never ran is counted beside the core gap', async () => {
|
|
79
|
+
const dir = tmpMigrations();
|
|
80
|
+
const core = ['001_init', 'core_002_add', 'core_003_more'];
|
|
81
|
+
const moduleStems = () => enabledModuleMigrationStems({ loaded: tmpModules(), isEnabled: (key) => key === 'on' });
|
|
82
|
+
assert.deepEqual(await refreshSchemaLag({ db: dbWith([...core, 'on_001_a']), dir, moduleStems }), ['on_002_b']);
|
|
83
|
+
assert.deepEqual(await refreshSchemaLag({ db: dbWith([...core, 'on_001_a', 'on_002_b']), dir, moduleStems }), []);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('the default expectation follows the server\'s own enablement, DB overrides included', () => {
|
|
87
|
+
// `discord` ships a migration and meters no spend, so the override alone decides it. A DB
|
|
88
|
+
// override is the layer migrate.sh's fresh process never loads (task 1004137).
|
|
89
|
+
const STEM = 'discord_001_board_messages';
|
|
90
|
+
try {
|
|
91
|
+
setModuleOverrides({ discord: true });
|
|
92
|
+
assert.ok(enabledModuleMigrationStems().includes(STEM), 'switched on by override: its migration is expected');
|
|
93
|
+
setModuleOverrides({ discord: false });
|
|
94
|
+
assert.ok(!enabledModuleMigrationStems().includes(STEM), 'switched off by override: nothing expected');
|
|
95
|
+
} finally {
|
|
96
|
+
setModuleOverrides({});
|
|
97
|
+
}
|
|
54
98
|
});
|
|
55
99
|
|
|
56
100
|
test('an unreadable database is unknown (null), never an alarm and never a throw', async () => {
|
|
57
101
|
const down = { query: async () => { throw new Error('ECONNREFUSED'); } };
|
|
58
|
-
assert.equal(await refreshSchemaLag({ db: down, dir: tmpMigrations() }), null);
|
|
102
|
+
assert.equal(await refreshSchemaLag({ db: down, dir: tmpMigrations(), moduleStems: none }), null);
|
|
59
103
|
});
|
|
60
104
|
|
|
61
105
|
test('schemaPending serves the cached count and refreshes at most once per window', async () => {
|
|
62
106
|
const dir = tmpMigrations();
|
|
63
107
|
const t0 = 1_000_000;
|
|
64
|
-
await refreshSchemaLag({ db: dbWith(['001_init']), dir, now: () => t0 });
|
|
108
|
+
await refreshSchemaLag({ db: dbWith(['001_init']), dir, moduleStems: none, now: () => t0 });
|
|
65
109
|
|
|
66
110
|
let calls = 0;
|
|
67
111
|
const refresh = () => { calls += 1; return new Promise(() => {}); }; // never settles: stays in flight
|
package/tests/schema_lag_db.mjs
CHANGED
|
@@ -29,7 +29,7 @@ try {
|
|
|
29
29
|
|
|
30
30
|
try {
|
|
31
31
|
const pending = await refreshSchemaLag();
|
|
32
|
-
assert.deepEqual(pending, [], `a freshly migrated database must report no pending core
|
|
32
|
+
assert.deepEqual(pending, [], `a freshly migrated database must report no pending migrations (core or enabled module), got: ${JSON.stringify(pending)}`);
|
|
33
33
|
assert.equal(schemaPending(), 0, 'and /version serves 0 — known-current, not unknown (null)');
|
|
34
34
|
console.log(' ok a freshly migrated database reports schemaPending 0');
|
|
35
35
|
} finally {
|