@bongos/core 1.19.836 → 1.19.837

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.bongos-core.json CHANGED
@@ -2,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.836",
6
- "core_contract": "1.19.836",
7
- "source_commit": "faff4dc07439d637df2442a60de7b6ffe4942bef",
5
+ "core_version": "1.19.837",
6
+ "core_contract": "1.19.837",
7
+ "source_commit": "d3feb6abfa5bf8256137d7be87929d517c266c48",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-17T16:35:52.288Z",
9
+ "built_at": "2026-09-17T16:49:51.613Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 493,
13
13
  "agent_docs_stubbed": 26,
14
- "functional_verbatim": 2251,
14
+ "functional_verbatim": 2253,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2770,
20
- "tree_sha256": "b5a83bc0c677727ffcea2ac2708a0cecfbc67bf4ad36bb5fd810f89370f8a5a7",
19
+ "file_count": 2772,
20
+ "tree_sha256": "ce96f2d9d0c9b141f032d74100c6f505b5c6fdce72de9da3a6a8620538af685b",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2562,7 +2562,7 @@
2562
2562
  {
2563
2563
  "path": "docs/module-api-changelog.md",
2564
2564
  "mode": "0000644",
2565
- "sha256": "5945b79e8b7f10f3a6fdd8d2e4df80e0219ee19101801cc3d8dd39a412f3c957"
2565
+ "sha256": "7b2488f4099bcc1060d6257077010857c05b886459ad106f722ed19b0b5178f0"
2566
2566
  },
2567
2567
  {
2568
2568
  "path": "docs/modules-contract.md",
@@ -7852,12 +7852,12 @@
7852
7852
  {
7853
7853
  "path": "package-lock.json",
7854
7854
  "mode": "0000644",
7855
- "sha256": "c03405cc4429940c79f245171cd6b474c802187584b108f842bfffcf1b904bdf"
7855
+ "sha256": "376f02b1d5253b7b790331c0f6639d16da022314d4a83c58ca10a9648337f01e"
7856
7856
  },
7857
7857
  {
7858
7858
  "path": "package.json",
7859
7859
  "mode": "0000644",
7860
- "sha256": "f6889cab0ca16f36a0902f132a23f0251d8e30ad20f4b11b861ab60f66d6fef6"
7860
+ "sha256": "f8da5247d7da8fdcdb322b12a5ebb362315389a7049ea4d76160115066a6cf3f"
7861
7861
  },
7862
7862
  {
7863
7863
  "path": "public-docs/index.html",
@@ -8097,7 +8097,7 @@
8097
8097
  {
8098
8098
  "path": "scripts/gds/box.js",
8099
8099
  "mode": "0000644",
8100
- "sha256": "bf2f9c0ccbdaaa615bcd4020402dac32466a5a3ace6a1fb5cb543baa905a2ecd"
8100
+ "sha256": "a2d44a3c30aa83d675edc985bb562ed0599ed1a59bdca3efb515c8be2abcea35"
8101
8101
  },
8102
8102
  {
8103
8103
  "path": "scripts/gds/bug-attachment-audit.js",
@@ -8317,7 +8317,7 @@
8317
8317
  {
8318
8318
  "path": "scripts/gds/doctor.js",
8319
8319
  "mode": "0000644",
8320
- "sha256": "2fcc3f97558ed6a653638df96d585dc7a1ef7dd884657a63d3a9545dbdb03e92"
8320
+ "sha256": "eaae00a0124635c26f45b4f6620ed95e913a5c2140178fb85bd3f5020dab5d44"
8321
8321
  },
8322
8322
  {
8323
8323
  "path": "scripts/gds/enroll-control-plane.js",
@@ -8792,7 +8792,7 @@
8792
8792
  {
8793
8793
  "path": "scripts/gds/provision.js",
8794
8794
  "mode": "0000644",
8795
- "sha256": "4f60207affe476f637b2b2c13c38ab785940deef9bf8b5a8fa43b5d6448a408a"
8795
+ "sha256": "94a77427cb16c8a959eb279866f3777830d7a33617f02aed5eedb383ca26e84a"
8796
8796
  },
8797
8797
  {
8798
8798
  "path": "scripts/gds/publish-credential-check.js",
@@ -8909,6 +8909,11 @@
8909
8909
  "mode": "0000644",
8910
8910
  "sha256": "c5fb602f48d48ca31f5f5290d3624050267ec9bdf4b0c2988e3ff7d59e523bff"
8911
8911
  },
8912
+ {
8913
+ "path": "scripts/gds/runner-drift.js",
8914
+ "mode": "0000644",
8915
+ "sha256": "2a0919b7769007eba2b58f2c0a3f77d42a644c8c2912059ae2af689e06302d6e"
8916
+ },
8912
8917
  {
8913
8918
  "path": "scripts/gds/sandbox-stage.js",
8914
8919
  "mode": "0000644",
@@ -9782,7 +9787,7 @@
9782
9787
  {
9783
9788
  "path": "src/module-api.js",
9784
9789
  "mode": "0000644",
9785
- "sha256": "7faebdb6698a23244085b855c10b7f25d3363a5fd92f8f1190305f4a3b627da9"
9790
+ "sha256": "5434f6390d888bb9c3181c449483867da3fd6637a8748fe98f0609cc57f75022"
9786
9791
  },
9787
9792
  {
9788
9793
  "path": "src/module-loader/catalog.js",
@@ -12602,7 +12607,7 @@
12602
12607
  {
12603
12608
  "path": "tests/provision.mjs",
12604
12609
  "mode": "0000644",
12605
- "sha256": "56be00457be12f34e12658e032285865a6e3a2997df629e4d8dec3ec78c52b31"
12610
+ "sha256": "09a759a1b1e0cac3ddafdd64adca4daed0f6a8ab4c48a07f42954f38ed510433"
12606
12611
  },
12607
12612
  {
12608
12613
  "path": "tests/provision_disconnect.mjs",
@@ -12974,6 +12979,11 @@
12974
12979
  "mode": "0000644",
12975
12980
  "sha256": "b5698af4ca50c44b384a3adfd1a1b1132bab630241ebb70ac77b13a67ffc8eae"
12976
12981
  },
12982
+ {
12983
+ "path": "tests/runner_drift.mjs",
12984
+ "mode": "0000644",
12985
+ "sha256": "51e07247e84af72a316fac8b3c373198d80af6f97ec3fd08a9e99ab010a1b6ff"
12986
+ },
12977
12987
  {
12978
12988
  "path": "tests/sandbox_stage.mjs",
12979
12989
  "mode": "0000644",
@@ -2146,5 +2146,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
2146
2146
  landed since 1.19.834 with no explicit bump. run 35245617627. (task 1002620)
2147
2147
  1.19.836 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2148
2148
  landed since 1.19.835 with no explicit bump. run 35247495964. (task 1002620)
2149
+ 1.19.837 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2150
+ landed since 1.19.836 with no explicit bump. run 35248928789. (task 1002620)
2149
2151
  ---------------------------------------------------------------------------
2150
2152
  ```
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.836",
3
+ "version": "1.19.837",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.836",
9
+ "version": "1.19.837",
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.836",
3
+ "version": "1.19.837",
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",
@@ -54,6 +54,8 @@ const REPO_ROOT = path.resolve(__dirname, '..', '..');
54
54
  // INSTANCE root, riding ADR 0108's resolvers, instead of assuming it sits beside
55
55
  // box.js. ADR 0150 (BV1.R97/R98).
56
56
  const instanceConfig = require(path.join(REPO_ROOT, 'src/instance-config'));
57
+ const { installedCoreVersion, runnerBanner } = require('./runner-drift.js');
58
+ const { coreVersionSafe } = require('./provision-config.js');
57
59
  const BOX_INFRA_TEMPLATE = 'builder-box-cloud-init.yaml';
58
60
 
59
61
  // Ordered candidate infra/ dirs — first that actually holds the cloud-init
@@ -1120,6 +1122,8 @@ async function cmdReconcileDrift(pool, boxes, { apply }) {
1120
1122
  const MAX_INTENT_ATTEMPTS = numEnv('BOX_INTENT_MAX_ATTEMPTS', 3);
1121
1123
 
1122
1124
  async function cmdRunIntents(pool, boxes, boxOnboard, { apply }) {
1125
+ const instanceRoot = process.env.BONGOS_INSTANCE_ROOT || process.cwd();
1126
+ console.log(runnerBanner({ runnerVersion: coreVersionSafe(), runnerRoot: REPO_ROOT, instanceVersion: installedCoreVersion(instanceRoot), instanceRoot }));
1123
1127
  if (!apply) {
1124
1128
  const { rows } = await pool.query(
1125
1129
  `SELECT bi.*, b.github_login FROM box_intents bi
@@ -1325,4 +1329,4 @@ async function main() {
1325
1329
 
1326
1330
  if (require.main === module) main();
1327
1331
 
1328
- module.exports = { cmdProvision, deprovisionOne, parkOne, cmdSweepIdle, cmdSweepDormant, deleteWithRetry, cmdReconcileDrift, runDnsHook, runDnsHookBlackhole, markErrorUnlessDestroyed, resolveBoxInfraDir, boxInfraCandidates, readCloudInit, buildBoxEnv, BOX_INFRA_TEMPLATE };
1332
+ module.exports = { cmdProvision, cmdRunIntents, deprovisionOne, parkOne, cmdSweepIdle, cmdSweepDormant, deleteWithRetry, cmdReconcileDrift, runDnsHook, runDnsHookBlackhole, markErrorUnlessDestroyed, resolveBoxInfraDir, boxInfraCandidates, readCloudInit, buildBoxEnv, BOX_INFRA_TEMPLATE };
@@ -375,12 +375,38 @@ function checkDeployTooling() {
375
375
  }
376
376
  }
377
377
 
378
+ // Intent-runner drift (task 1002722): a runner on a stale core once turned a domain-attach
379
+ // into a teardown (task 1002716). Only a host with *intent-runner* units shows this section,
380
+ // so a builder laptop or dev box sees nothing.
381
+ function runnerDriftRows(results) {
382
+ return (results || []).map((r) => {
383
+ const msg = `${r.name}: ${r.reason}`;
384
+ if (r.status === 'ok') return { status: 'ok', msg };
385
+ if (r.status === 'drift') return { status: 'fail', msg: `${msg} — repoint the unit's WorkingDirectory + ExecStart at the installed core (a systemd drop-in, as task 1002716 did).` };
386
+ return { status: 'warn', msg };
387
+ });
388
+ }
389
+
390
+ function checkRunnerDrift() {
391
+ let rows = [];
392
+ try {
393
+ const { collectRunnerDrift } = require('./runner-drift.js');
394
+ rows = runnerDriftRows(collectRunnerDrift({ instanceRoot: process.env.BONGOS_INSTANCE_ROOT || process.cwd() }));
395
+ } catch (_) {
396
+ return;
397
+ }
398
+ if (!rows.length) return;
399
+ section('Intent runners');
400
+ const emit = { ok, warn, fail };
401
+ for (const row of rows) emit[row.status]('runners', row.msg);
402
+ }
403
+
378
404
  // --------------------------------------------------------------------------
379
405
  // main
380
406
  // --------------------------------------------------------------------------
381
407
  // Guarded so requiring this file for its pure helpers (tests/doctor_hooks.mjs)
382
408
  // does not run the checks, which shell out to git and hit the network.
383
- module.exports = { classifyHooksState };
409
+ module.exports = { classifyHooksState, runnerDriftRows };
384
410
 
385
411
  if (require.main === module) (async () => {
386
412
  console.log(`${C.bold}GDS Doctor${C.reset} — local-machine health check`);
@@ -390,6 +416,7 @@ if (require.main === module) (async () => {
390
416
  checkGitPlumbing();
391
417
  await checkRepoState();
392
418
  checkEnvironment();
419
+ checkRunnerDrift();
393
420
 
394
421
  const fails = findings.filter((f) => f.status === 'fail').length;
395
422
  const warns = findings.filter((f) => f.status === 'warn').length;
@@ -54,6 +54,7 @@ const { APP_USER, alreadyScaffolded, buildCorePinRefreshCommit, dbCreateCmd, dbN
54
54
  const { backupScriptPath, backupService, backupServicePath, backupTimer, backupTimerPath, backupUnitName, instanceManifestCmd, originEnvVarsFor, serviceUnit, serviceUnitPath, settingsConsumed, settingsEnvVarsFor, upsertEnvVars, webEnvBody, webEnvPath } = require('./provision-units.js');
55
55
  const { caddyBlock, caddySnippetPath, checkDnsTokenScope, classifyZoneScope, dnsReleaseEnv, dnsUpsertEnv, federateInstance, federationHubOrigin, healthzCmd, identityCmd, identityVerdict, shouldFederate, zoneForDomain } = require('./provision-net.js');
56
56
  const { capacityPreflight, statusCapacityLine } = require('./provision-capacity.js');
57
+ const { installedCoreVersion, runnerBanner } = require('./runner-drift.js');
57
58
  const { teardownInstance } = require('./provision-teardown.js');
58
59
  const { disconnectInstance } = require('./provision-disconnect.js');
59
60
  const { coreUpgradeInstance, snapshotAllCoreVersions } = require('./provision-core-upgrade.js'); // the owner's deploy door, ADR 0293
@@ -1221,6 +1222,8 @@ async function drainOAuthManifests(deps) {
1221
1222
  // ---------------------------------------------------------------------------
1222
1223
  async function cmdRunIntents(deps) {
1223
1224
  const { db, provisioning, log, apply } = deps;
1225
+ const instanceRoot = deps.instanceRoot || process.env.BONGOS_INSTANCE_ROOT || process.cwd();
1226
+ log(runnerBanner({ runnerVersion: coreVersionSafe(), runnerRoot: REPO_ROOT, instanceVersion: installedCoreVersion(instanceRoot), instanceRoot }));
1224
1227
  if (!apply) {
1225
1228
  const { rows } = await db.query(
1226
1229
  `SELECT pi.*, i.slug FROM provisioning_intents pi
@@ -1254,7 +1257,7 @@ async function cmdRunIntents(deps) {
1254
1257
  else if (intent.action === 'restart') result = await restartInstance(inst, deps);
1255
1258
  else if (intent.action === 'disconnect') result = await disconnectInstance(inst, deps);
1256
1259
  else if (intent.action === 'core-upgrade') result = await coreUpgradeInstance(inst, deps, intent); // also reads the INTENT: the version the owner previewed (ADR 0293 D4)
1257
- else result = { ok: false, error: `unknown intent action '${intent.action}' — refusing to guess` };
1260
+ else result = { ok: false, error: `unknown_action '${intent.action}' — runner core ${coreVersionSafe() || '?'} does not know it (runner too old?); refusing to guess` };
1258
1261
  if (result && result.ok === false) {
1259
1262
  if (intent.attempts >= MAX_INTENT_ATTEMPTS) {
1260
1263
  await provisioning.resolveIntent(db, intent.id, 'error', result.error || 'op failed');
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+ // scripts/gds/runner-drift.js — do the intent-runner units run the core this instance installed? (task 1002722)
3
+ //
4
+ // WHY. For weeks the prod intent runners ran core 1.19.2 from a retired clone while the
5
+ // instance served a current core, so an owner's domain-attach click executed as a TEARDOWN
6
+ // (task 1002716). The hotfix repointed the units with systemd drop-ins; nothing noticed the
7
+ // drift, and the only tell was a PWD in sudo logs. This reads each `*intent-runner*` unit the
8
+ // way systemd does (a drop-in overrides the base file), finds the core package the ExecStart
9
+ // script lives in, and compares its version to the instance's installed @bongos/core.
10
+ //
11
+ // A unit whose version cannot be read — a shell wrapper that picks the core at runtime, a
12
+ // missing package — is UNRESOLVED, never ok: a gate that guesses green is the incident again.
13
+ //
14
+ // node scripts/gds/runner-drift.js [--instance-root <dir>] [--json]
15
+ // exit 0 = every unit resolved and matched (or no systemd here), 1 = drift, 2 = bad usage.
16
+ // Unresolved units are listed but do not fail the run. `bongos doctor` runs the same check.
17
+ 'use strict';
18
+
19
+ const fs = require('node:fs');
20
+ const path = require('node:path');
21
+ const { spawnSync } = require('node:child_process');
22
+
23
+ const CORE_NAMES = ['@bongos/core', '@cloudbongos/core'];
24
+ const P = path.posix; // unit files describe the Linux control plane, whatever runs the tests
25
+
26
+ // The effective WorkingDirectory + ExecStart lines from `systemctl cat` output. Later files
27
+ // are drop-ins: a later WorkingDirectory wins, an empty `ExecStart=` clears the list, and a
28
+ // drop-in that only ADDS one leaves the base command running too (oneshot runs them all).
29
+ function effectiveUnit(catText) {
30
+ let workingDirectory = null;
31
+ let execs = [];
32
+ for (const raw of String(catText || '').split(/\r?\n/)) {
33
+ const line = raw.trim();
34
+ const m = line.match(/^(WorkingDirectory|ExecStart)=(.*)$/);
35
+ if (!m) continue;
36
+ const value = m[2].trim();
37
+ if (m[1] === 'WorkingDirectory') workingDirectory = value.replace(/^-/, '') || null;
38
+ else if (value === '') execs = [];
39
+ else execs.push(value);
40
+ }
41
+ return { workingDirectory, execStarts: execs };
42
+ }
43
+
44
+ // The absolute path of the .js script an ExecStart runs, or null when its first argument is
45
+ // not one (a shell wrapper decides at runtime which core it loads).
46
+ function runnerScriptPath({ workingDirectory, execStart }) {
47
+ const tokens = String(execStart || '').trim().split(/\s+/).filter(Boolean);
48
+ if (!tokens.length) return null;
49
+ let i = 0;
50
+ if (P.basename(tokens[i].replace(/^[@\-:+!]+/, '')) === 'env') {
51
+ i++;
52
+ while (i < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[i])) i++;
53
+ }
54
+ const script = tokens.slice(i + 1).find((tok) => !tok.startsWith('-'));
55
+ if (!script || !/\.[cm]?js$/.test(script)) return null;
56
+ if (script.startsWith('/')) return script;
57
+ return workingDirectory ? P.join(workingDirectory, script) : null;
58
+ }
59
+
60
+ function readJson(p, fsImpl) {
61
+ try { return JSON.parse(fsImpl.readFileSync(p, 'utf8')); } catch { return null; }
62
+ }
63
+
64
+ // The core version a script belongs to: the nearest enclosing @bongos/core package, or a
65
+ // source checkout's CORE_VERSION. Null when neither is found.
66
+ function coreVersionFor(scriptPath, { fsImpl = fs } = {}) {
67
+ let dir = P.dirname(String(scriptPath || ''));
68
+ for (;;) {
69
+ const pkg = readJson(P.join(dir, 'package.json'), fsImpl);
70
+ if (pkg && CORE_NAMES.includes(pkg.name) && pkg.version) return String(pkg.version);
71
+ try {
72
+ const m = String(fsImpl.readFileSync(P.join(dir, 'src', 'module-api.js'), 'utf8')).match(/CORE_VERSION\s*=\s*['"]([^'"]+)['"]/);
73
+ if (m) return m[1];
74
+ } catch { /* not a checkout root */ }
75
+ const up = P.dirname(dir);
76
+ if (up === dir) return null;
77
+ dir = up;
78
+ }
79
+ }
80
+
81
+ function installedCoreVersion(instanceRoot, { fsImpl = fs } = {}) {
82
+ if (!instanceRoot) return null;
83
+ for (const name of CORE_NAMES) {
84
+ const pkg = readJson(P.join(instanceRoot, 'node_modules', name, 'package.json'), fsImpl);
85
+ if (pkg && pkg.version) return String(pkg.version);
86
+ }
87
+ return null;
88
+ }
89
+
90
+ function assessExecStart({ workingDirectory, execStart, instanceVersion, instanceRoot, fsImpl }) {
91
+ const scriptPath = runnerScriptPath({ workingDirectory, execStart });
92
+ const base = { scriptPath, runnerVersion: null, instanceVersion };
93
+ if (!scriptPath) return { ...base, status: 'unresolved', reason: `ExecStart does not run a .js script directly (${execStart}) — cannot tell which core it loads` };
94
+ const runnerVersion = coreVersionFor(scriptPath, { fsImpl });
95
+ const found = { ...base, runnerVersion };
96
+ if (!runnerVersion) return { ...found, status: 'unresolved', reason: `no core version found above ${scriptPath}` };
97
+ if (!instanceVersion) return { ...found, status: 'unresolved', reason: `no installed @bongos/core under ${instanceRoot}` };
98
+ if (runnerVersion === instanceVersion) return { ...found, status: 'ok', reason: `runs core ${runnerVersion}` };
99
+ return { ...found, status: 'drift', reason: `runs core ${runnerVersion} (${scriptPath}) but the instance installed ${instanceVersion} at ${instanceRoot}` };
100
+ }
101
+
102
+ // One verdict per unit, from its WORST ExecStart: drift, then unresolved, then ok.
103
+ function assessRunnerUnit({ name, catText, instanceRoot, fsImpl = fs }) {
104
+ const { workingDirectory, execStarts } = effectiveUnit(catText);
105
+ const instanceVersion = installedCoreVersion(instanceRoot, { fsImpl });
106
+ if (!execStarts.length) return { name, status: 'unresolved', reason: 'no ExecStart in the unit', scriptPath: null, runnerVersion: null, instanceVersion };
107
+ const all = execStarts.map((execStart) => assessExecStart({ workingDirectory, execStart, instanceVersion, instanceRoot, fsImpl }));
108
+ const worst = ['drift', 'unresolved', 'ok'].map((s) => all.find((r) => r.status === s)).find(Boolean);
109
+ return { name, ...worst };
110
+ }
111
+
112
+ function parseUnitList(stdout) {
113
+ return String(stdout || '').split(/\r?\n/).map((l) => l.trim().split(/\s+/)[0]).filter((n) => n && n.endsWith('.service'));
114
+ }
115
+
116
+ // The first line of every run-intents, so a journal grep shows a stale runner at once.
117
+ function runnerBanner({ runnerVersion, runnerRoot, instanceVersion, instanceRoot }) {
118
+ const tail = instanceVersion ? `instance core ${instanceVersion} at ${instanceRoot}` : `no installed core at ${instanceRoot}`;
119
+ const line = `runner core ${runnerVersion || '?'} from ${runnerRoot} (${tail})`;
120
+ return runnerVersion && instanceVersion && runnerVersion !== instanceVersion ? `⚠ DRIFT: ${line}` : line;
121
+ }
122
+
123
+ // Every intent-runner unit on this host, assessed. Null when there is no systemd to ask.
124
+ function collectRunnerDrift({ instanceRoot }) {
125
+ const run = (args) => spawnSync('systemctl', args, { encoding: 'utf8', timeout: 15000 });
126
+ const list = run(['list-units', '--all', '--plain', '--no-legend', '--no-pager', '--type=service', '*intent-runner*']);
127
+ if (list.error || list.status !== 0) return null;
128
+ return parseUnitList(list.stdout).map((name) => {
129
+ const cat = run(['cat', '--no-pager', name]);
130
+ return assessRunnerUnit({ name, catText: cat.status === 0 ? cat.stdout : '', instanceRoot });
131
+ });
132
+ }
133
+
134
+ function main(argv) {
135
+ const at = argv.indexOf('--instance-root');
136
+ const instanceRoot = (at >= 0 ? argv[at + 1] : null) || process.env.BONGOS_INSTANCE_ROOT;
137
+ if (!instanceRoot) {
138
+ console.error('runner-drift: name the instance root — --instance-root <dir> or BONGOS_INSTANCE_ROOT');
139
+ return 2;
140
+ }
141
+ const results = collectRunnerDrift({ instanceRoot });
142
+ if (argv.includes('--json')) console.log(JSON.stringify({ instanceRoot, systemd: results !== null, units: results || [] }, null, 2));
143
+ else if (results === null) console.log('runner-drift: no systemd on this host — nothing to check');
144
+ else if (!results.length) console.log('runner-drift: no *intent-runner* units on this host');
145
+ else for (const r of results) console.log(`${r.status === 'ok' ? '✓' : r.status === 'drift' ? '✖' : '?'} ${r.name}: ${r.reason}`);
146
+ return results && results.some((r) => r.status === 'drift') ? 1 : 0;
147
+ }
148
+
149
+ module.exports = { effectiveUnit, runnerScriptPath, coreVersionFor, installedCoreVersion, assessRunnerUnit, parseUnitList, runnerBanner, collectRunnerDrift };
150
+
151
+ if (require.main === module) process.exitCode = main(process.argv.slice(2));
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.836'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.837'; // 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');
@@ -1219,6 +1219,21 @@ await ta('cmdRunIntents (dry-run) lists the pending queue, runs nothing', async
1219
1219
  assert.equal(r.ran, 0);
1220
1220
  });
1221
1221
 
1222
+ await ta('cmdRunIntents opens with the runner core version + path, so a journal grep shows a stale runner (task 1002722)', async () => {
1223
+ for (const apply of [false, true]) {
1224
+ const lines = [];
1225
+ const deps = {
1226
+ apply, log: (l) => lines.push(l), instanceRoot: '/no/such/instance',
1227
+ db: { query: async () => ({ rows: [] }) },
1228
+ provisioning: { claimNextIntent: async () => null, claimNextOAuthManifest: async () => null },
1229
+ exec: () => ({ ok: true }), writeFile: () => ({ ok: true }),
1230
+ };
1231
+ await P.cmdRunIntents(deps);
1232
+ const { REPO_ROOT } = require('../scripts/gds/provision-config.js');
1233
+ assert.equal(lines[0], `runner core ${P.coreVersionSafe()} from ${REPO_ROOT} (no installed core at /no/such/instance)`, `apply=${apply}`);
1234
+ }
1235
+ });
1236
+
1222
1237
  await ta('cmdRunIntents (--apply) claims → provisions → resolves the intent done', async () => {
1223
1238
  let claims = 0; const resolved = [];
1224
1239
  const deps = {
@@ -2240,6 +2255,9 @@ await ta('cmdRunIntents (--apply): an UNKNOWN intent action never falls through
2240
2255
  assert.equal(resolved.length, 1);
2241
2256
  assert.equal(resolved[0].state, 'error');
2242
2257
  assert.ok(!cmds.some((c) => /systemctl (stop|disable)/.test(c)), 'no teardown commands ran');
2258
+ // task 1002722 (the refusal itself landed in task 1002692): the error names the action AND
2259
+ // the runner's core, so a stale runner is diagnosable from the intent row alone.
2260
+ assert.equal(resolved[0].err, `unknown_action 'oauth-manifest' — runner core ${P.coreVersionSafe()} does not know it (runner too old?); refusing to guess`);
2243
2261
  });
2244
2262
 
2245
2263
  console.log('\nprovisioning truthfulness (task 1002720 — QA-walk F15/F16/F19):');
@@ -0,0 +1,173 @@
1
+ // tests/runner_drift.mjs — the intent-runner drift gate (task 1002722).
2
+ //
3
+ // The incident: for weeks the prod intent runners ran core 1.19.2 from a retired clone while
4
+ // the instance served a current core, so an owner's domain-attach click executed as a
5
+ // TEARDOWN (task 1002716). The hotfix repointed the units with systemd drop-ins. These cases
6
+ // pin the reading of a unit the way systemd reads it (a drop-in overrides the base file) and
7
+ // the version comparison, against a fake filesystem shaped like the live box.
8
+ //
9
+ // Run: node tests/runner_drift.mjs
10
+
11
+ import { strict as assert } from 'node:assert';
12
+ import { readFileSync } from 'node:fs';
13
+ import { effectiveUnit, runnerScriptPath, coreVersionFor, installedCoreVersion, assessRunnerUnit, parseUnitList, runnerBanner } from '../scripts/gds/runner-drift.js';
14
+
15
+ let passed = 0, failed = 0;
16
+ function t(name, fn) {
17
+ try { fn(); passed++; console.log(` PASS ${name}`); }
18
+ catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
19
+ }
20
+
21
+ // The live selfhost unit as `systemctl cat` prints it (2026-09-17): base file + drop-in.
22
+ const LIVE_CAT = `# /etc/systemd/system/selfhost-provision-intent-runner.service
23
+ [Service]
24
+ Type=oneshot
25
+ WorkingDirectory=/home/lars/cloudbongos
26
+ EnvironmentFile=/etc/cloudbongos/box.env
27
+ ExecStart=/usr/bin/env bash infra/core-node.sh scripts/gds/provision.js run-intents --apply
28
+
29
+ # /etc/systemd/system/selfhost-provision-intent-runner.service.d/override.conf
30
+ [Service]
31
+ WorkingDirectory=/home/lars/cloudbongos-instance
32
+ ExecStart=
33
+ ExecStart=/usr/bin/node node_modules/@bongos/core/scripts/gds/provision.js run-intents --apply
34
+ `;
35
+
36
+ // A fake fs: path → file body. Only readFileSync + existsSync are used.
37
+ function fakeFs(files) {
38
+ return {
39
+ existsSync: (p) => Object.prototype.hasOwnProperty.call(files, p),
40
+ readFileSync: (p) => { if (!(p in files)) { const e = new Error(`ENOENT ${p}`); e.code = 'ENOENT'; throw e; } return files[p]; },
41
+ };
42
+ }
43
+ const pkg = (name, version) => JSON.stringify({ name, version });
44
+ const INSTANCE = '/home/lars/cloudbongos-instance';
45
+ const boxFs = (installed, runnerPkg = installed) => fakeFs({
46
+ [`${INSTANCE}/node_modules/@bongos/core/package.json`]: pkg('@bongos/core', installed),
47
+ [`${INSTANCE}/package.json`]: pkg('cloudbongos-instance', '0.0.1'),
48
+ ...(runnerPkg !== installed ? { '/opt/other/node_modules/@bongos/core/package.json': pkg('@bongos/core', runnerPkg) } : {}),
49
+ '/home/lars/cloudbongos/src/module-api.js': "const CORE_VERSION = '1.19.2'; // old clone\n",
50
+ '/home/lars/cloudbongos/package.json': pkg('cloud-bongos', '0.0.2'),
51
+ });
52
+
53
+ t('effectiveUnit: a drop-in WorkingDirectory wins, and an empty ExecStart= resets the base one', () => {
54
+ assert.deepEqual(effectiveUnit(LIVE_CAT), {
55
+ workingDirectory: INSTANCE,
56
+ execStarts: ['/usr/bin/node node_modules/@bongos/core/scripts/gds/provision.js run-intents --apply'],
57
+ });
58
+ });
59
+
60
+ t('effectiveUnit: with no drop-in the base file stands (the pre-hotfix shape)', () => {
61
+ const base = LIVE_CAT.split('# /etc/systemd/system/selfhost-provision-intent-runner.service.d')[0];
62
+ const u = effectiveUnit(base);
63
+ assert.equal(u.workingDirectory, '/home/lars/cloudbongos');
64
+ assert.equal(u.execStarts.length, 1);
65
+ assert.match(u.execStarts[0], /infra\/core-node\.sh/);
66
+ });
67
+
68
+ t('assessRunnerUnit: a drop-in that ADDS an ExecStart without clearing leaves the stale base command running — never ok', () => {
69
+ const cat = LIVE_CAT.replace('ExecStart=\nExecStart=/usr/bin/node', 'ExecStart=/usr/bin/node')
70
+ .replace('ExecStart=/usr/bin/env bash infra/core-node.sh scripts/gds/provision.js run-intents --apply', 'ExecStart=/usr/bin/node /home/lars/cloudbongos/scripts/gds/provision.js run-intents --apply');
71
+ assert.equal(effectiveUnit(cat).execStarts.length, 2);
72
+ const r = assessRunnerUnit({ name: 'x', catText: cat, instanceRoot: INSTANCE, fsImpl: boxFs('1.19.831') });
73
+ assert.equal(r.status, 'drift', r.reason);
74
+ assert.match(r.reason, /1\.19\.2/);
75
+ });
76
+
77
+ t('runnerScriptPath: node + a relative script resolves against WorkingDirectory; a wrapper resolves to null', () => {
78
+ assert.equal(runnerScriptPath({ workingDirectory: INSTANCE, execStart: '/usr/bin/node node_modules/@bongos/core/scripts/gds/provision.js run-intents --apply' }),
79
+ `${INSTANCE}/node_modules/@bongos/core/scripts/gds/provision.js`);
80
+ assert.equal(runnerScriptPath({ workingDirectory: '/w', execStart: '/usr/bin/env node /abs/core/scripts/gds/box.js run-intents' }), '/abs/core/scripts/gds/box.js');
81
+ assert.equal(runnerScriptPath({ workingDirectory: '/w', execStart: '-/usr/bin/node scripts/gds/box.js' }), '/w/scripts/gds/box.js', 'systemd exec prefixes are stripped');
82
+ assert.equal(runnerScriptPath({ workingDirectory: '/w', execStart: '/usr/bin/env NODE_ENV=production node --max-old-space-size=512 scripts/gds/box.js' }), '/w/scripts/gds/box.js', 'env assignments and node flags are skipped');
83
+ assert.equal(runnerScriptPath({ workingDirectory: '/w', execStart: '/usr/bin/env bash infra/core-node.sh scripts/gds/provision.js' }), null,
84
+ 'a shell wrapper decides at runtime which core it loads — the gate must not guess');
85
+ });
86
+
87
+ t('coreVersionFor: an installed package reads its package.json; a source checkout reads CORE_VERSION', () => {
88
+ const fs = boxFs('1.19.831');
89
+ assert.equal(coreVersionFor(`${INSTANCE}/node_modules/@bongos/core/scripts/gds/provision.js`, { fsImpl: fs }), '1.19.831');
90
+ assert.equal(coreVersionFor('/home/lars/cloudbongos/scripts/gds/provision.js', { fsImpl: fs }), '1.19.2');
91
+ assert.equal(coreVersionFor('/nowhere/scripts/gds/provision.js', { fsImpl: fs }), null);
92
+ });
93
+
94
+ t('installedCoreVersion: reads <root>/node_modules/@bongos/core; null when absent', () => {
95
+ assert.equal(installedCoreVersion(INSTANCE, { fsImpl: boxFs('1.19.831') }), '1.19.831');
96
+ assert.equal(installedCoreVersion('/empty', { fsImpl: boxFs('1.19.831') }), null);
97
+ });
98
+
99
+ t('assessRunnerUnit: the live drop-in shape is ok', () => {
100
+ const r = assessRunnerUnit({ name: 'selfhost-provision-intent-runner.service', catText: LIVE_CAT, instanceRoot: INSTANCE, fsImpl: boxFs('1.19.831') });
101
+ assert.equal(r.status, 'ok', r.reason);
102
+ assert.equal(r.runnerVersion, '1.19.831');
103
+ });
104
+
105
+ t('assessRunnerUnit: a unit running a source clone on another core is DRIFT, naming both versions', () => {
106
+ const cat = LIVE_CAT.replace(/ExecStart=\/usr\/bin\/node node_modules[^\n]*/, 'ExecStart=/usr/bin/node /home/lars/cloudbongos/scripts/gds/provision.js run-intents --apply');
107
+ const r = assessRunnerUnit({ name: 'x.service', catText: cat, instanceRoot: INSTANCE, fsImpl: boxFs('1.19.831') });
108
+ assert.equal(r.status, 'drift');
109
+ assert.match(r.reason, /1\.19\.2/);
110
+ assert.match(r.reason, /1\.19\.831/);
111
+ });
112
+
113
+ t('assessRunnerUnit: a wrapper, or a version it cannot read, is UNRESOLVED — never reported as ok', () => {
114
+ const base = LIVE_CAT.split('# /etc/systemd/system/selfhost-provision-intent-runner.service.d')[0];
115
+ assert.equal(assessRunnerUnit({ name: 'x', catText: base, instanceRoot: INSTANCE, fsImpl: boxFs('1.19.831') }).status, 'unresolved');
116
+ assert.equal(assessRunnerUnit({ name: 'x', catText: LIVE_CAT, instanceRoot: '/empty', fsImpl: boxFs('1.19.831') }).status, 'unresolved');
117
+ assert.equal(assessRunnerUnit({ name: 'x', catText: '', instanceRoot: INSTANCE, fsImpl: boxFs('1.19.831') }).status, 'unresolved');
118
+ });
119
+
120
+ t('parseUnitList: plain list-units output → service names only', () => {
121
+ const out = 'cohost-provision-intent-runner.service loaded inactive dead Cloud Bongos — drain\nselfhost-provision-intent-runner.service loaded inactive dead x\n\n';
122
+ assert.deepEqual(parseUnitList(out), ['cohost-provision-intent-runner.service', 'selfhost-provision-intent-runner.service']);
123
+ assert.deepEqual(parseUnitList(''), []);
124
+ });
125
+
126
+ t('runnerBanner: names the runner core + path, and flags a differing instance core', () => {
127
+ assert.equal(runnerBanner({ runnerVersion: '1.19.831', runnerRoot: '/r', instanceVersion: '1.19.831', instanceRoot: '/i' }),
128
+ 'runner core 1.19.831 from /r (instance core 1.19.831 at /i)');
129
+ assert.match(runnerBanner({ runnerVersion: '1.19.2', runnerRoot: '/r', instanceVersion: '1.19.831', instanceRoot: '/i' }), /^⚠ DRIFT: runner core 1\.19\.2 from \/r \(instance core 1\.19\.831 at \/i\)/);
130
+ assert.equal(runnerBanner({ runnerVersion: null, runnerRoot: '/r', instanceVersion: null, instanceRoot: '/i' }), 'runner core ? from /r (no installed core at /i)');
131
+ });
132
+
133
+ // box.js run-intents is the other runner the incident class covers — it opens with the same line.
134
+ const { cmdRunIntents: boxRunIntents } = await import('../scripts/gds/box.js');
135
+ {
136
+ const lines = [];
137
+ const origLog = console.log;
138
+ const origRoot = process.env.BONGOS_INSTANCE_ROOT;
139
+ process.env.BONGOS_INSTANCE_ROOT = '/no/such/instance';
140
+ console.log = (l) => lines.push(String(l));
141
+ try { await boxRunIntents({ query: async () => ({ rows: [] }) }, {}, null, { apply: false }); }
142
+ finally {
143
+ console.log = origLog;
144
+ if (origRoot === undefined) delete process.env.BONGOS_INSTANCE_ROOT; else process.env.BONGOS_INSTANCE_ROOT = origRoot;
145
+ }
146
+ t('box.js run-intents opens with the runner core version line', () => {
147
+ assert.match(lines[0], /^runner core \d+\.\d+\.\d+ from .+ \(no installed core at \/no\/such\/instance\)$/);
148
+ });
149
+ }
150
+
151
+ const { runnerDriftRows } = await import('../scripts/gds/doctor.js');
152
+
153
+ t('doctor maps each verdict to a row: ok → ok, drift → FAIL with the fix, unresolved → warn', () => {
154
+ const rows = runnerDriftRows([
155
+ { name: 'a.service', status: 'ok', reason: 'runs core 1.19.831' },
156
+ { name: 'b.service', status: 'drift', reason: 'runs core 1.19.2 (/x) but the instance installed 1.19.831 at /i' },
157
+ { name: 'c.service', status: 'unresolved', reason: 'ExecStart does not run a .js script directly' },
158
+ ]);
159
+ assert.deepEqual(rows.map((r) => r.status), ['ok', 'fail', 'warn']);
160
+ assert.equal(rows[0].msg, 'a.service: runs core 1.19.831');
161
+ assert.match(rows[1].msg, /^b\.service: runs core 1\.19\.2 .* — repoint the unit's WorkingDirectory \+ ExecStart at the installed core/);
162
+ assert.equal(rows[2].msg, 'c.service: ExecStart does not run a .js script directly');
163
+ assert.deepEqual(runnerDriftRows(null), [], 'no systemd → no rows, so a laptop shows no section');
164
+ assert.deepEqual(runnerDriftRows([]), []);
165
+ });
166
+
167
+ t('doctor runs the runner-drift probe', () => {
168
+ const src = readFileSync(new URL('../scripts/gds/doctor.js', import.meta.url), 'utf8');
169
+ assert.match(src.slice(src.indexOf('require.main === module')), /checkRunnerDrift\(\)/);
170
+ });
171
+
172
+ console.log(`\nrunner_drift: ${passed} passed, ${failed} failed`);
173
+ process.exit(failed ? 1 : 0);