@bongos/core 1.21.131 → 1.21.132

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,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.21.131",
6
- "core_contract": "1.21.131",
7
- "source_commit": "20e5d1edc0b0fcf6174f198a5b4a419dd665a3a7",
5
+ "core_version": "1.21.132",
6
+ "core_contract": "1.21.132",
7
+ "source_commit": "10dc21ed515c04c95e9dba666c809be9ea45a593",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-10-15T17:40:46.146Z",
9
+ "built_at": "2026-10-15T18:14:12.382Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 606,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 3781,
20
- "tree_sha256": "31c81958624432df97a7d5ddcbbb268280c384ac5411f4c91015de0d01141376",
20
+ "tree_sha256": "2f366274bb2738a2385ff8682e82cddcda6f1cffea387e43a5a9001755b9ee84",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2922,7 +2922,7 @@
2922
2922
  {
2923
2923
  "path": "docs/module-api-changelog.md",
2924
2924
  "mode": "0000644",
2925
- "sha256": "fa69c03c8f48c36f148522c0f882c1f075c4d534a53e5cc2d0f97a587f53af44"
2925
+ "sha256": "fcac7f752834fcc7244b69e173b1c9711bb86100e1904e3a59933fa508716c0c"
2926
2926
  },
2927
2927
  {
2928
2928
  "path": "docs/modules-contract.md",
@@ -8567,7 +8567,7 @@
8567
8567
  {
8568
8568
  "path": "modules/provisioning/github-setup.js",
8569
8569
  "mode": "0000644",
8570
- "sha256": "3de4b170e869c21236d5e5c60f4a0774788dc3022a9879c20dab09a50b210f04"
8570
+ "sha256": "ba6b384d7a7a73bd38f0db9f1d93bd02ebd87c60014bfbd7494bf4a9010c5538"
8571
8571
  },
8572
8572
  {
8573
8573
  "path": "modules/provisioning/look.js",
@@ -8802,7 +8802,7 @@
8802
8802
  {
8803
8803
  "path": "modules/provisioning/pollers/liveness-sweep.js",
8804
8804
  "mode": "0000644",
8805
- "sha256": "2403b4c3da7a401e65f9d97815d6582e17c28dcb5fdc78fc3956906163acb712"
8805
+ "sha256": "18578b6eafb69daf2991323ecc2aebf668e1209fc500ff30c165aca2b1bfcfd4"
8806
8806
  },
8807
8807
  {
8808
8808
  "path": "modules/provisioning/pollers/runner-drift-notify.js",
@@ -8942,7 +8942,7 @@
8942
8942
  {
8943
8943
  "path": "modules/provisioning/tests/liveness.mjs",
8944
8944
  "mode": "0000644",
8945
- "sha256": "a0889e57bf8f620f678dbd5d39799ae261e6a813586af9206dd87e453b34edba"
8945
+ "sha256": "f4adaf4dacf05b31f7848c62fafafc3f0498e276b2f4fe4d04f18ee5b27fd969"
8946
8946
  },
8947
8947
  {
8948
8948
  "path": "modules/provisioning/tests/provisioning.mjs",
@@ -10502,12 +10502,12 @@
10502
10502
  {
10503
10503
  "path": "package-lock.json",
10504
10504
  "mode": "0000644",
10505
- "sha256": "4456c94ea95ed42f413b1be2d6a1ba71efd942fe4ea5c8c3d2b3fbef5f98abaa"
10505
+ "sha256": "6664906b88a5d77b193eb6c60bcad81603678fd33b14dc6c60d94e56cd7221d0"
10506
10506
  },
10507
10507
  {
10508
10508
  "path": "package.json",
10509
10509
  "mode": "0000644",
10510
- "sha256": "7287b5976d50f478332c5dabefe23d78403c4c608bafeb8bff5fb15a7850c21d"
10510
+ "sha256": "971b67f78304f94f08f4ca1f66bab6b692362b81b949aa8ed886e51696000d83"
10511
10511
  },
10512
10512
  {
10513
10513
  "path": "public-docs/index.html",
@@ -10527,7 +10527,7 @@
10527
10527
  {
10528
10528
  "path": "release-notes.json",
10529
10529
  "mode": "0000644",
10530
- "sha256": "ff392941dfce2d1fe56a33d2169fcb26309c568dd17acb6f8ab63cc685c8ee9c"
10530
+ "sha256": "ba6b8f8a08e0aaf99bb72acb3f28f291bc92232c5f234e4c7ab86ca7c23bcb95"
10531
10531
  },
10532
10532
  {
10533
10533
  "path": "scripts/cron/style_guide_audit.sh",
@@ -12882,7 +12882,7 @@
12882
12882
  {
12883
12883
  "path": "src/module-api.js",
12884
12884
  "mode": "0000644",
12885
- "sha256": "9ad423365192371aa8a7035110108ad484fad685dc33386e647cb396b916dcbb"
12885
+ "sha256": "551446e38657afe5ad3430c5b33ee82f052bee8d6da7f4ad24f4ffeef24d57dd"
12886
12886
  },
12887
12887
  {
12888
12888
  "path": "src/module-loader/catalog.js",
@@ -14617,7 +14617,7 @@
14617
14617
  {
14618
14618
  "path": "tests/github_sign_in_seen.mjs",
14619
14619
  "mode": "0000644",
14620
- "sha256": "7a337ab8268947ad705872d341e1d1d95df70fed342e4ecb1df82ff3ef0044c2"
14620
+ "sha256": "6c17ca54ca78aadfd0032c91f28d642740f2489a8baa94b5958c64be5276a492"
14621
14621
  },
14622
14622
  {
14623
14623
  "path": "tests/github_token_session_life.mjs",
@@ -3099,5 +3099,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
3099
3099
  landed since 1.21.129 with no explicit bump. run 38775541138. (task 1002620)
3100
3100
  1.21.131 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3101
3101
  landed since 1.21.130 with no explicit bump. run 38779214079. (task 1002620)
3102
+ 1.21.132 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3103
+ landed since 1.21.131 with no explicit bump. run 38783817499. (task 1002620)
3102
3104
  ---------------------------------------------------------------------------
3103
3105
  ```
@@ -47,8 +47,8 @@ const SETUP_EVENTS = Object.freeze({
47
47
  // the founder pressed "Pick up where it stopped" or "Check again" (task 1004652). Like an
48
48
  // install report it re-opens the runner's checking window; it claims nothing itself.
49
49
  checkRequested: 'github-app-check-requested',
50
- // the liveness sweep saw the project's own /healthz answer auth_configured: true (task
51
- // 1004410). Recorded once per attempt (recordSignInSeen); no detail.
50
+ // the liveness sweep saw the project's own /api/bongos/healthz answer auth_configured: true
51
+ // (task 1004410). Recorded once per attempt (recordSignInSeen); no detail.
52
52
  signInSeen: 'github-sign-in-seen',
53
53
  });
54
54
 
@@ -164,13 +164,20 @@ function probeReason(err) {
164
164
  return err.message ? String(err.message).slice(0, 40) : 'error';
165
165
  }
166
166
 
167
- // The /healthz answer is tiny; read no more than this of a body a builder's host chose.
167
+ // The health answer is tiny; read no more than this of a body a builder's host chose.
168
168
  const BODY_CAP_BYTES = 4096;
169
169
 
170
+ // auth_configured lives on the JSON health answer; the root /healthz says a plain "ok" and
171
+ // stays the up/down read, so a core without this route never reads as down (task 1004743).
172
+ const AUTH_HEALTHZ_PATH = '/api/bongos/healthz';
173
+
174
+ const is2xx = (res) => res.status >= 200 && res.status < 300;
175
+
170
176
  // Did the answer say auth_configured: true (task 1004410)? Reads at most BODY_CAP_BYTES
171
- // and answers false for anything else — a big, slow or non-JSON body is never "configured".
177
+ // and answers false for anything else — a non-2xx, big, slow or non-JSON body is never "configured".
172
178
  async function readAuthConfigured(res) {
173
179
  try {
180
+ if (!is2xx(res)) return false;
174
181
  if (!res.body || typeof res.body.getReader !== 'function') {
175
182
  return typeof res.json === 'function' ? (await res.json()).auth_configured === true : false;
176
183
  }
@@ -192,22 +199,30 @@ async function readAuthConfigured(res) {
192
199
 
193
200
  // Knock once. Any 2xx = up; everything else (non-2xx, refused, DNS, TLS, timeout)
194
201
  // = down with a reason. Never throws — a probe result is the whole point. An up answer
195
- // also carries whether the project says its sign-in is configured (task 1004410).
202
+ // also carries whether the project says its sign-in is configured (task 1004410), asked
203
+ // on the same origin through the same pin and time budget; that read failing is only "no".
196
204
  async function probeHealthz(url, { timeoutMs = PROBE_TIMEOUT_MS, fetchImpl, pin } = {}) {
197
205
  const doFetch = fetchImpl || globalThis.fetch;
198
206
  const dispatcher = pin && !fetchImpl ? pinnedDispatcher(pin, timeoutMs) : null;
207
+ const signal = AbortSignal.timeout(timeoutMs);
208
+ const get = async (target) => doFetch(target, {
209
+ method: 'GET',
210
+ // manual: a redirect must never re-aim the probe (SSRF pivot) — and a
211
+ // healthz that redirects isn't answering anyway.
212
+ redirect: 'manual',
213
+ signal,
214
+ headers: { accept: 'application/json, text/plain;q=0.9, */*;q=0.8' },
215
+ ...(dispatcher ? { dispatcher } : {}),
216
+ });
199
217
  try {
200
- const res = await doFetch(url, {
201
- method: 'GET',
202
- // manual: a redirect must never re-aim the probe (SSRF pivot) — and a
203
- // healthz that redirects isn't answering anyway.
204
- redirect: 'manual',
205
- signal: AbortSignal.timeout(timeoutMs),
206
- headers: { accept: 'application/json, text/plain;q=0.9, */*;q=0.8' },
207
- ...(dispatcher ? { dispatcher } : {}),
208
- });
209
- if (res.status >= 200 && res.status < 300) return { up: true, authConfigured: await readAuthConfigured(res) };
210
- return { up: false, reason: `http ${res.status}` };
218
+ const res = await get(url);
219
+ if (!is2xx(res)) return { up: false, reason: `http ${res.status}` };
220
+ if (res.body && typeof res.body.cancel === 'function') res.body.cancel().catch(() => {});
221
+ // An https answer with no pin reached an address the gate never vetted; ask it nothing more.
222
+ const askAuth = !!pin || !url.startsWith('https://');
223
+ const authConfigured = askAuth
224
+ && await get(new URL(AUTH_HEALTHZ_PATH, url).href).then(readAuthConfigured, () => false);
225
+ return { up: true, authConfigured };
211
226
  } catch (e) {
212
227
  return { up: false, reason: probeReason(e) };
213
228
  } finally {
@@ -115,7 +115,7 @@ console.log('\nsweepOnce (real loopback probes, recording fake db):');
115
115
 
116
116
  await ta('an answering instance is UP and a refusing one is DOWN — both columns written, status never touched', async () => {
117
117
  const server = http.createServer((req, res) => {
118
- if (req.url === '/healthz') { res.writeHead(200, { 'content-type': 'application/json' }); res.end('{"ok":true}'); return; }
118
+ if (req.url === '/healthz') { res.writeHead(200, { 'content-type': 'text/plain; charset=utf-8' }); res.end('ok'); return; }
119
119
  res.writeHead(404); res.end();
120
120
  });
121
121
  await listen(server);
@@ -322,15 +322,17 @@ await ta('a rebinding nameserver cannot re-aim the probe — the pinned address
322
322
 
323
323
  await ta('the pin is load-bearing on the REAL client: same URL, the pinned address decides where it lands', async () => {
324
324
  const server = http.createServer((req, res) => {
325
- if (req.url === '/healthz') { res.writeHead(200); res.end('{"ok":true}'); return; }
325
+ if (req.url === '/healthz') { res.writeHead(200, { 'content-type': 'text/plain; charset=utf-8' }); res.end('ok'); return; }
326
+ if (req.url === '/api/bongos/healthz') { res.writeHead(200, { 'content-type': 'application/json' }); res.end('{"ok":true,"auth_configured":true}'); return; }
326
327
  res.writeHead(404); res.end();
327
328
  });
328
329
  await listen(server);
329
330
  const url = `http://pinned.invalid:${server.address().port}/healthz`;
330
331
  try {
331
- // 'pinned.invalid' resolves nowhere; only the pin can make this connect.
332
+ // 'pinned.invalid' resolves nowhere; only the pin can make this connect — for the sign-in
333
+ // read (task 1004743) as much as for the up/down one.
332
334
  const hit = await liveness.probeHealthz(url, { timeoutMs: 3000, pin: { address: '127.0.0.1', family: 4 } });
333
- assert.deepEqual(hit, { up: true, authConfigured: false }, 'the pinned address is what gets connected');
335
+ assert.deepEqual(hit, { up: true, authConfigured: true }, 'the pinned address is what both reads connect to');
334
336
  const miss = await liveness.probeHealthz(url, { timeoutMs: 3000, pin: { address: '127.0.0.2', family: 4 } });
335
337
  assert.equal(miss.up, false, 'a different pin cannot reach the same server — the pin, not DNS, decides');
336
338
  } finally {
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.131",
3
+ "version": "1.21.132",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.21.131",
9
+ "version": "1.21.132",
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.21.131",
3
+ "version": "1.21.132",
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",
@@ -9788,5 +9788,11 @@
9788
9788
  "id": "1004577",
9789
9789
  "text": "Two projects whose GitHub sign-in actually worked no longer show it as failed; their setup checklists now point at the real next step."
9790
9790
  }
9791
+ ],
9792
+ "1.21.132": [
9793
+ {
9794
+ "id": "1004743",
9795
+ "text": "A project whose GitHub sign-in already works now gets that step ticked on its setup checklist automatically, within about five minutes."
9796
+ }
9791
9797
  ]
9792
9798
  }
package/src/module-api.js CHANGED
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
75
75
  // MAJOR (see allowBoxScope below): passes the request through untouched.
76
76
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
77
77
 
78
- const CORE_VERSION = '1.21.131'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
78
+ const CORE_VERSION = '1.21.132'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
79
79
 
80
80
  // A namespaced logger so a module's log lines are attributable + consistent.
81
81
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -5,7 +5,7 @@
5
5
  // Sign-in used to tick only from the one-click GitHub attempt reaching 'done', so a project
6
6
  // whose sign-in was wired some other way — a hand-made OAuth app, or a GitHub tab that never
7
7
  // came back — sat at "Waiting on GitHub" for good. Now the liveness sweep reads the project's
8
- // /healthz answer, records 'github-sign-in-seen' once per attempt, and githubChecklist reads it.
8
+ // JSON health answer, records 'github-sign-in-seen' once per attempt, and githubChecklist reads it.
9
9
  // The page half (what the founder sees) is tests/setup_status_facts.mjs §6.
10
10
  //
11
11
  // Pinned here, DB-free over recording fakes and real loopback servers:
@@ -14,7 +14,10 @@
14
14
  // * pickUpFor offers nothing to pick up once sign-in works elsewhere;
15
15
  // * githubSetupRead reads the fact even when no attempt exists;
16
16
  // * recordSignInSeen writes at most one row per attempt;
17
- // * probeHealthz reports auth_configured only from a small JSON answer saying true;
17
+ // * probeHealthz reports auth_configured only from a small JSON answer saying true, read
18
+ // from <API prefix>/healthz — the root /healthz says a plain "ok" (task 1004743), and every
19
+ // server here answers both paths the way a real project does;
20
+ // * a sign-in read that fails, hangs or redirects never turns an up project down;
18
21
  // * sweepOnce records the fact only for an up project that says so.
19
22
  //
20
23
  // Run: node --test tests/github_sign_in_seen.mjs
@@ -23,6 +26,7 @@ import { test } from 'node:test';
23
26
  import assert from 'node:assert/strict';
24
27
  import http from 'node:http';
25
28
  import path from 'node:path';
29
+ import { readFileSync } from 'node:fs';
26
30
  import { createRequire } from 'node:module';
27
31
  import { fileURLToPath } from 'node:url';
28
32
 
@@ -30,6 +34,8 @@ const require = createRequire(import.meta.url);
30
34
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
31
35
  const gs = require(path.join(ROOT, 'modules', 'provisioning', 'github-setup.js'));
32
36
  const liveness = require(path.join(ROOT, 'modules', 'provisioning', 'pollers', 'liveness-sweep.js'));
37
+ const { API_PREFIX } = require(path.join(ROOT, 'src', 'bongos', 'api-prefix.js'));
38
+ const AUTH_PATH = `${API_PREFIX}/healthz`;
33
39
 
34
40
  const SEEN = { signInSeenAt: '2026-10-14T10:00:00.000Z' };
35
41
 
@@ -100,13 +106,38 @@ test('recordSignInSeen writes at most one row per attempt, from the liveness swe
100
106
  });
101
107
 
102
108
  // ── the probe and the sweep ────────────────────────────────────────────────────────────
103
- function serve(body, type = 'application/json') {
104
- const server = http.createServer((req, res) => { res.writeHead(200, { 'content-type': type }); res.end(body); });
105
- return new Promise((r) => server.listen(0, '127.0.0.1', () => r(server)));
109
+ // A project as it really answers: the ROOT /healthz is a plain-text "ok", and auth_configured
110
+ // lives only on the JSON answer under the API prefix. `api` handles that one path; without it
111
+ // the path 404s, like a core with no such route. A root mocked as JSON is what hid task 1004743.
112
+ function project(api) {
113
+ const server = http.createServer((req, res) => {
114
+ if (req.url === '/healthz') { res.writeHead(200, { 'content-type': 'text/plain; charset=utf-8' }); res.end('ok'); return; }
115
+ if (req.url === AUTH_PATH && api) { api(req, res); return; }
116
+ res.writeHead(404); res.end();
117
+ });
118
+ return listen(server);
106
119
  }
120
+ function json(body, status = 200) {
121
+ return (_req, res) => { res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' }); res.end(body); };
122
+ }
123
+ function listen(server) { return new Promise((r) => server.listen(0, '127.0.0.1', () => r(server))); }
107
124
  function close(server) { if (server.closeAllConnections) server.closeAllConnections(); server.close(); }
125
+ const rootOf = (s) => `http://127.0.0.1:${s.address().port}/healthz`;
126
+
127
+ test('the fixture is the real shape: the root /healthz says a plain-text "ok"', () => {
128
+ const src = readFileSync(path.join(ROOT, 'src', 'bongos', 'serve-internal.js'), 'utf8').replace(/\r\n/g, '\n');
129
+ assert.match(src, /app\.get\('\/healthz', \(_req, res\) => \{\n\s*res\.type\('text\/plain'\)\.send\('ok'\);/,
130
+ 'if the root ever answers JSON, project() above must change with it');
131
+ });
108
132
 
109
- test('probeHealthz says auth_configured only from a small JSON answer saying true', async () => {
133
+ test('a project whose root is the only answer is up, never configured, and never crashes the probe', async () => {
134
+ const s = await project(null);
135
+ try {
136
+ assert.deepEqual(await liveness.probeHealthz(rootOf(s), { timeoutMs: 3000 }), { up: true, authConfigured: false });
137
+ } finally { close(s); }
138
+ });
139
+
140
+ test('probeHealthz says auth_configured only from a small JSON answer under the API prefix saying true', async () => {
110
141
  const cases = [
111
142
  ['{"ok":true,"auth_configured":true}', true],
112
143
  ['{"ok":true,"auth_configured":false}', false],
@@ -116,23 +147,80 @@ test('probeHealthz says auth_configured only from a small JSON answer saying tru
116
147
  [JSON.stringify({ ok: true, auth_configured: true, pad: 'x'.repeat(8000) }), false],
117
148
  ];
118
149
  for (const [body, want] of cases) {
119
- const s = await serve(body);
150
+ const s = await project(json(body));
120
151
  try {
121
- const r = await liveness.probeHealthz(`http://127.0.0.1:${s.address().port}/healthz`, { timeoutMs: 3000 });
122
- assert.equal(r.up, true, 'any 2xx is still up');
152
+ const r = await liveness.probeHealthz(rootOf(s), { timeoutMs: 3000 });
153
+ assert.equal(r.up, true, 'the root answered, so it is up');
123
154
  assert.equal(r.authConfigured, want, body.slice(0, 50));
124
155
  } finally { close(s); }
125
156
  }
126
157
  });
127
158
 
159
+ test('against the REAL healthz router, the probe reads what the project\'s auth says', async () => {
160
+ const express = require('express');
161
+ const auth = require(path.join(ROOT, 'src', 'bongos', 'auth.js'));
162
+ const buildHealthzRouter = require(path.join(ROOT, 'src', 'bongos', 'routes', 'healthz.js'));
163
+ const realAuthConfigured = auth.authConfigured;
164
+ let configured = true;
165
+ let s = null;
166
+ try {
167
+ auth.authConfigured = () => configured;
168
+ const app = express();
169
+ app.get('/healthz', (_req, res) => { res.type('text/plain').send('ok'); });
170
+ app.use(API_PREFIX, buildHealthzRouter());
171
+ s = await listen(http.createServer(app));
172
+ assert.deepEqual(await liveness.probeHealthz(rootOf(s), { timeoutMs: 3000 }), { up: true, authConfigured: true });
173
+ configured = false;
174
+ assert.deepEqual(await liveness.probeHealthz(rootOf(s), { timeoutMs: 3000 }), { up: true, authConfigured: false });
175
+ } finally { auth.authConfigured = realAuthConfigured; if (s) close(s); }
176
+ });
177
+
178
+ test('a sign-in read that fails, hangs or redirects never turns an up project down, and is never followed', async () => {
179
+ const elsewhere = await project(json('{"ok":true,"auth_configured":true}'));
180
+ const answers = {
181
+ 'a 500': json('{"ok":true,"auth_configured":true}', 500),
182
+ 'a hang': () => {},
183
+ 'a redirect to a yes': (_req, res) => { res.writeHead(302, { location: `http://127.0.0.1:${elsewhere.address().port}${AUTH_PATH}` }); res.end(); },
184
+ };
185
+ try {
186
+ for (const [name, api] of Object.entries(answers)) {
187
+ const s = await project(api);
188
+ try {
189
+ assert.deepEqual(await liveness.probeHealthz(rootOf(s), { timeoutMs: 400 }), { up: true, authConfigured: false }, name);
190
+ } finally { close(s); }
191
+ }
192
+ } finally { close(elsewhere); }
193
+ });
194
+
195
+ test('the sign-in read rides only a vetted address, and no shape of failure turns it into a down', async () => {
196
+ const asked = [];
197
+ const answering = async (url) => {
198
+ asked.push(String(url));
199
+ return String(url).endsWith(AUTH_PATH) ? new Response('{"ok":true,"auth_configured":true}', { status: 200 }) : new Response('ok', { status: 200 });
200
+ };
201
+ const url = 'https://kestrel.example.com/healthz';
202
+ assert.deepEqual(await liveness.probeHealthz(url, { fetchImpl: answering, pin: { address: '93.184.216.34', family: 4 } }), { up: true, authConfigured: true });
203
+ assert.deepEqual(asked, [url, `https://kestrel.example.com${AUTH_PATH}`], 'the same origin, nowhere else');
204
+ asked.length = 0;
205
+ assert.deepEqual(await liveness.probeHealthz(url, { fetchImpl: answering }), { up: true, authConfigured: false });
206
+ assert.deepEqual(asked, [url], 'an https answer the gate never pinned is asked nothing more');
207
+
208
+ let calls = 0;
209
+ const throwsSecond = (u) => { calls += 1; if (calls > 1) throw new TypeError(`no: ${u}`); return Promise.resolve(new Response('ok', { status: 200 })); };
210
+ assert.deepEqual(await liveness.probeHealthz('http://127.0.0.1:1/healthz', { fetchImpl: throwsSecond }), { up: true, authConfigured: false }, 'a synchronous throw is only "no"');
211
+ const noStatus = await liveness.probeHealthz('http://127.0.0.1:1/healthz', { fetchImpl: async () => ({}) });
212
+ assert.equal(noStatus.up, false, 'an answer with no status is not a 2xx');
213
+ });
214
+
128
215
  test('the sweep records the fact for an up project that says so, and for no other', async () => {
129
- const yes = await serve('{"ok":true,"auth_configured":true}');
130
- const no = await serve('{"ok":true,"auth_configured":false}');
216
+ const kestrel = await project(json('{"ok":true,"auth_configured":true}'));
217
+ const tern = await project(json('{"ok":true,"auth_configured":false}'));
218
+ const wren = await project(null);
131
219
  const inserts = [];
132
220
  const db = {
133
221
  query: async (sql, params) => {
134
222
  if (/FROM provisioning_instances/.test(sql) && /WHERE status = 'active'/.test(sql)) {
135
- return { rows: [{ id: 1, slug: 'kestrel', domain: null, port: yes.address().port }, { id: 2, slug: 'tern', domain: null, port: no.address().port }] };
223
+ return { rows: [kestrel, tern, wren].map((s, i) => ({ id: i + 1, slug: ['kestrel', 'tern', 'wren'][i], domain: null, port: s.address().port })) };
136
224
  }
137
225
  if (/^UPDATE provisioning_instances/.test(sql)) return { rows: [{ id: params[0], prev_up: true }] };
138
226
  if (/INSERT INTO provisioning_events/.test(sql)) { inserts.push(params); return { rows: [] }; }
@@ -142,7 +230,7 @@ test('the sweep records the fact for an up project that says so, and for no othe
142
230
  try {
143
231
  liveness.resetNoticeState();
144
232
  const out = await liveness.sweepOnce({ pool: db, log: () => {}, timeoutMs: 3000, notify: () => {}, collectRunnerDrift: async () => [] });
145
- assert.equal(out.up, 2);
233
+ assert.equal(out.up, 3, 'what the sign-in read finds never changes up or down');
146
234
  assert.deepEqual(inserts, [[1, 'github-sign-in-seen']]);
147
- } finally { close(yes); close(no); }
235
+ } finally { close(kestrel); close(tern); close(wren); }
148
236
  });