@volter/supercode-health 0.1.12 → 0.1.13

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/lib/drift.mjs CHANGED
@@ -5,7 +5,10 @@
5
5
  // A process runs a file of a supercode install (the native, `…/@volter/supercode-cli-<os>-<cpu>/bin/supercode`, or
6
6
  // node running a file of an `@volter/supercode*` package); when that file was written after the process started (its
7
7
  // ctime: an extractor may set a file's mtime, never its ctime), the release on disk is not the one it runs.
8
- import { realpathSync, statSync } from 'node:fs';
8
+ import { readFileSync, realpathSync, statSync } from 'node:fs';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { homedir } from 'node:os';
11
+ import { dirname, join } from 'node:path';
9
12
  import { run } from './run.mjs';
10
13
 
11
14
  /** A supercode install's file: the native, or a script of an @volter/supercode* package. */
@@ -70,3 +73,33 @@ export async function staleSupercode({ rows = null } = {}) {
70
73
  }
71
74
  return stale.sort((a, b) => b.installedAt.localeCompare(a.installedAt));
72
75
  }
76
+
77
+ /**
78
+ * Board homes served by an orchestrator other than the machine's (t_8e109edc): a home records the orchestrator and
79
+ * the supercode that serve it (`<home>/orchestrator.json`, hand-off.mjs obeys it on every command). On npm's prefix that
80
+ * path follows each upgrade in place; a home made by another install (a scratch prefix, a removed `~/.local`, a kit's
81
+ * per-version install) stays served by it until its path is gone, and then refuses. Each home this machine's daemon
82
+ * holds a board for (`<home>/board-homes.json`): `[{ home, version, entry, why }]` where its record names another
83
+ * orchestrator than the one installed beside this package, or one that is not on this machine.
84
+ */
85
+ export function pinnedHomes({ home = null } = {}) {
86
+ const supercodeHome = home ?? (process.env.SUPERCODE_HOME || (process.env.XDG_CONFIG_HOME ? join(process.env.XDG_CONFIG_HOME, 'supercode') : join(homedir(), '.config', 'supercode')));
87
+ let held = {};
88
+ try { held = JSON.parse(readFileSync(join(supercodeHome, 'board-homes.json'), 'utf8')) ?? {}; } catch { return []; }
89
+ let own = null;
90
+ // its package's own folder (its exports name no bin), from its main module up to its package.json
91
+ try {
92
+ let dir = dirname(fileURLToPath(import.meta.resolve('@volter/supercode-orchestrator'))); // an import-only package
93
+ while (dir !== dirname(dir) && JSON.parse((() => { try { return readFileSync(join(dir, 'package.json'), 'utf8'); } catch { return '{}'; } })()).name !== '@volter/supercode-orchestrator') dir = dirname(dir);
94
+ own = realpathSync(join(dir, 'bin', 'orchestrator.mjs'));
95
+ } catch { /* none beside this package: a checkout */ }
96
+ const pinned = [];
97
+ for (const root of new Set(Object.values(held).filter((value) => typeof value === 'string'))) {
98
+ let record = null;
99
+ try { record = JSON.parse(readFileSync(join(root, 'orchestrator.json'), 'utf8')); } catch { continue; } // no record: the machine's own serves it
100
+ let entry = null;
101
+ try { entry = realpathSync(record.entry); } catch { pinned.push({ home: root, version: record.version ?? null, entry: record.entry ?? null, why: 'its orchestrator is not on this machine' }); continue; }
102
+ if (own && entry !== own) pinned.push({ home: root, version: record.version ?? null, entry, why: `served by ${entry}, not the machine's ${own}` });
103
+ }
104
+ return pinned;
105
+ }
package/lib/sample.mjs CHANGED
@@ -7,7 +7,7 @@ import { daemonCall } from './mail.mjs';
7
7
  import { rendererOrigins, originOf, localPort } from './chrome.mjs';
8
8
  import { expiredFailures, failureSummary, keyFor, Ledger, runningRelease, sayOnChanges, slowFailures } from './failures.mjs';
9
9
  import { healthDir } from './paths.mjs';
10
- import { staleSupercode } from './drift.mjs';
10
+ import { pinnedHomes, staleSupercode } from './drift.mjs';
11
11
  import { join } from 'node:path';
12
12
 
13
13
  const MB = 1024 * 1024;
@@ -392,6 +392,12 @@ export class Sampler {
392
392
  slow.observations.push({ enumerated: 'release', family: 'release', by: 'suffix', entities: new Set(stale.map((r) => String(r.pid))) });
393
393
  slow.readings.drift = stale;
394
394
  for (const r of stale) slow.observations.push({ key: `release.drift:${r.pid}`, family: 'release', def: alarm('release.drift'), value: 1, met: { warn: true, crit: false, clears: false }, summary: `supercode process ${r.pid} runs a release replaced at ${r.installedAt} (started ${r.startedAt}): ${r.command.slice(0, 120)}`, detail: r });
395
+ // board homes served by another install's orchestrator: their own family, cleared when a home's record moves
396
+ const pinned = pinnedHomes();
397
+ slow.families.add('pinned');
398
+ slow.observations.push({ enumerated: 'pinned', family: 'pinned', by: 'suffix', entities: new Set(pinned.map((p) => p.home)) });
399
+ slow.readings.pinned = pinned;
400
+ for (const p of pinned) slow.observations.push({ key: `release.pinned:${p.home}`, family: 'pinned', def: alarm('release.pinned'), value: 1, met: { warn: true, crit: false, clears: false }, summary: `board home ${p.home} is ${p.why} (orchestrator ${p.version ?? 'unknown'})`, detail: p });
395
401
  });
396
402
  if (on.has('services')) await attempt('services', async () => {
397
403
  const list = cfg.services ?? [];
@@ -34,6 +34,8 @@ export const DEFAULTS = {
34
34
  'service.down': { forSec: 60 },
35
35
  // a process on a replaced release keeps speaking to the new one as the old: said once it has run so 5 min
36
36
  'release.drift': { forSec: 300 },
37
+ // a board home its record holds to another install's orchestrator: said once it has stayed so 5 min
38
+ 'release.pinned': { forSec: 300 },
37
39
  'supercode.connector': { forSec: 60 },
38
40
  'supercode.launch': { stuckMin: 5, failedWithinMin: 30 },
39
41
  // one failure that keeps happening (the same command or door with the same outcome, the same mail expiry) in the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-health",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "description": "Optional machine pack: a cheap, deterministic probe of a machine's vitals that mails the machine's maintainer when a threshold is crossed. Each feature sits behind its own permission barrier.",
6
6
  "license": "MIT",