@bongos/core 1.19.91 → 1.19.92
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 +12 -12
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/upgrade.js +47 -4
- package/scripts/migrate.sh +60 -3
- package/src/module-api.js +3 -1
- package/tests/migrate_root_resolution.mjs +100 -2
- package/tests/upgrade.mjs +58 -0
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.92",
|
|
6
|
+
"core_contract": "1.19.92",
|
|
7
|
+
"source_commit": "3458c7057c3f26cc4304f95dc80ea9b5d6928d7c",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-
|
|
9
|
+
"built_at": "2026-08-13T17:47:34.165Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 298,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 1559,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "475c679379a933a41a8c0c717e122af3bee2bfa56999f07d1e800961300c989e",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -4112,12 +4112,12 @@
|
|
|
4112
4112
|
{
|
|
4113
4113
|
"path": "package-lock.json",
|
|
4114
4114
|
"mode": "0000644",
|
|
4115
|
-
"sha256": "
|
|
4115
|
+
"sha256": "64b1a11a2b4a426879abf792df6861e28ffa1539b32f0527b9044baa2c1533ef"
|
|
4116
4116
|
},
|
|
4117
4117
|
{
|
|
4118
4118
|
"path": "package.json",
|
|
4119
4119
|
"mode": "0000644",
|
|
4120
|
-
"sha256": "
|
|
4120
|
+
"sha256": "fe0b5e65bcb64aff0ccc7609387e31656d5eaa7a48441a48a3b46862b9000323"
|
|
4121
4121
|
},
|
|
4122
4122
|
{
|
|
4123
4123
|
"path": "public-docs/index.html",
|
|
@@ -5387,7 +5387,7 @@
|
|
|
5387
5387
|
{
|
|
5388
5388
|
"path": "scripts/gds/upgrade.js",
|
|
5389
5389
|
"mode": "0000644",
|
|
5390
|
-
"sha256": "
|
|
5390
|
+
"sha256": "04ee659f61c79a1dbc2302f047d1d7870352d0892c2df974f6f000184794d3f8"
|
|
5391
5391
|
},
|
|
5392
5392
|
{
|
|
5393
5393
|
"path": "scripts/gds/validate-design.js",
|
|
@@ -5417,7 +5417,7 @@
|
|
|
5417
5417
|
{
|
|
5418
5418
|
"path": "scripts/migrate.sh",
|
|
5419
5419
|
"mode": "0000644",
|
|
5420
|
-
"sha256": "
|
|
5420
|
+
"sha256": "0768bf9c9d6368ae673e1e5ac161cfe9bae4d113129f11f8b89afeeef1f9dc1b"
|
|
5421
5421
|
},
|
|
5422
5422
|
{
|
|
5423
5423
|
"path": "scripts/migrate_cost_ledger.sh",
|
|
@@ -5752,7 +5752,7 @@
|
|
|
5752
5752
|
{
|
|
5753
5753
|
"path": "src/module-api.js",
|
|
5754
5754
|
"mode": "0000644",
|
|
5755
|
-
"sha256": "
|
|
5755
|
+
"sha256": "e334fc7dbd4247d7f9362c0304fa635f5470b058ebaf6d08e70a9276846531e7"
|
|
5756
5756
|
},
|
|
5757
5757
|
{
|
|
5758
5758
|
"path": "src/module-loader/loader.js",
|
|
@@ -7007,7 +7007,7 @@
|
|
|
7007
7007
|
{
|
|
7008
7008
|
"path": "tests/migrate_root_resolution.mjs",
|
|
7009
7009
|
"mode": "0000644",
|
|
7010
|
-
"sha256": "
|
|
7010
|
+
"sha256": "e5cfa24bb69d6fade0f2f8bffd668bbda9490aca335d301abcf6f79154b60690"
|
|
7011
7011
|
},
|
|
7012
7012
|
{
|
|
7013
7013
|
"path": "tests/migration_allocation.mjs",
|
|
@@ -7762,7 +7762,7 @@
|
|
|
7762
7762
|
{
|
|
7763
7763
|
"path": "tests/upgrade.mjs",
|
|
7764
7764
|
"mode": "0000644",
|
|
7765
|
-
"sha256": "
|
|
7765
|
+
"sha256": "a565b3a5e93a41feccaa8b616272390ea27cf2d50950a96f04856d18caa67744"
|
|
7766
7766
|
},
|
|
7767
7767
|
{
|
|
7768
7768
|
"path": "tests/upgrade_persist_pin.mjs",
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.92",
|
|
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.92",
|
|
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.92",
|
|
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/scripts/gds/upgrade.js
CHANGED
|
@@ -324,6 +324,42 @@ function runMigrate(instanceDir, run = spawnSync) {
|
|
|
324
324
|
return !r || r.error ? { ok: false, reason: r && r.error ? r.error.message : 'spawn failed' } : { ok: r.status === 0, code: r.status };
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
// WHICH database will migrate target? (task 1002755)
|
|
328
|
+
//
|
|
329
|
+
// The pre-flight probe below is only meaningful if it tests the SAME database the migration
|
|
330
|
+
// will later use. It used to be handed `process.env.PGDATABASE` — undefined on a normal box —
|
|
331
|
+
// so psql fell through to a database named after the OS user, failed for an unrelated reason,
|
|
332
|
+
// and soft-skipped with "psql failed for another reason". migrate then picked its own name and
|
|
333
|
+
// hit a database that did not exist. Two soft failures composing into a hard one, discovered
|
|
334
|
+
// only AFTER the pin had moved.
|
|
335
|
+
//
|
|
336
|
+
// So resolve it here, up front, by asking the core's OWN resolver in a child pointed at this
|
|
337
|
+
// instance — the same precedence migrate.sh uses (PGDATABASE wins, else the instance's branding
|
|
338
|
+
// pack) and the same function, so the probe and the migration cannot disagree. Spawned rather
|
|
339
|
+
// than required because requiring pool.js constructs a pg Pool in this process.
|
|
340
|
+
// Returns { db, ran }:
|
|
341
|
+
// { db: 'name', ran: true } — resolved
|
|
342
|
+
// { db: null, ran: true } — the resolver RAN and this instance names no database. Fatal:
|
|
343
|
+
// migrate cannot proceed and we know it before the pin moves.
|
|
344
|
+
// { db: null, ran: false } — the resolver could not run at all (no node_modules yet, a
|
|
345
|
+
// stubbed spawn in tests). NOT fatal: that is a different problem
|
|
346
|
+
// from "no database configured", and guessing which one it is would
|
|
347
|
+
// block bumps for the wrong reason. migrate.sh makes the same
|
|
348
|
+
// distinction and still fails loudly later, so the net holds.
|
|
349
|
+
function resolveInstanceDb({ instanceDir }, run = spawnSync) {
|
|
350
|
+
if (process.env.PGDATABASE) return { db: process.env.PGDATABASE, ran: true };
|
|
351
|
+
const poolPath = path.join(instanceDir, 'node_modules', CORE_PKG, 'src', 'bongos', 'pool');
|
|
352
|
+
// the module path rides as argv, never interpolated into JS source — a Windows-shaped path's
|
|
353
|
+
// backslashes are escape sequences inside a string literal
|
|
354
|
+
const r = run(process.execPath, ['-e', "process.stdout.write(require(process.argv[1]).instanceDbName() || '')", poolPath], {
|
|
355
|
+
cwd: instanceDir,
|
|
356
|
+
encoding: 'utf8',
|
|
357
|
+
env: { ...process.env, CLOUDBONGOS_INSTANCE_ROOT: instanceDir },
|
|
358
|
+
});
|
|
359
|
+
if (!r || r.error || r.status !== 0 || typeof r.stdout !== 'string') return { db: null, ran: false };
|
|
360
|
+
return { db: String(r.stdout).trim() || null, ran: true };
|
|
361
|
+
}
|
|
362
|
+
|
|
327
363
|
// Regenerate the per-instance API artifacts (OpenAPI spec + api-reference + typed
|
|
328
364
|
// client) from the NEW core's routes — the claude-materialize sibling (task 2051).
|
|
329
365
|
// WHY: the routes + generators ship WITH core, and the generated spec/docs/client also
|
|
@@ -748,10 +784,17 @@ async function runUpgrade(opts, deps = {}) {
|
|
|
748
784
|
// mistake, and finding it at migrate means unwinding a pin that never should have
|
|
749
785
|
// been written (task 1002712).
|
|
750
786
|
if (!opts.skipMigrate) {
|
|
751
|
-
const
|
|
787
|
+
const { db, ran } = (deps.resolveInstanceDb || resolveInstanceDb)({ instanceDir }, run);
|
|
788
|
+
if (ran && !db) {
|
|
789
|
+
return { ok: false, error: 'cannot determine which database to migrate.\n'
|
|
790
|
+
+ ' Set PGDATABASE, or give this instance a db.database in config/branding.json.\n'
|
|
791
|
+
+ ' Checked BEFORE the pin moves: migrate runs after the install, so a name that\n'
|
|
792
|
+
+ ' cannot be resolved there costs a full rollback to discover (task 1002755).' };
|
|
793
|
+
}
|
|
794
|
+
const dbId = (deps.preflightDbIdentity || preflightDbIdentity)({ instanceDir, db }, run);
|
|
752
795
|
if (!dbId.ok) return { ok: false, error: dbId.reason };
|
|
753
|
-
if (dbId.skipped) log(` • db identity pre-check: ${dbId.skipped}`);
|
|
754
|
-
else log(
|
|
796
|
+
if (dbId.skipped) log(` • db identity pre-check${db ? ` (${db})` : ''}: ${dbId.skipped}`);
|
|
797
|
+
else log(` ✓ pre-flight: database${db ? ` "${db}"` : ''} reachable as the current user`);
|
|
755
798
|
}
|
|
756
799
|
|
|
757
800
|
// 1b. mandatory post-restart health check (audit H9 / task 1002222, finding F5). A bump that
|
|
@@ -1005,7 +1048,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1005
1048
|
module.exports = {
|
|
1006
1049
|
runUpgrade, parseArgs, main, rollback,
|
|
1007
1050
|
versionFromTgzPath, versionFromDep, readInstalledCoreVersion, readPinnedCoreVersion, writePin, writeRegistryPin, restorePin,
|
|
1008
|
-
vendorTarball, gitTreeClean, persistPin, dirtyPinFiles, gitCurrentBranch, PIN_FILES, preflightDbIdentity, preflightModules, reportModulePreflight, npmInstall, runMigrate, regenerateApiArtifacts, regenerateNavDocs, NAV_WHOLE_FILE_GENERATORS, missingDocAssets, healDocAssets, DOC_ASSET_GENERATORS, restartService, recordLedger, pollHealth,
|
|
1051
|
+
vendorTarball, gitTreeClean, persistPin, dirtyPinFiles, gitCurrentBranch, PIN_FILES, preflightDbIdentity, resolveInstanceDb, preflightModules, reportModulePreflight, npmInstall, runMigrate, regenerateApiArtifacts, regenerateNavDocs, NAV_WHOLE_FILE_GENERATORS, missingDocAssets, healDocAssets, DOC_ASSET_GENERATORS, restartService, recordLedger, pollHealth,
|
|
1009
1052
|
readManifest, resolveReferenceManifest, verifyInstalledPin, // task 1002216 (audit H8) — integrity-pin verification
|
|
1010
1053
|
CORE_PKG, ARTIFACT,
|
|
1011
1054
|
};
|
package/scripts/migrate.sh
CHANGED
|
@@ -32,7 +32,13 @@ INVOKE_CWD="${INIT_CWD:-$PWD}"
|
|
|
32
32
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
33
33
|
DEFAULT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
34
34
|
CORE_ROOT="$(cd "$DEFAULT_ROOT" && node -e "process.stdout.write(require('./src/instance-config').resolveCoreRoot())" 2>/dev/null || echo "$DEFAULT_ROOT")"
|
|
35
|
-
|
|
35
|
+
# The module path rides in as argv rather than being interpolated into the JS source: a
|
|
36
|
+
# Windows-shaped DEFAULT_ROOT contains backslashes, which are ESCAPE SEQUENCES inside a JS
|
|
37
|
+
# string literal ("\U", "\r", "\D"), so interpolating corrupts the path, the require throws,
|
|
38
|
+
# and this silently falls back to DEFAULT_ROOT — precisely the F6 shape described above, just
|
|
39
|
+
# triggered by the path separator instead of the cwd. Fixed alongside task 1002755, which hit
|
|
40
|
+
# the identical bug one resolution further down.
|
|
41
|
+
INSTANCE_ROOT="$(cd "$INVOKE_CWD" && node -e "process.stdout.write(require(process.argv[1]).resolveInstanceRootExplicit() || '')" "$DEFAULT_ROOT/src/instance-config" 2>/dev/null || true)"
|
|
36
42
|
INSTANCE_ROOT="${INSTANCE_ROOT:-$DEFAULT_ROOT}"
|
|
37
43
|
# Run from the instance root so instance-relative paths resolve (the post-deploy art check below)
|
|
38
44
|
# AND so the enabledModules() read finds the instance's config/modules.json (resolveInstanceRoot
|
|
@@ -49,17 +55,68 @@ if [ "${1:-}" = "--print-roots" ]; then
|
|
|
49
55
|
exit 0
|
|
50
56
|
fi
|
|
51
57
|
|
|
52
|
-
DB="${PGDATABASE:-amazonprimea}"
|
|
53
|
-
|
|
54
58
|
# Test/ops seam (added with the 1.18.19 space-in-path fix): --print-migrations walks the REAL core-migration loop but reports
|
|
55
59
|
# each path instead of applying it, and touches NO DB. tests/migrate_root_resolution.mjs uses it to
|
|
56
60
|
# assert that a checkout whose path contains a space still yields whole, readable paths. Reporting
|
|
57
61
|
# through the real loop (rather than a copy of it in the test) is the point: if the loop ever reverts
|
|
58
62
|
# to an unquoted `for f in $MIGRATIONS`, this seam fragments the paths and the test fails. Also handy
|
|
59
63
|
# in the field — `npm run migrate -- --print-migrations` shows exactly what migrate would apply.
|
|
64
|
+
#
|
|
65
|
+
# Resolved BEFORE the database name below, because a dry run touches no DB (every psql call is
|
|
66
|
+
# guarded by DRY_RUN) and so must not require one.
|
|
60
67
|
DRY_RUN=0
|
|
61
68
|
if [ "${1:-}" = "--print-migrations" ]; then DRY_RUN=1; fi
|
|
62
69
|
|
|
70
|
+
# --- Which database? (task 1002755) ---------------------------------------
|
|
71
|
+
# SAME precedence as src/bongos/pool.js: an explicit PGDATABASE wins, else the instance's own
|
|
72
|
+
# branding pack names it (config/branding.json db.database, else the neutral starter). Resolved by
|
|
73
|
+
# CALLING pool.instanceDbName() rather than re-reading the config here, so the migrator and the
|
|
74
|
+
# running server can never disagree about which database this instance is.
|
|
75
|
+
#
|
|
76
|
+
# There is deliberately NO literal fallback. This script ships inside the core to EVERY instance,
|
|
77
|
+
# so a baked-in default is one host's database name pointed at everyone else's box: on a co-hosted
|
|
78
|
+
# machine it silently targets a stranger's data, and everywhere else it dies with a confusing
|
|
79
|
+
# "database ... does not exist" long after the caller has moved its version pin. pool.js already
|
|
80
|
+
# made exactly this call for the server ("NOT hard-defaulting to a host's DB name", pool.js:10-16);
|
|
81
|
+
# this is the migrator catching up, not a new policy.
|
|
82
|
+
# The module path rides in as argv, NOT interpolated into the JS source: a Windows-shaped
|
|
83
|
+
# CORE_ROOT contains backslashes, and inside a JS string literal those are escape sequences
|
|
84
|
+
# ("\U", "\r"), so interpolating silently corrupts the path and this resolution degrades to
|
|
85
|
+
# empty. argv values are never parsed as JS.
|
|
86
|
+
DB="${PGDATABASE:-}"
|
|
87
|
+
DB_RESOLVER_ERR=""
|
|
88
|
+
if [ -z "$DB" ]; then
|
|
89
|
+
# stderr is CAPTURED, not discarded: "the pack names no database" and "the resolver could not
|
|
90
|
+
# run at all" are different problems with different fixes, and collapsing both into an empty
|
|
91
|
+
# string sends the operator to edit a config file when the real answer is `npm install`.
|
|
92
|
+
# (pool.js requires pg, so an instance with no node_modules fails here rather than resolving.)
|
|
93
|
+
if DB_RESOLVED="$(node -e "process.stdout.write(require(process.argv[1]).instanceDbName() || '')" "$CORE_ROOT/src/bongos/pool" 2>&1)"; then
|
|
94
|
+
DB="$DB_RESOLVED"
|
|
95
|
+
else
|
|
96
|
+
DB_RESOLVER_ERR="$DB_RESOLVED"
|
|
97
|
+
fi
|
|
98
|
+
fi
|
|
99
|
+
if [ -z "$DB" ] && [ "$DRY_RUN" = 0 ]; then
|
|
100
|
+
echo "migrate: cannot determine which database to migrate." >&2
|
|
101
|
+
if [ -n "$DB_RESOLVER_ERR" ]; then
|
|
102
|
+
echo " the db-name resolver did not run: $(printf '%s' "$DB_RESOLVER_ERR" | head -n 1)" >&2
|
|
103
|
+
echo " if the core's dependencies are not installed yet, run: npm install" >&2
|
|
104
|
+
else
|
|
105
|
+
echo " Set PGDATABASE, or give this instance a db.database in config/branding.json." >&2
|
|
106
|
+
fi
|
|
107
|
+
echo " Refusing to guess: a baked-in default would target another instance's database." >&2
|
|
108
|
+
exit 2
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
# Test/ops seam: --print-db reports the database this run WOULD migrate, and exits before any DB
|
|
112
|
+
# work. `bongos upgrade` calls it to learn the name up front, so its pre-flight can prove the DB is
|
|
113
|
+
# reachable BEFORE the version pin moves — otherwise an unresolvable name is only discovered at
|
|
114
|
+
# migrate time, and the pin has to be unwound (task 1002755).
|
|
115
|
+
if [ "${1:-}" = "--print-db" ]; then
|
|
116
|
+
printf '%s\n' "$DB"
|
|
117
|
+
exit 0
|
|
118
|
+
fi
|
|
119
|
+
|
|
63
120
|
# --- Instance-seed policy (task 1704 / ADR 0105) --------------------------
|
|
64
121
|
# OTB/product scope-truth (versions, goals, done_when_criteria) lives in seed
|
|
65
122
|
# migrations under migrations/instance/ (plus a few guarded blocks embedded in the
|
package/src/module-api.js
CHANGED
|
@@ -609,8 +609,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
609
609
|
// landed since 1.19.89 with no explicit bump. run 31717551308. (task 1002620)
|
|
610
610
|
// 1.19.91 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
611
611
|
// landed since 1.19.90 with no explicit bump. run 31722841411. (task 1002620)
|
|
612
|
+
// 1.19.92 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
613
|
+
// landed since 1.19.91 with no explicit bump. run 31727411036. (task 1002620)
|
|
612
614
|
// ---------------------------------------------------------------------------
|
|
613
|
-
const CORE_VERSION = '1.19.
|
|
615
|
+
const CORE_VERSION = '1.19.92'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
614
616
|
|
|
615
617
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
616
618
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { strict as assert } from 'node:assert';
|
|
19
19
|
import { execFileSync } from 'node:child_process';
|
|
20
20
|
import {
|
|
21
|
-
mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, symlinkSync, realpathSync,
|
|
21
|
+
mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, symlinkSync, copyFileSync, realpathSync,
|
|
22
22
|
} from 'node:fs';
|
|
23
23
|
import { tmpdir } from 'node:os';
|
|
24
24
|
import { fileURLToPath } from 'node:url';
|
|
@@ -116,7 +116,15 @@ function makeCore({ spaced, files = ['001_a.sql', '002_b.sql', '006_pms_v2.sql',
|
|
|
116
116
|
mkdirSync(path.join(dir, 'scripts'));
|
|
117
117
|
mkdirSync(path.join(dir, 'migrations'));
|
|
118
118
|
mkdirSync(path.join(dir, 'src'));
|
|
119
|
-
|
|
119
|
+
// Symlink so the fixture can never drift from the real script. Windows refuses symlinks without
|
|
120
|
+
// elevation (EPERM), so fall back to a COPY taken fresh from MIGRATE on each run — equally
|
|
121
|
+
// drift-proof, and it is what lets these cases run on a Windows checkout at all.
|
|
122
|
+
try {
|
|
123
|
+
symlinkSync(MIGRATE, path.join(dir, 'scripts', 'migrate.sh'));
|
|
124
|
+
} catch (e) {
|
|
125
|
+
if (e.code !== 'EPERM' && e.code !== 'EACCES') throw e;
|
|
126
|
+
copyFileSync(MIGRATE, path.join(dir, 'scripts', 'migrate.sh'));
|
|
127
|
+
}
|
|
120
128
|
// Pin both roots to the fixture explicitly, so the test does not lean on migrate.sh's
|
|
121
129
|
// node-unavailable fallback to decide what CORE_ROOT is.
|
|
122
130
|
writeFileSync(path.join(dir, 'src', 'instance-config.js'), [
|
|
@@ -216,5 +224,95 @@ t('the pre-fix unquoted loop now fails loudly instead of silently skipping', ()
|
|
|
216
224
|
assert.equal(r.applied.length, 0, 'the unquoted loop applies nothing (that was the bug)');
|
|
217
225
|
});
|
|
218
226
|
|
|
227
|
+
// ==========================================================================
|
|
228
|
+
// WHICH DATABASE does migrate target? (task 1002755)
|
|
229
|
+
//
|
|
230
|
+
// scripts/migrate.sh:52 used to read `DB="${PGDATABASE:-amazonprimea}"` — one host instance's
|
|
231
|
+
// database name, baked into a script the core ships to EVERY instance. With no PGDATABASE in the
|
|
232
|
+
// environment, a co-hosted box silently aimed at a stranger's database and everywhere else died
|
|
233
|
+
// with a confusing "database ... does not exist" — after the caller had already moved its version
|
|
234
|
+
// pin. ADR 0149:17 had already named the divergence: the server pool resolves from the branding
|
|
235
|
+
// pack while migrate.sh defaulted to a literal, and "they only agree if PGDATABASE is set for both."
|
|
236
|
+
//
|
|
237
|
+
// These drive the `--print-db` seam, which resolves the name and exits before any psql — the
|
|
238
|
+
// --print-roots pattern. The fixture carries a REAL config/branding.json so the assertions
|
|
239
|
+
// exercise the actual resolver (pool.instanceDbName) rather than a stub of it.
|
|
240
|
+
|
|
241
|
+
// A core fixture that is ALSO an instance: adds the branding pack instanceDbName() reads.
|
|
242
|
+
// db.database === null models the neutral starter — an instance that has not named its database.
|
|
243
|
+
function makeInstance({ database } = {}) {
|
|
244
|
+
const dir = makeCore({ spaced: false });
|
|
245
|
+
mkdirSync(path.join(dir, 'config'), { recursive: true });
|
|
246
|
+
writeFileSync(
|
|
247
|
+
path.join(dir, 'config', 'branding.json'),
|
|
248
|
+
`${JSON.stringify({ db: { database: database ?? null } }, null, 2)}\n`,
|
|
249
|
+
);
|
|
250
|
+
// migrate.sh resolves the name through <CORE_ROOT>/src/bongos/pool. Re-export the REAL module
|
|
251
|
+
// so these cases exercise the actual resolver rather than a restatement of it — a stub here
|
|
252
|
+
// would pass happily while pool.js drifted. The real file resolves its own `pg` from the repo's
|
|
253
|
+
// node_modules (module resolution walks up from ITS path, not the fixture's), and its
|
|
254
|
+
// resolveInstanceRoot() falls back to cwd — which migrate.sh has already set to this fixture.
|
|
255
|
+
mkdirSync(path.join(dir, 'src', 'bongos'), { recursive: true });
|
|
256
|
+
writeFileSync(
|
|
257
|
+
path.join(dir, 'src', 'bongos', 'pool.js'),
|
|
258
|
+
`module.exports = require(${JSON.stringify(path.join(REPO_ROOT, 'src', 'bongos', 'pool.js'))});\n`,
|
|
259
|
+
);
|
|
260
|
+
return dir;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function printDb(coreDir, extraEnv = {}) {
|
|
264
|
+
const env = { ...process.env, ...extraEnv };
|
|
265
|
+
for (const k of Object.keys(env)) if (/_INSTANCE_ROOT$/.test(k)) delete env[k];
|
|
266
|
+
if (!('PGDATABASE' in extraEnv)) delete env.PGDATABASE;
|
|
267
|
+
try {
|
|
268
|
+
const stdout = execFileSync('bash', [path.join(coreDir, 'scripts', 'migrate.sh'), '--print-db'],
|
|
269
|
+
{ cwd: coreDir, env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
270
|
+
return { status: 0, stdout: stdout.trim(), stderr: '' };
|
|
271
|
+
} catch (e) {
|
|
272
|
+
return { status: e.status ?? 1, stdout: String(e.stdout || '').trim(), stderr: String(e.stderr || '') };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
t('--print-db reports the database named by the instance\'s own branding pack', () => {
|
|
277
|
+
const r = printDb(makeInstance({ database: 'acme_instance_db' }));
|
|
278
|
+
assert.equal(r.status, 0, r.stderr);
|
|
279
|
+
assert.equal(r.stdout, 'acme_instance_db');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
t('an explicit PGDATABASE wins over the pack — same precedence as pool.js', () => {
|
|
283
|
+
const r = printDb(makeInstance({ database: 'from_the_pack' }), { PGDATABASE: 'from_the_env' });
|
|
284
|
+
assert.equal(r.status, 0, r.stderr);
|
|
285
|
+
assert.equal(r.stdout, 'from_the_env');
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
t('an instance that names no database is REFUSED, never defaulted', () => {
|
|
289
|
+
const r = printDb(makeInstance({ database: null }));
|
|
290
|
+
assert.equal(r.status, 2, 'must exit 2 — the seam refuses rather than printing an empty name');
|
|
291
|
+
assert.doesNotMatch(r.stdout, /\S/, 'nothing is printed as the database name');
|
|
292
|
+
assert.match(r.stderr, /cannot determine which database/i);
|
|
293
|
+
assert.match(r.stderr, /Refusing to guess/i);
|
|
294
|
+
// the regression itself: it must not reach for another instance's database
|
|
295
|
+
assert.doesNotMatch(`${r.stdout}${r.stderr}`, /amazonprimea/i);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
t('--print-migrations stays DB-free — a dry run must not require a database name', () => {
|
|
299
|
+
// the seam walks the real loop and touches no psql, so an instance with no db.database
|
|
300
|
+
// must still be able to list what would be applied
|
|
301
|
+
const core = makeInstance({ database: null });
|
|
302
|
+
const r = printMigrations(core);
|
|
303
|
+
assert.equal(r.status, 0, `a dry run must not need a database: ${r.stderr}`);
|
|
304
|
+
assert.equal(r.listed, 4);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
t('migrate.sh hardcodes no instance database name', () => {
|
|
308
|
+
// the static lock on the original defect — a literal default reintroduced anywhere in the
|
|
309
|
+
// script points every other instance at a database that is not theirs
|
|
310
|
+
const src = readFileSync(MIGRATE, 'utf8');
|
|
311
|
+
const code = src.split(/\r?\n/).filter((l) => !/^\s*#/.test(l)).join('\n');
|
|
312
|
+
assert.doesNotMatch(code, /PGDATABASE:-\s*[A-Za-z]/,
|
|
313
|
+
'PGDATABASE must have no literal fallback — resolve from the branding pack instead');
|
|
314
|
+
assert.doesNotMatch(code, /amazonprimea/i, 'no instance database name in executable lines');
|
|
315
|
+
});
|
|
316
|
+
|
|
219
317
|
console.log(`\nmigrate_root_resolution: ${passed} passed, ${failed} failed`);
|
|
220
318
|
process.exit(failed ? 1 : 0);
|
package/tests/upgrade.mjs
CHANGED
|
@@ -1033,6 +1033,64 @@ t('runUpgrade: a REFUSED bump still reports the non-blocking module findings', a
|
|
|
1033
1033
|
assert.match(text, /author unreachable/);
|
|
1034
1034
|
});
|
|
1035
1035
|
|
|
1036
|
+
// ── which database will migrate target? (task 1002755) ────────────────────
|
|
1037
|
+
// The pre-flight probe is only meaningful if it tests the SAME database migrate will use. It used
|
|
1038
|
+
// to be handed process.env.PGDATABASE — undefined on a normal box — so psql fell through to a
|
|
1039
|
+
// database named after the OS user, failed for an unrelated reason, and soft-skipped; migrate then
|
|
1040
|
+
// picked its own name and died on a database that did not exist, AFTER the pin had moved.
|
|
1041
|
+
// The three-way result is the point: "names no database" and "resolver could not run" are
|
|
1042
|
+
// different problems and only the first may block a bump.
|
|
1043
|
+
|
|
1044
|
+
t('resolveInstanceDb: an explicit PGDATABASE wins without spawning anything', () => {
|
|
1045
|
+
const prev = process.env.PGDATABASE;
|
|
1046
|
+
process.env.PGDATABASE = 'from_the_env';
|
|
1047
|
+
try {
|
|
1048
|
+
let spawned = false;
|
|
1049
|
+
const r = u.resolveInstanceDb({ instanceDir: '/x' }, () => { spawned = true; return { status: 0, stdout: 'other' }; });
|
|
1050
|
+
assert.deepEqual(r, { db: 'from_the_env', ran: true });
|
|
1051
|
+
assert.equal(spawned, false, 'the env answer needs no child process');
|
|
1052
|
+
} finally {
|
|
1053
|
+
if (prev === undefined) delete process.env.PGDATABASE; else process.env.PGDATABASE = prev;
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
t('resolveInstanceDb: reports the name the core resolver printed', () => {
|
|
1058
|
+
const prev = process.env.PGDATABASE;
|
|
1059
|
+
delete process.env.PGDATABASE;
|
|
1060
|
+
try {
|
|
1061
|
+
const r = u.resolveInstanceDb({ instanceDir: '/x' }, () => ({ status: 0, stdout: 'acme_db\n' }));
|
|
1062
|
+
assert.deepEqual(r, { db: 'acme_db', ran: true });
|
|
1063
|
+
} finally { if (prev !== undefined) process.env.PGDATABASE = prev; }
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
t('resolveInstanceDb: ran-but-empty is FATAL, unrunnable is not', () => {
|
|
1067
|
+
const prev = process.env.PGDATABASE;
|
|
1068
|
+
delete process.env.PGDATABASE;
|
|
1069
|
+
try {
|
|
1070
|
+
// the instance genuinely names no database — knowable, and a bump must stop
|
|
1071
|
+
assert.deepEqual(u.resolveInstanceDb({ instanceDir: '/x' }, () => ({ status: 0, stdout: '' })),
|
|
1072
|
+
{ db: null, ran: true });
|
|
1073
|
+
// the resolver could not run — a DIFFERENT problem; must not be reported as "no database"
|
|
1074
|
+
assert.deepEqual(u.resolveInstanceDb({ instanceDir: '/x' }, () => ({ status: 1, stdout: '' })),
|
|
1075
|
+
{ db: null, ran: false });
|
|
1076
|
+
assert.deepEqual(u.resolveInstanceDb({ instanceDir: '/x' }, () => ({ error: new Error('ENOENT') })),
|
|
1077
|
+
{ db: null, ran: false });
|
|
1078
|
+
} finally { if (prev !== undefined) process.env.PGDATABASE = prev; }
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
t('runUpgrade: refuses BEFORE the pin moves when the instance names no database', async () => {
|
|
1082
|
+
const dir = mkdtempSync(join(tmpdir(), 'nodb-'));
|
|
1083
|
+
writeFileSync(join(dir, 'package.json'), JSON.stringify({ dependencies: { '@bongos/core': '1.0.0' } }, null, 2));
|
|
1084
|
+
const res = await u.runUpgrade({ to: '1.0.1', instance: dir, registry: true, force: true }, {
|
|
1085
|
+
resolveInstanceDb: () => ({ db: null, ran: true }),
|
|
1086
|
+
log: () => {},
|
|
1087
|
+
});
|
|
1088
|
+
assert.equal(res.ok, false);
|
|
1089
|
+
assert.match(res.error, /cannot determine which database/i);
|
|
1090
|
+
// the pin must be untouched — that is the whole point of checking in pre-flight
|
|
1091
|
+
assert.match(readFileSync(join(dir, 'package.json'), 'utf8'), /"@bongos\/core": "1\.0\.0"/);
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1036
1094
|
await Promise.all(pending);
|
|
1037
1095
|
console.log(`\nupgrade: ${passed} passed, ${failed} failed`);
|
|
1038
1096
|
process.exit(failed ? 1 : 0);
|