@bongos/core 1.19.775 → 1.19.777
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 +25 -15
- package/docs/adr/0290-disconnect-is-the-owner-initiated-exception-to-address-retention.md +1 -1
- package/docs/module-api-changelog.md +4 -0
- package/modules/provisioning/migrations/provisioning_017_domain_lookup_index.sql +20 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/db-backup-nightly.sh +7 -3
- package/scripts/gds/provision-disconnect.js +2 -2
- package/src/module-api.js +1 -1
- package/tests/db_backup_nightly.mjs +21 -1
- package/tests/provisioning_domain_lookup_index.mjs +85 -0
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.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.777",
|
|
6
|
+
"core_contract": "1.19.777",
|
|
7
|
+
"source_commit": "8c4c3db04491300feb300426c07f7d4bce19f981",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-16T18:
|
|
9
|
+
"built_at": "2026-09-16T18:55:13.712Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 486,
|
|
13
13
|
"agent_docs_stubbed": 25,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2153,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2664,
|
|
20
|
+
"tree_sha256": "48b4056d95f14a44152bbeafa4b8208f71b3bbb6890cb8fff14b4b5a70f27b10",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -1957,7 +1957,7 @@
|
|
|
1957
1957
|
{
|
|
1958
1958
|
"path": "docs/adr/0290-disconnect-is-the-owner-initiated-exception-to-address-retention.md",
|
|
1959
1959
|
"mode": "0000644",
|
|
1960
|
-
"sha256": "
|
|
1960
|
+
"sha256": "37ab80018f5d9e6fdb35271be80ca07f10e6e593ef33b9a7a0a30fe9ea8c5fb3"
|
|
1961
1961
|
},
|
|
1962
1962
|
{
|
|
1963
1963
|
"path": "docs/adr/README.md",
|
|
@@ -2527,7 +2527,7 @@
|
|
|
2527
2527
|
{
|
|
2528
2528
|
"path": "docs/module-api-changelog.md",
|
|
2529
2529
|
"mode": "0000644",
|
|
2530
|
-
"sha256": "
|
|
2530
|
+
"sha256": "48accd0cc072b560c546805d257d6ac9695ec17a3702aa7434c17b6eb632f819"
|
|
2531
2531
|
},
|
|
2532
2532
|
{
|
|
2533
2533
|
"path": "docs/modules-contract.md",
|
|
@@ -6309,6 +6309,11 @@
|
|
|
6309
6309
|
"mode": "0000644",
|
|
6310
6310
|
"sha256": "aba202502cffe31a031facd7b8e902315b81729d42028d857fa913754f2f0a7b"
|
|
6311
6311
|
},
|
|
6312
|
+
{
|
|
6313
|
+
"path": "modules/provisioning/migrations/provisioning_017_domain_lookup_index.sql",
|
|
6314
|
+
"mode": "0000644",
|
|
6315
|
+
"sha256": "69fa8f9cdc3c9ec272c318226558254267ae63ef2ffa68ab93a89a3ab5d782a2"
|
|
6316
|
+
},
|
|
6312
6317
|
{
|
|
6313
6318
|
"path": "modules/provisioning/module.json",
|
|
6314
6319
|
"mode": "0000644",
|
|
@@ -7627,12 +7632,12 @@
|
|
|
7627
7632
|
{
|
|
7628
7633
|
"path": "package-lock.json",
|
|
7629
7634
|
"mode": "0000644",
|
|
7630
|
-
"sha256": "
|
|
7635
|
+
"sha256": "620248e6545e156fd98d84b9dc29bdad59a77be39ba3e761a90d62d385255f4d"
|
|
7631
7636
|
},
|
|
7632
7637
|
{
|
|
7633
7638
|
"path": "package.json",
|
|
7634
7639
|
"mode": "0000644",
|
|
7635
|
-
"sha256": "
|
|
7640
|
+
"sha256": "d4ebe23ce76cdf1091ce8ab656a5199df19fb171768a947ff7cad259f9262dfd"
|
|
7636
7641
|
},
|
|
7637
7642
|
{
|
|
7638
7643
|
"path": "public-docs/index.html",
|
|
@@ -7987,7 +7992,7 @@
|
|
|
7987
7992
|
{
|
|
7988
7993
|
"path": "scripts/gds/db-backup-nightly.sh",
|
|
7989
7994
|
"mode": "0000644",
|
|
7990
|
-
"sha256": "
|
|
7995
|
+
"sha256": "93f0b743b90a0cfdc91806ecb10da20456b98c0fac0f2b8eb3d690bccf79e532"
|
|
7991
7996
|
},
|
|
7992
7997
|
{
|
|
7993
7998
|
"path": "scripts/gds/db-backup-offsite.sh",
|
|
@@ -8487,7 +8492,7 @@
|
|
|
8487
8492
|
{
|
|
8488
8493
|
"path": "scripts/gds/provision-disconnect.js",
|
|
8489
8494
|
"mode": "0000644",
|
|
8490
|
-
"sha256": "
|
|
8495
|
+
"sha256": "81cdd20b43aa1fc8b39a4fa06aada292aa00e84724feb803ba96bf15ce92cca9"
|
|
8491
8496
|
},
|
|
8492
8497
|
{
|
|
8493
8498
|
"path": "scripts/gds/provision-net.js",
|
|
@@ -9492,7 +9497,7 @@
|
|
|
9492
9497
|
{
|
|
9493
9498
|
"path": "src/module-api.js",
|
|
9494
9499
|
"mode": "0000644",
|
|
9495
|
-
"sha256": "
|
|
9500
|
+
"sha256": "3791abffec940edb90c976525b388d122d16ff5613c1ee1a487d6e9f1ad1a1e0"
|
|
9496
9501
|
},
|
|
9497
9502
|
{
|
|
9498
9503
|
"path": "src/module-loader/catalog.js",
|
|
@@ -10357,7 +10362,7 @@
|
|
|
10357
10362
|
{
|
|
10358
10363
|
"path": "tests/db_backup_nightly.mjs",
|
|
10359
10364
|
"mode": "0000644",
|
|
10360
|
-
"sha256": "
|
|
10365
|
+
"sha256": "cb731d19610530cc07457271aed57d5e04bec3d42bf85f246601b6cbdd02b3fd"
|
|
10361
10366
|
},
|
|
10362
10367
|
{
|
|
10363
10368
|
"path": "tests/dead_deps.mjs",
|
|
@@ -12229,6 +12234,11 @@
|
|
|
12229
12234
|
"mode": "0000644",
|
|
12230
12235
|
"sha256": "62ea8ffc2c21c351289dc7f96cab38fcd5824afba1fed0df7a545cd5df9e396c"
|
|
12231
12236
|
},
|
|
12237
|
+
{
|
|
12238
|
+
"path": "tests/provisioning_domain_lookup_index.mjs",
|
|
12239
|
+
"mode": "0000644",
|
|
12240
|
+
"sha256": "77110372442881ea3beaa59f25d08128e0dd2b92c49a78779567454ec8298d38"
|
|
12241
|
+
},
|
|
12232
12242
|
{
|
|
12233
12243
|
"path": "tests/provisioning_domain_unique.mjs",
|
|
12234
12244
|
"mode": "0000644",
|
|
@@ -35,6 +35,6 @@ A failed erase or catalog step returns a retryable `{ ok:false }`; a teardown th
|
|
|
35
35
|
- The manage page's copy changed with the capability. The teardown truth now ends "Taking it offline erases nothing." rather than "Nothing here erases a project.", and the fold points at the new card instead of admitting the capability is missing.
|
|
36
36
|
- The `project.catalog` port has three verbs. `tests/catalog_only_client.mjs` still pins the list.
|
|
37
37
|
- If the runner dies mid-disconnect, the reconcile sweep re-enqueues a *teardown* for a `tearing_down` row, not a disconnect. The project is left offline, with its row, and the owner presses Disconnect again. This is accepted rather than giving the reconcile a memory of intent.
|
|
38
|
-
- The nightly prune's `<db>-*.sql.gz` glob
|
|
38
|
+
- The nightly prune's `<db>-*.sql.gz` glob could still reach a longer-named sibling's dumps older than 30 days. It was filed as its own row and fixed in [task 1003948](https://cloudbongos.com/builders#/task/1003948): the prune and its retained count now match the same exact filename.
|
|
39
39
|
|
|
40
40
|
Proofs: `tests/provisioning_disconnect.mjs` (queue + route), `tests/provision_disconnect.mjs` (runner order, shapes, failure, guards), `tests/project_catalog_forget.mjs` (claim rules), `tests/hub_disconnect_ui.mjs` + the kit probe (the card).
|
|
@@ -2024,5 +2024,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2024
2024
|
landed since 1.19.773 with no explicit bump. run 35132992149. (task 1002620)
|
|
2025
2025
|
1.19.775 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2026
2026
|
landed since 1.19.774 with no explicit bump. run 35133750943. (task 1002620)
|
|
2027
|
+
1.19.776 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2028
|
+
landed since 1.19.775 with no explicit bump. run 35135604764. (task 1002620)
|
|
2029
|
+
1.19.777 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2030
|
+
landed since 1.19.776 with no explicit bump. run 35137334438. (task 1002620)
|
|
2027
2031
|
---------------------------------------------------------------------------
|
|
2028
2032
|
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
-- provisioning_017_domain_lookup_index.sql — an index every lower(domain) read can
|
|
2
|
+
-- use (task 1003541; grader minor on PR 451, task 1003347).
|
|
3
|
+
--
|
|
4
|
+
-- WHY. Every address read in provisioning.js matches `lower(domain)`, and the only
|
|
5
|
+
-- index on that expression is provisioning_015's UNIQUE one, which is PARTIAL on
|
|
6
|
+
-- status <> 'torn_down' (ADR 0181 D2 keeps a torn-down address for the revive
|
|
7
|
+
-- path). A query that does not also exclude torn-down rows cannot use a partial
|
|
8
|
+
-- index, so planetChoicesForHosts — the provisioning.projectPlanets seam behind
|
|
9
|
+
-- every public /projects/featured read — scanned the whole table. It keeps
|
|
10
|
+
-- torn-down rows on purpose (an offline project still shows its owner's planet),
|
|
11
|
+
-- so the fix is an index, not a narrower query.
|
|
12
|
+
--
|
|
13
|
+
-- Non-unique and non-partial: uniqueness stays provisioning_015's job. Additive,
|
|
14
|
+
-- no row changes. Applied only when the provisioning module is enabled. Safe to re-run.
|
|
15
|
+
|
|
16
|
+
CREATE INDEX IF NOT EXISTS ix_provisioning_instances_lower_domain
|
|
17
|
+
ON provisioning_instances (lower(domain));
|
|
18
|
+
|
|
19
|
+
INSERT INTO schema_migrations (version) VALUES ('provisioning_017_domain_lookup_index')
|
|
20
|
+
ON CONFLICT (version) DO NOTHING;
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.777",
|
|
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.777",
|
|
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.777",
|
|
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",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
#
|
|
4
4
|
# Runs as user `lars` via the gds-db-backup.timer systemd unit (see
|
|
5
5
|
# scripts/gds/gds-db-backup.timer + .service in this repo). Output lands
|
|
6
|
-
# in /var/backups/gds/ as YYYY-MM-
|
|
6
|
+
# in /var/backups/gds/ as <db>-YYYY-MM-DDTHHMMSSZ.sql.gz (UTC timestamp), and
|
|
7
7
|
# files older than 30 days are pruned.
|
|
8
8
|
#
|
|
9
9
|
# Authentication: peer auth via the local Unix socket — the invoking unix
|
|
@@ -59,6 +59,10 @@ RETENTION_DAYS="${GDS_BACKUP_RETENTION_DAYS:-30}"
|
|
|
59
59
|
# Timestamp format. UTC so dumps sort sanely regardless of TZ.
|
|
60
60
|
TS=$(date -u +"%Y-%m-%dT%H%M%SZ")
|
|
61
61
|
OUT="${BACKUP_DIR}/${DB_NAME}-${TS}.sql.gz"
|
|
62
|
+
# Exactly the name above. Every project's unit shares BACKUP_DIR, so a looser
|
|
63
|
+
# `${DB_NAME}-*` would let `test` prune and count `test-nk`'s dumps (task 1003948).
|
|
64
|
+
D='[0-9]'
|
|
65
|
+
DUMP_GLOB="${DB_NAME}-${D}${D}${D}${D}-${D}${D}-${D}${D}T${D}${D}${D}${D}${D}${D}Z.sql.gz"
|
|
62
66
|
|
|
63
67
|
# ---- preconditions ----------------------------------------------------------
|
|
64
68
|
|
|
@@ -173,7 +177,7 @@ fi
|
|
|
173
177
|
# Done AFTER the new dump lands so a streak of failed runs can't erase
|
|
174
178
|
# everything: we never delete on a day we didn't successfully dump.
|
|
175
179
|
PRUNED=$(find "$BACKUP_DIR" -maxdepth 1 -type f \
|
|
176
|
-
-name "$
|
|
180
|
+
-name "$DUMP_GLOB" \
|
|
177
181
|
-mtime +"${RETENTION_DAYS}" \
|
|
178
182
|
-print -delete | wc -l)
|
|
179
183
|
|
|
@@ -183,5 +187,5 @@ fi
|
|
|
183
187
|
|
|
184
188
|
# ---- emit a one-line summary for journald + monitoring ---------------------
|
|
185
189
|
|
|
186
|
-
REMAINING=$(find "$BACKUP_DIR" -maxdepth 1 -type f -name "$
|
|
190
|
+
REMAINING=$(find "$BACKUP_DIR" -maxdepth 1 -type f -name "$DUMP_GLOB" | wc -l)
|
|
187
191
|
echo "[$(date -u +%FT%TZ)] gds-db-backup: ok · ${REMAINING} dump(s) retained · ${BYTES} bytes latest"
|
|
@@ -28,8 +28,8 @@ const { catalogOriginForInstance, ownerGithubIdForInstance } = require('../../mo
|
|
|
28
28
|
const { deleteInstance } = require('../../modules/provisioning/disconnect.js');
|
|
29
29
|
|
|
30
30
|
// The dump filename db-backup-nightly.sh writes — <db>-YYYY-MM-DDTHHMMSSZ.sql.gz — matched
|
|
31
|
-
// EXACTLY
|
|
32
|
-
// `test-nk`'s dumps
|
|
31
|
+
// EXACTLY, as the nightly prune's DUMP_GLOB does (task 1003948): a looser `<db>-*.sql.gz` would
|
|
32
|
+
// let a project named `test` erase `test-nk`'s dumps. PURE.
|
|
33
33
|
function dumpGlob(inst) {
|
|
34
34
|
const d = '[0-9]';
|
|
35
35
|
return `${dbName(inst)}-${d.repeat(4)}-${d.repeat(2)}-${d.repeat(2)}T${d.repeat(6)}Z.sql.gz`;
|
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.777'; // 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');
|
|
@@ -29,12 +29,19 @@ const ENV = 'gds_backup_'.toUpperCase();
|
|
|
29
29
|
// A fake pg_dump: a plain-format dump of `rows` rows, closed the way pg_dump closes one
|
|
30
30
|
// (including the \unrestrict line patched servers append) unless it is `cut` short, and
|
|
31
31
|
// exiting `exitCode`.
|
|
32
|
-
|
|
32
|
+
// `seed` pre-places dumps in the shared dir as [filename, ageInDays].
|
|
33
|
+
function runBackup({ rows = 40, cut = false, exitCode = 0, env = {}, seed = [] } = {}) {
|
|
33
34
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'db-backup-'));
|
|
34
35
|
const bin = path.join(tmp, 'bin');
|
|
35
36
|
const dir = path.join(tmp, 'backups');
|
|
36
37
|
fs.mkdirSync(bin);
|
|
37
38
|
fs.mkdirSync(dir);
|
|
39
|
+
for (const [name, ageDays] of seed) {
|
|
40
|
+
const f = path.join(dir, name);
|
|
41
|
+
fs.writeFileSync(f, '');
|
|
42
|
+
const t = new Date(Date.now() - ageDays * 86400000);
|
|
43
|
+
fs.utimesSync(f, t, t);
|
|
44
|
+
}
|
|
38
45
|
const body = Array.from({ length: rows }, (_, i) => `INSERT INTO public.t VALUES (${i});`).join('\n');
|
|
39
46
|
const close = cut ? '' : '\n--\n-- PostgreSQL database dump complete\n--\n\n\\unrestrict abc123\n';
|
|
40
47
|
fs.writeFileSync(path.join(bin, 'pg_dump'),
|
|
@@ -73,6 +80,19 @@ test('an explicit operator size floor is still enforced', { skip }, () => {
|
|
|
73
80
|
assert.match(r.stderr, /floor/);
|
|
74
81
|
});
|
|
75
82
|
|
|
83
|
+
test('the prune and the count reach only this database\'s own dumps, never a longer-named sibling\'s (task 1003948)', { skip }, () => {
|
|
84
|
+
const r = runBackup({ seed: [
|
|
85
|
+
['smallproj-2026-01-01T000000Z.sql.gz', 40],
|
|
86
|
+
['smallproj-nk-2026-01-01T000000Z.sql.gz', 40],
|
|
87
|
+
['smallproj-2-2026-09-01T000000Z.sql.gz', 2],
|
|
88
|
+
] });
|
|
89
|
+
assert.equal(r.status, 0, r.stderr);
|
|
90
|
+
assert.ok(!r.dumps.includes('smallproj-2026-01-01T000000Z.sql.gz'), 'its own expired dump is pruned');
|
|
91
|
+
assert.ok(r.dumps.includes('smallproj-nk-2026-01-01T000000Z.sql.gz'), 'a sibling\'s expired dump is left for the sibling\'s own unit');
|
|
92
|
+
assert.match(r.stdout, /pruned 1 dump\(s\)/);
|
|
93
|
+
assert.match(r.stdout, /ok · 1 dump\(s\) retained/, 'the count is this database\'s dumps only');
|
|
94
|
+
});
|
|
95
|
+
|
|
76
96
|
test('no fixed size floor is baked in for every instance', () => {
|
|
77
97
|
const src = fs.readFileSync(SCRIPT, 'utf8');
|
|
78
98
|
assert.doesNotMatch(src, new RegExp(`${ENV}MIN_BYTES:-[1-9]`), 'a default floor fails every database smaller than it');
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// tests/provisioning_domain_lookup_index.mjs
|
|
2
|
+
//
|
|
3
|
+
// The index behind the planet seam's address read (task 1003541).
|
|
4
|
+
//
|
|
5
|
+
// planetChoicesForHosts — the provisioning.projectPlanets port every public
|
|
6
|
+
// /projects/featured read calls — matches lower(domain) and deliberately keeps
|
|
7
|
+
// torn-down rows, because the catalog still lists an offline project and its
|
|
8
|
+
// owner's planet should still draw. provisioning_015's lower(domain) index is
|
|
9
|
+
// PARTIAL on status <> 'torn_down', which such a query cannot use, so the read
|
|
10
|
+
// scanned the whole table. provisioning_017 adds the index it can use.
|
|
11
|
+
//
|
|
12
|
+
// These tests hold the pair together: the migration's expression and the seam's
|
|
13
|
+
// predicate must stay the same expression, and the seam must not "fix" the scan by
|
|
14
|
+
// quietly dropping torn-down rows.
|
|
15
|
+
//
|
|
16
|
+
// DB-free: the seam runs against a fake `db` that records its SQL. Postgres is not
|
|
17
|
+
// asked to EXPLAIN; there is no DB lane in CI (task 1003381).
|
|
18
|
+
//
|
|
19
|
+
// Run: node tests/provisioning_domain_lookup_index.mjs
|
|
20
|
+
|
|
21
|
+
import { strict as assert } from 'node:assert';
|
|
22
|
+
import { createRequire } from 'node:module';
|
|
23
|
+
import fs from 'node:fs';
|
|
24
|
+
|
|
25
|
+
const require = createRequire(import.meta.url);
|
|
26
|
+
const provisioning = require('../modules/provisioning/provisioning.js');
|
|
27
|
+
|
|
28
|
+
let passed = 0, failed = 0;
|
|
29
|
+
function t(name, fn) {
|
|
30
|
+
try { fn(); passed++; console.log(` PASS ${name}`); }
|
|
31
|
+
catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
|
|
32
|
+
}
|
|
33
|
+
async function ta(name, fn) {
|
|
34
|
+
try { await fn(); passed++; console.log(` PASS ${name}`); }
|
|
35
|
+
catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const SQL = fs.readFileSync(new URL('../modules/provisioning/migrations/provisioning_017_domain_lookup_index.sql', import.meta.url), 'utf8');
|
|
39
|
+
const CREATE = (SQL.match(/CREATE[\s\S]*?;/) || [''])[0];
|
|
40
|
+
|
|
41
|
+
console.log('the migration — provisioning_017_domain_lookup_index.sql:');
|
|
42
|
+
|
|
43
|
+
t('indexes lower(domain), the expression every address read matches', () => {
|
|
44
|
+
assert.match(CREATE, /CREATE INDEX IF NOT EXISTS ix_provisioning_instances_lower_domain\s+ON provisioning_instances \(lower\(domain\)\)/);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
t('is neither UNIQUE nor PARTIAL — uniqueness stays provisioning_015\'s, and a partial index is what the seam could not use', () => {
|
|
48
|
+
assert.doesNotMatch(CREATE, /UNIQUE/);
|
|
49
|
+
assert.doesNotMatch(CREATE, /WHERE/);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
t('self-registers and is safe to re-run', () => {
|
|
53
|
+
assert.match(SQL, /INSERT INTO schema_migrations \(version\) VALUES \('provisioning_017_domain_lookup_index'\)\s+ON CONFLICT \(version\) DO NOTHING/);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
console.log('\nthe seam — planetChoicesForHosts:');
|
|
57
|
+
|
|
58
|
+
function recordingDb(rows = []) {
|
|
59
|
+
const calls = [];
|
|
60
|
+
return { calls, query: async (sql, params) => { calls.push({ sql, params }); return { rows }; } };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await ta('matches on the same lower(domain) expression the index is built on, with the hosts lowercased', async () => {
|
|
64
|
+
const db = recordingDb();
|
|
65
|
+
await provisioning.planetChoicesForHosts(db, ['Mercury.Example.com', 'mercury.example.com', '']);
|
|
66
|
+
assert.equal(db.calls.length, 1);
|
|
67
|
+
assert.match(db.calls[0].sql, /lower\(domain\) = ANY\(\$1\)/);
|
|
68
|
+
assert.deepEqual(db.calls[0].params, [['mercury.example.com']]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
await ta('keeps torn-down rows — an offline project still draws its owner\'s planet', async () => {
|
|
72
|
+
const db = recordingDb([{ host: 'lantern.example.com', status: 'torn_down', settings: { planet_template: 'blob-tint' }, updated_at: '2026-09-16' }]);
|
|
73
|
+
const out = await provisioning.planetChoicesForHosts(db, ['lantern.example.com']);
|
|
74
|
+
assert.doesNotMatch(db.calls[0].sql, /status/, 'no status filter — that would change what the map draws, not just how fast');
|
|
75
|
+
assert.equal(out['lantern.example.com'].planet_template, 'blob-tint');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
await ta('no hosts asks nothing', async () => {
|
|
79
|
+
const db = recordingDb();
|
|
80
|
+
assert.deepEqual(await provisioning.planetChoicesForHosts(db, []), {});
|
|
81
|
+
assert.equal(db.calls.length, 0);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
console.log(`\nprovisioning_domain_lookup_index: ${passed} passed, ${failed} failed`);
|
|
85
|
+
process.exit(failed ? 1 : 0);
|