@volter/supercode-health 0.1.17 → 0.1.18

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/bin/health.mjs CHANGED
@@ -7,6 +7,8 @@
7
7
  // status [--json] [--all] this machine's latest reading (latest.json)
8
8
  // features [--json] every feature, on or off, its permission barrier and why
9
9
  // alarms [--json] the alarms raised now
10
+ // promises [--json] [--last] the core promises (promises.json) read now, each with its time and the pass's total;
11
+ // --last prints the probe's last pass instead. Exits 1 when one is false.
10
12
  // subscribe <address> | unsubscribe <address> alarm mail for an agent (sc:<machine>:agent:<name>), session or board
11
13
  // config where the config lives and what it currently resolves to
12
14
  import { readFileSync } from 'node:fs';
@@ -15,7 +17,7 @@ import { Probe, subscribe, loadConfig } from '../lib/probe.mjs';
15
17
  import { healthDir, files } from '../lib/paths.mjs';
16
18
  import { formatStatus, formatFeatures } from '../lib/format.mjs';
17
19
 
18
- const { values, positionals } = parseArgs({ allowPositionals: true, options: { json: { type: 'boolean' }, all: { type: 'boolean' }, machine: { type: 'string' }, help: { type: 'boolean' } } });
20
+ const { values, positionals } = parseArgs({ allowPositionals: true, options: { last: { type: 'boolean' }, json: { type: 'boolean' }, all: { type: 'boolean' }, machine: { type: 'string' }, help: { type: 'boolean' } } });
19
21
  const verb = positionals[0] ?? 'status';
20
22
  const dir = healthDir();
21
23
  const f = files(dir);
@@ -60,12 +62,17 @@ if (values.help || verb === 'help') {
60
62
  } else if (verb === 'alarms') {
61
63
  const s = latest();
62
64
  print(s?.alarms ?? [], s?.alarms?.length ? s.alarms.map((a) => `${a.level.toUpperCase()} ${a.key}: ${a.summary} (since ${a.since})`).join('\n') : 'no alarms raised');
65
+ } else if (verb === 'promises') {
66
+ const { readPromises, formatPromises } = await import('../lib/promises.mjs');
67
+ const pass = values.last ? latest()?.readings?.promises ?? null : await readPromises({ config: loadConfig(f.config), machine: values.machine });
68
+ print(pass, formatPromises(pass));
69
+ if (!pass) process.exitCode = 4; else if (pass.items.some((item) => item.state === 'false')) process.exitCode = 1;
63
70
  } else if (verb === 'subscribe' || verb === 'unsubscribe') {
64
71
  print(subscribe(dir, positionals[1], verb === 'unsubscribe'), `${verb}d ${positionals[1]}`);
65
72
  } else if (verb === 'config') {
66
73
  const config = loadConfig(f.config);
67
74
  print({ file: f.config, config }, `config file: ${f.config}${config.source ? '' : ' (absent: defaults)'}\n${JSON.stringify(config, null, 2)}`);
68
75
  } else {
69
- process.stderr.write(`supercode-health: unknown verb ${verb} (run, once, status, features, alarms, subscribe, unsubscribe, config)\n`);
76
+ process.stderr.write(`supercode-health: unknown verb ${verb} (run, once, status, features, alarms, promises, subscribe, unsubscribe, config)\n`);
70
77
  process.exit(2);
71
78
  }
package/index.mjs CHANGED
@@ -4,3 +4,4 @@ export { FEATURES, resolveFeatures } from './lib/features.mjs';
4
4
  export { DEFAULTS, judge, meets, raised } from './lib/thresholds.mjs';
5
5
  export { healthDir, machineName } from './lib/paths.mjs';
6
6
  export { formatStatus, formatFeatures } from './lib/format.mjs';
7
+ export { readPromises, lastPromises, comparePromises, promiseList, formatPromises } from './lib/promises.mjs';
package/lib/features.mjs CHANGED
@@ -18,6 +18,7 @@ export const FEATURES = [
18
18
  { id: 'drift', barrier: 'none', platforms: ['darwin', 'linux'], reads: 'running supercode processes whose install was replaced after they started (one ps)' },
19
19
  { id: 'services', barrier: 'none', platforms: ['darwin', 'linux'], reads: "configured launchd/systemd user jobs: running, last exit, log silence" },
20
20
  { id: 'supercode', barrier: 'integration:machine-daemon', platforms: ['darwin', 'linux', 'win32'], reads: "this machine's supercode connector: answering, server link, stuck launches" },
21
+ { id: 'promises', barrier: 'integration:machine-daemon', platforms: ['darwin', 'linux'], reads: 'the core promises (promises.json), every one read live each slow interval: a false one is mailed to the `core` addresses on the pass that first reads it false' },
21
22
  { id: 'browser-tabs', barrier: 'integration:browser-controller', platforms: ['darwin', 'linux', 'win32'], reads: "every tab with its owner (controller holder, or the process and session serving its local port) and the renderer serving it" },
22
23
  { id: 'power', barrier: 'root', platforms: ['darwin'], reads: 'thermal pressure (powermetrics)' },
23
24
  ];
@@ -33,6 +34,7 @@ export function resolveFeatures({ platform = process.platform, reader, config =
33
34
  if (feature.id === 'power' && !reader.powermetricsAvailable?.()) return { ...row, on: false, reason: 'powermetrics not found' };
34
35
  if (feature.id === 'browser-tabs' && !config.integrations?.browserController) return { ...row, on: false, reason: 'no browser controller configured (integrations.browserController.portFile or .url)' };
35
36
  if (feature.id === 'services' && !(config.services ?? []).length) return { ...row, on: false, reason: 'no services configured (services: [{label, log?, maxSilentMin?}])' };
37
+ if (feature.id === 'promises' && extras.supercode === false) return { ...row, on: false, reason: 'supercode not found (SUPERCODE_BIN)' };
36
38
  if (feature.id === 'supercode' && extras.supercode === false) return { ...row, on: false, reason: 'supercode not found (SUPERCODE_BIN)' };
37
39
  return { ...row, on: true, reason: null };
38
40
  });
package/lib/format.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  // The snapshot as a person reads it: alarms first, then each reading, then which features are off and why.
2
2
  import { groupUnknowns } from './mail.mjs';
3
+ import { formatPromises } from './promises.mjs';
3
4
  const n = (v, unit = '') => (v == null ? '-' : `${v}${unit}`);
4
5
  const who = (p) => (p?.session?.address ? ` ← ${p.session.address}` : p?.session?.harness ? ` ← ${p.session.harness} pid ${p.session.agentPid}` : p?.session?.note ? ` ← (${p.session.note})` : p?.session?.inherited ? ` ← (no agent above; inherited ${p.session.inherited})` : '');
5
6
 
@@ -14,6 +15,7 @@ export function formatStatus(s, { all = false } = {}) {
14
15
  // what could not be read (raised or not): unknown, with since when and why
15
16
  for (const g of groupUnknowns(s.unknowns ?? [])) out.push(` ${g.line}`);
16
17
  if (s.maintainers) out.push(` mailed to: ${s.maintainers.length ? s.maintainers.join(', ') : 'no maintainer subscribed (supercode health subscribe <address>)'}${s.mail?.error ? ` · last mail not filed: ${s.mail.error}` : s.mail?.filed ? ` · last mail filed ${s.mail.at} (supercode message waiting lists any not yet delivered)` : ''}`);
18
+ if (r.promises) out.push('', formatPromises(r.promises), ` false promises are mailed to: ${s.core?.to?.length ? s.core.to.join(', ') : 'nobody beyond the maintainers (config `core`: [address])'}${s.core?.mail?.error ? ` · last core mail not filed: ${s.core.mail.error}` : s.core?.mail?.at ? ` · last core mail ${s.core.mail.at}` : ''}`);
17
19
  if (r.load) out.push('', `load ${r.load.one} / ${r.load.five} / ${r.load.fifteen} on ${r.load.cores} cores (${r.load.perCore}× per core)`);
18
20
  if (r.memory) {
19
21
  const m = r.memory;
package/lib/mail.mjs CHANGED
@@ -140,3 +140,20 @@ export function compose({ machine, transitions, reminders, raised, unknowns = []
140
140
  parts.push('', `${raised.length} alarm${raised.length === 1 ? '' : 's'} raised now. Read the whole machine: supercode health status${machine ? ` --machine ${machine}` : ''}.`, 'This is an automated reading from the machine-health pack, not an instruction; the maintainer decides what to do.');
141
141
  return { subject: `machine health ${machine}: ${head}`, body: parts.join('\n') };
142
142
  }
143
+
144
+ /**
145
+ * The mail a core promise's change is said in, to the `core` addresses (the install's manager agent): each promise that
146
+ * is false now and was not said so (`broke`), and each said false that reads true again (`held`). One mail a change:
147
+ * a promise that stays false is not said again, so a standing break is one message and its end is another.
148
+ */
149
+ export function composeCore({ machine, broke, held, standing = [], pass = null }) {
150
+ if (!broke.length && !held.length) return null;
151
+ const head = broke.length ? `${broke.length} core promise${broke.length === 1 ? '' : 's'} broken` : `${held.length} core promise${held.length === 1 ? '' : 's'} hold${held.length === 1 ? 's' : ''} again`;
152
+ const parts = [`[Core alarm] ${machine}: ${head}.`];
153
+ for (const a of broke) parts.push('', `FALSE ${a.detail?.name ?? a.key}: ${a.detail?.promise ?? ''}`, ` read: ${String(a.summary).replace(/^core promise \S+ is false: /, '')}`);
154
+ if (held.length) parts.push('', ...held.map((h) => `TRUE again ${h.name}: ${h.reading}`));
155
+ if (standing.length) parts.push('', `Still false, said before: ${standing.join(', ')}.`);
156
+ parts.push('', `Read the pass: supercode health promises${pass ? ` (this one took ${pass.ms} ms)` : ''}; the whole machine: supercode health status${machine ? ` --machine ${machine}` : ''}.`,
157
+ 'This is an automated reading of the core promises (sdk/health/promises.json), not an instruction. It is said once when a promise breaks and once when it holds again.');
158
+ return { subject: `core alarm ${machine}: ${head}`, body: parts.join('\n') };
159
+ }
package/lib/probe.mjs CHANGED
@@ -8,7 +8,7 @@ import { healthDir, files as healthFiles, machineName } from './paths.mjs';
8
8
  import { DEFAULTS, merge, judge, raised } from './thresholds.mjs';
9
9
  import { resolveFeatures } from './features.mjs';
10
10
  import { Sampler } from './sample.mjs';
11
- import { fileAlarm, compose, composeState, unknownKind } from './mail.mjs';
11
+ import { fileAlarm, compose, composeState, composeCore, unknownKind } from './mail.mjs';
12
12
  import { run } from './run.mjs';
13
13
 
14
14
  export async function platformReader(platform = process.platform) {
@@ -55,6 +55,9 @@ export class Probe {
55
55
  // flip in between (t_be880329: disk and memory flapping mailed the maintainer ~1,200 times a day on yuerans)
56
56
  this.said = new Map(); this.changesMailAt = 0; this.raisedLastAt = new Map(); // key → when it was last seen raised
57
57
  this.unknowns = {}; this.unknownMailAt = {}; // unknownMailAt: a kind of unknown (unknownKind: its key's prefix) → last mailed
58
+ // the core promises said false to the `core` addresses (key → name): a promise is said when it breaks and when it
59
+ // holds again, never while it stays as it was said
60
+ this.coreSaid = new Map(); this.lastCoreMail = null;
58
61
  this.lastHistory = 0;
59
62
  this.sampleMs = null;
60
63
  }
@@ -103,6 +106,8 @@ export class Probe {
103
106
  for (const a of (previous?.alarms ?? []).filter((x) => !x.detail?.pid || alive(x.detail.pid))) this.state[a.key] = { key: a.key, family: a.family, level: a.level, warnSince: null, critSince: null, raisedAt: a.since ? Date.parse(a.since) : Date.now(), lastMailAt: this.droppedOutbox ? 0 : Date.now(), restoredUntil: Date.now() + 2 * 60_000, value: a.value, unit: a.unit, summary: a.summary, detail: a.detail,
104
107
  ...(a.unknown?.since ? { unread: a.unknown.why, unreadAt: Date.parse(a.unknown.since) } : {}) };
105
108
  for (const a of (previous?.alarms ?? [])) this.said.set(a.key, { level: a.level, summary: a.summary, detail: a.detail ?? null });
109
+ for (const [key, name] of Object.entries(previous?.core?.said ?? {})) this.coreSaid.set(key, name);
110
+ this.lastCoreMail = previous?.core?.mail ?? null;
106
111
  }
107
112
 
108
113
  /** Remove an earlier pack's outbox; answers how many mails it held, logged by recipient. */
@@ -203,6 +208,17 @@ export class Probe {
203
208
  if (composedState || changes.length) this.changesMailAt = now;
204
209
  this.log(`${mail.subject}${to.length ? '' : ' (no maintainer subscribed)'}\n${mail.body}`);
205
210
  }
211
+ // A core promise read false is the install's manager's to know at once (config `core`), beside the maintainers'
212
+ // mail above: said on the cycle its alarm raises (its pass's own reading: no window), and once more when it reads
213
+ // true again. A promise held unknown stays raised and is not said to hold.
214
+ const core = [...new Set(this.config.core ?? [])];
215
+ const falseNow = alarms.filter((a) => a.family === 'promise');
216
+ const falseKeys = new Set(falseNow.map((a) => a.key));
217
+ const broke = falseNow.filter((a) => !this.coreSaid.has(a.key));
218
+ const pass = readings.promises ?? null;
219
+ const held = [...this.coreSaid].filter(([key]) => !falseKeys.has(key)).map(([key, name]) => ({ key, name, reading: pass?.items?.find((item) => item.name === name)?.reading ?? 'its alarm cleared' }));
220
+ const coreMail = core.length ? composeCore({ machine: this.machine, broke, held, standing: falseNow.filter((a) => this.coreSaid.has(a.key)).map((a) => a.detail?.name ?? a.key), pass }) : null;
221
+ if (!core.length && (broke.length || held.length) && !this.coreUnsaid) { this.coreUnsaid = true; this.log(`${broke.length} core promise(s) false and no \`core\` address is configured: said to the maintainers only`); }
206
222
  const snapshot = {
207
223
  version: 1, machine: this.machine, platform: this.platform, at: new Date(now).toISOString(),
208
224
  probe: { pid: process.pid, intervalSec: this.config.intervalSec, slowIntervalSec: this.config.slowIntervalSec, sampleMs: this.sampleMs, user: this.env.USER ?? null, root: this.reader.isRoot?.() ?? false, config: this.config.source },
@@ -213,6 +229,7 @@ export class Probe {
213
229
  transitions: transitions.map((t) => ({ key: t.key, from: t.from, to: t.to, summary: t.summary })),
214
230
  maintainers: to,
215
231
  mail: this.lastMail ?? null,
232
+ core: { to: core, said: Object.fromEntries(this.coreSaid), mail: this.lastCoreMail },
216
233
  readings,
217
234
  };
218
235
  // The reading is written first; the mail is filed after it, and what the filing did is written beside it.
@@ -224,6 +241,18 @@ export class Probe {
224
241
  snapshot.mail = this.lastMail;
225
242
  writeAtomic(this.f.latest, snapshot);
226
243
  }
244
+ if (coreMail) {
245
+ const result = await fileAlarm({ machine: this.machine, env: this.env, to: core, key: `core-${this.machine}-${now}`, subject: coreMail.subject, body: coreMail.body });
246
+ this.lastCoreMail = { at: new Date(now).toISOString(), filed: result.filed.length, to: result.filed.map((f) => f.to), error: result.error, ms: Date.now() - now };
247
+ // said once it is filed for someone: a mail that could not be filed is tried again on the next cycle
248
+ if (result.filed.length) {
249
+ for (const a of broke) this.coreSaid.set(a.key, a.detail?.name ?? a.key);
250
+ for (const h of held) this.coreSaid.delete(h.key);
251
+ }
252
+ this.log(`${coreMail.subject}${result.error ? ` (${result.error})` : ''}\n${coreMail.body}`);
253
+ snapshot.core = { to: core, said: Object.fromEntries(this.coreSaid), mail: this.lastCoreMail };
254
+ writeAtomic(this.f.latest, snapshot);
255
+ }
227
256
  if (now - this.lastHistory >= 60_000) { this.#history(snapshot, now); this.lastHistory = now; }
228
257
  return snapshot;
229
258
  }
@@ -0,0 +1,274 @@
1
+ // The core promises, read live (promises.json is the list). One pass reads every promise at once, each inside its own
2
+ // hard bound, and says of each `true`, `false` or `unknown` with the reading behind it and the milliseconds it took.
3
+ // A pass changes nothing: it reads files, this machine's daemon socket, two process listings, the server's machine
4
+ // list and the installed release's own restart check. Where another program has to be started the reason is beside
5
+ // it; nothing here starts a shell to read what a file or a socket answers. No reading carries an environment's values,
6
+ // a credential or a message's text.
7
+ //
8
+ // Why a pass and not only alarms: each of 2026-10-09's later breaks was one of these promises false on a live machine
9
+ // for hours (the server listing a linked machine offline, a restart refused for a day, two Codex installs, a disk at
10
+ // zero) and readable there the whole time. The probe (probe.mjs) runs the pass every slow interval and mails a false
11
+ // promise to the `core` addresses on the pass that first reads it false; a release's activation watch runs the same
12
+ // pass once the new daemon serves and compares it with the last pass before it.
13
+ import { readFileSync, realpathSync, statfsSync, statSync, accessSync, constants } from 'node:fs';
14
+ import { homedir, tmpdir, userInfo } from 'node:os';
15
+ import { delimiter, isAbsolute, join } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { run } from './run.mjs';
18
+ import { daemonCall } from './mail.mjs';
19
+ import { healthDir, machineName } from './paths.mjs';
20
+ import { staleSupercode } from './drift.mjs';
21
+ import { harnessMarks } from './markers.mjs';
22
+
23
+ const json = (file) => { try { return JSON.parse(readFileSync(file, 'utf8')); } catch { return null; } };
24
+ const supercodeHome = (env) => env.SUPERCODE_HOME || join(env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'supercode');
25
+ const teamsHome = (env) => env.SUPERCODE_TEAMS_HOME ?? join(supercodeHome(env), 'teams');
26
+ const ago = (ms) => (ms < 90_000 ? `${Math.round(ms / 1000)} s` : ms < 5_400_000 ? `${Math.round(ms / 60_000)} min` : `${Math.round(ms / 360_000) / 10} h`);
27
+ const yes = (reading, detail) => ({ state: 'true', reading, ...(detail ? { detail } : {}) });
28
+ const no = (reading, detail) => ({ state: 'false', reading, ...(detail ? { detail } : {}) });
29
+ const unknown = (reading) => ({ state: 'unknown', reading });
30
+
31
+ /** The list: `{ version, promises: [{ name, promise, probe, bound_ms, activation }] }`. */
32
+ export function promiseList() {
33
+ return JSON.parse(readFileSync(new URL('../promises.json', import.meta.url), 'utf8'));
34
+ }
35
+
36
+ /** What the pass's lines are set by; a machine's config overrides any (`promises.<name>`). */
37
+ export const PROMISE_DEFAULTS = Object.freeze({
38
+ mailExpiryMin: 15, // mail that expired undelivered this recently says delivery is failing now
39
+ staleReleaseHours: 2, // extrapolated: a service restarts with its install; two hours is past any restart in hand
40
+ boardRoundMin: 10, // extrapolated from a round's own length (about 30 s here) and the maintainer's 5 min line
41
+ diskFreeGB: 5, // extrapolated: admission's copy of an 800 MB mailbox and an install both need room
42
+ fleetSeenHours: 24, // a machine silent longer than this is taken to be off, not broken
43
+ harnesses: ['codex', 'claude'],
44
+ });
45
+
46
+ /** The server's machine list, asked once a pass and shared by the two promises that read it. The server alone can say
47
+ * what it sees, and the credential that asks it stays inside the CLI: this is the pass's one call through `supercode`
48
+ * beside the restart check. */
49
+ function machineList(context) {
50
+ context.machines ??= run(context.bin, ['teams', 'machines', 'list', '--json'], { timeoutMs: 2800, env: context.env }).then((answer) => {
51
+ if (!answer.ok) throw new Error(`the server's machine list was not read: ${answer.error}`);
52
+ const body = JSON.parse(answer.stdout);
53
+ const items = body.items ?? body.data?.items;
54
+ if (!Array.isArray(items)) throw new Error('the server answered no machine list');
55
+ return items;
56
+ });
57
+ return context.machines;
58
+ }
59
+
60
+ /** Every `name` an absolute folder of `search` holds as an executable file, in order, one per real file. */
61
+ function installs(name, search) {
62
+ const found = [], seen = new Set();
63
+ for (const folder of String(search ?? '').split(delimiter)) {
64
+ if (!folder || !isAbsolute(folder)) continue;
65
+ const candidate = join(folder, name);
66
+ try { if (!statSync(candidate).isFile()) continue; accessSync(candidate, constants.X_OK); } catch { continue; }
67
+ let real = candidate; try { real = realpathSync(candidate); } catch { /* as named */ }
68
+ if (seen.has(real)) continue;
69
+ seen.add(real); found.push(candidate);
70
+ }
71
+ return found;
72
+ }
73
+
74
+ const PROBES = {
75
+ async server(context) {
76
+ const items = await machineList(context);
77
+ const mine = items.filter((m) => m.enrollment === 'active' && machineName(m.name) === context.machine);
78
+ if (!mine.length) return unknown(`the server lists no active machine named ${context.machine}`);
79
+ const machine = mine.sort((a, b) => (b.last_seen_at ?? 0) - (a.last_seen_at ?? 0))[0];
80
+ const heard = machine.last_seen_at == null ? 'never heard from' : `last heard ${ago(context.now - machine.last_seen_at)} ago`;
81
+ const detail = { connection: machine.connection, last_seen_at: machine.last_seen_at ?? null, revision: machine.revision ?? null };
82
+ return machine.connection === 'online' ? yes(`the server lists ${context.machine} online, ${heard}`, detail)
83
+ : no(`the server lists ${context.machine} ${machine.connection}, ${heard}`, detail);
84
+ },
85
+
86
+ async fleet(context) {
87
+ const items = await machineList(context);
88
+ const window = context.lines.fleetSeenHours * 3_600_000;
89
+ const peers = items.filter((m) => m.enrollment === 'active' && machineName(m.name) !== context.machine && m.last_seen_at != null && context.now - m.last_seen_at <= window);
90
+ const down = peers.filter((m) => m.connection !== 'online');
91
+ const detail = { peers: peers.length, down: down.map((m) => ({ name: m.name, connection: m.connection, last_seen_at: m.last_seen_at })) };
92
+ return down.length ? no(`${down.length} of ${peers.length} machine(s) heard from in the last ${context.lines.fleetSeenHours} h are not online: ${down.map((m) => `${m.name} (${m.connection}, last heard ${ago(context.now - m.last_seen_at)} ago)`).join(', ')}`, detail)
93
+ : yes(`${peers.length} other machine(s) heard from in the last ${context.lines.fleetSeenHours} h, all online`, detail);
94
+ },
95
+
96
+ // The installed release's own check, as its restart would run it: the code that decides is the teams package's, and
97
+ // this pack depends on nothing, so it is asked through the release's CLI (one node start, no shell).
98
+ async restart(context) {
99
+ const answer = await run(context.bin, ['teams', 'mail', 'restart-check', '--json'], { timeoutMs: 2800, env: context.env });
100
+ let receipt = null; try { receipt = JSON.parse(answer.stdout); } catch { /* no receipt */ }
101
+ if (receipt?.stage !== 'restart_standing') return unknown(`the restart check gave no receipt: ${(answer.error || answer.stderr || 'no output').trim().split('\n')[0].slice(0, 200)}`);
102
+ return receipt.admitted ? yes(`no standing refusal (${receipt.checks?.legacy_wrappers?.live ?? 0} pane wrapper(s) read; the mailbox import itself is not run)`, receipt.checks)
103
+ : no(`a restart would be refused: ${receipt.refusals.join('; ')}`, receipt.checks);
104
+ },
105
+
106
+ async mail(context) {
107
+ const record = json(join(teamsHome(context.env), 'machine.json'));
108
+ if (!record?.pid) return no('no machine daemon has a record on this machine');
109
+ const answer = await daemonCall('harness.v1.mail.expired', {}, { env: context.env, timeoutMs: 1300 });
110
+ if (!Array.isArray(answer?.rows)) return no('the daemon answered its mail door with no list of expiries');
111
+ if (record.mail_ready !== true) return no(`the daemon (pid ${record.pid}) answers and does not serve mail`);
112
+ const since = context.now - context.lines.mailExpiryMin * 60_000;
113
+ const recent = answer.rows.filter((row) => Number(row?.expired_at_ms) >= since);
114
+ const reasons = [...new Set(recent.map((row) => String(row.reason ?? 'no reason given').slice(0, 80)))];
115
+ const detail = { pid: record.pid, expired_recently: recent.length, reasons };
116
+ return recent.length ? no(`${recent.length} mail(s) expired undelivered in the last ${context.lines.mailExpiryMin} min (${reasons.join(', ')})`, detail)
117
+ : yes(`the daemon (pid ${record.pid}) serves mail; none expired undelivered in the last ${context.lines.mailExpiryMin} min`, detail);
118
+ },
119
+
120
+ // The login shell is started because only it can say its PATH (its profile builds it), and each install is asked its
121
+ // own version because only it knows: the two things a launch does. Codex's background server names its version in
122
+ // the folder it runs from, which is read from the process list.
123
+ async harness(context) {
124
+ const env = context.env;
125
+ const shell = env.SHELL?.trim() || (() => { try { return userInfo().shell; } catch { return null; } })() || '/bin/sh';
126
+ const asked = process.platform === 'win32' ? { ok: false } : await run(shell, ['-lc', 'printf "\\n<supercode-path>%s</supercode-path>\\n" "$PATH"'], { timeoutMs: 2000, env });
127
+ const login = /<supercode-path>([^\n]*)<\/supercode-path>/.exec(asked.stdout ?? '')?.[1]?.trim() ?? null;
128
+ const search = [...new Set([...String(login ?? '').split(delimiter), ...String(env.PATH ?? '').split(delimiter)].filter(Boolean))].join(delimiter);
129
+ const rows = [], disagreements = [];
130
+ await Promise.all(context.lines.harnesses.map(async (name) => {
131
+ const found = installs(name, search);
132
+ const versions = await Promise.all(found.map(async (program) => {
133
+ const said = await run(program, ['--version'], { timeoutMs: 1800, env });
134
+ return { program, version: said.ok ? said.stdout.trim().split('\n')[0].slice(0, 60) : 'version unreadable' };
135
+ }));
136
+ rows.push({ harness: name, installs: versions });
137
+ if (new Set(versions.map((v) => v.version)).size > 1) disagreements.push(`${name}: ${versions.map((v) => `${v.program} is ${v.version}`).join(', ')} (a launch runs the first)`);
138
+ if (name === 'codex' && versions.length) {
139
+ const launched = /(\d+\.\d+\.\d+\S*)/.exec(versions[0].version)?.[1] ?? null;
140
+ const listed = await run('/bin/ps', ['-axww', '-o', 'command='], { timeoutMs: 1500 });
141
+ const servers = new Set([...String(listed.stdout ?? '').matchAll(/\/app-server-daemon\/releases\/(\d+\.\d+\.\d+[^-/\s]*)-[^/\s]*\/bin\/codex\s+app-server/g)].map((m) => m[1]));
142
+ rows.push({ harness: 'codex background server', versions: [...servers] });
143
+ const other = [...servers].filter((version) => version !== launched);
144
+ if (launched && other.length) disagreements.push(`codex: its background server runs ${other.join(', ')} while a launch runs ${launched}`);
145
+ }
146
+ }));
147
+ if (login == null && process.platform !== 'win32') return unknown(`the login shell (${shell}) gave no PATH within 2 s`);
148
+ return disagreements.length ? no(disagreements.join('; '), rows)
149
+ : yes(rows.filter((r) => r.installs).map((r) => `${r.harness}: ${r.installs.length ? `${r.installs.length} install(s), ${r.installs[0].version}` : 'not installed'}`).join('; '), rows);
150
+ },
151
+
152
+ async marks() {
153
+ const { marked } = await harnessMarks({ ps: await run('/bin/ps', [process.platform === 'linux' ? '-axww' : '-axEww', '-o', 'pid=,tty=,etime=,command='], { timeoutMs: 1800 }) });
154
+ return marked.length ? no(`${marked.length} terminal process(es) carry a harness's child-session marks: ${marked.slice(0, 6).map((m) => `${m.program} ${m.pid} (${m.markers.join(', ')})`).join(', ')}`, marked)
155
+ : yes('no terminal window or tmux server carries a child-session mark');
156
+ },
157
+
158
+ async stale(context) {
159
+ const line = context.lines.staleReleaseHours * 3_600_000;
160
+ const stale = await staleSupercode();
161
+ const over = stale.filter((row) => context.now - Date.parse(row.installedAt) > line);
162
+ // what each runs, for the name: its program's last two path parts and its verb, never its arguments
163
+ const named = new Map();
164
+ for (const row of over) {
165
+ const words = row.command.split(/\s+/u), node = /(^|\/)node$/u.test(words[0]);
166
+ const program = (node ? words[1] : words[0]).split('/').slice(-2).join('/');
167
+ const verb = words.slice(node ? 2 : 1).filter((word) => /^[a-z][a-z-]*$/u.test(word)).slice(0, 2).join(' ');
168
+ const key = verb ? `${program} ${verb}` : program;
169
+ named.set(key, (named.get(key) ?? 0) + 1);
170
+ }
171
+ const detail = { stale: stale.length, over: over.length, by: Object.fromEntries(named), oldest_start: over.map((row) => row.startedAt).sort()[0] ?? null, pids: over.slice(0, 40).map((row) => row.pid) };
172
+ return over.length ? no(`${over.length} process(es) still run a release replaced more than ${context.lines.staleReleaseHours} h ago: ${[...named].sort((a, b) => b[1] - a[1]).map(([what, count]) => `${count} ${what}`).join(', ')}; the oldest started ${detail.oldest_start}`, detail)
173
+ : yes(stale.length ? `${stale.length} process(es) run a release replaced in the last ${context.lines.staleReleaseHours} h, none longer` : 'every supercode process runs the release on disk', detail);
174
+ },
175
+
176
+ async board(context) {
177
+ const held = json(join(supercodeHome(context.env), 'board-homes.json')) ?? {};
178
+ const line = context.lines.boardRoundMin * 60_000, rounds = [], broken = [];
179
+ for (const root of new Set(Object.values(held).filter((value) => typeof value === 'string'))) {
180
+ const beat = json(join(root, 'dispatcher-heartbeat.json'));
181
+ if (!beat?.at) continue; // no dispatcher has run a round in this home
182
+ const age = context.now - Date.parse(beat.at);
183
+ const failed = (beat.boards ?? []).reduce((sum, board) => sum + (Number(board.failed) || 0), 0), errors = (beat.errors ?? []).length;
184
+ rounds.push({ home: root, round: beat.round ?? null, at: beat.at, failed, errors });
185
+ if (!(age <= line)) broken.push(`${root}: its last round (${beat.round ?? '?'}) ended ${ago(age)} ago`);
186
+ else if (failed || errors) broken.push(`${root}: round ${beat.round ?? '?'} failed ${failed} and raised ${errors} error(s)`);
187
+ }
188
+ if (!rounds.length) return yes('no board dispatcher has run a round on this machine');
189
+ return broken.length ? no(broken.join('; '), rounds) : yes(rounds.map((r) => `${r.home}: round ${r.round ?? '?'} ${ago(context.now - Date.parse(r.at))} ago, failed 0`).join('; '), rounds);
190
+ },
191
+
192
+ async disk(context) {
193
+ const volumes = new Map();
194
+ for (const folder of [supercodeHome(context.env), homedir(), tmpdir()]) {
195
+ let stat; try { stat = statfsSync(folder); } catch { continue; }
196
+ // one volume is told from another by its size and free space together: folders on the same one read the same
197
+ const key = `${stat.blocks}:${stat.bsize}:${stat.bavail}`;
198
+ if (!volumes.has(key)) volumes.set(key, { folder, freeGB: Math.round((Number(stat.bavail) * Number(stat.bsize)) / 2 ** 30 * 10) / 10 });
199
+ }
200
+ if (!volumes.size) return unknown('no volume could be read');
201
+ const rows = [...volumes.values()], low = rows.filter((row) => row.freeGB <= context.lines.diskFreeGB);
202
+ return low.length ? no(low.map((row) => `the volume of ${row.folder} has ${row.freeGB} GB free (the line is ${context.lines.diskFreeGB} GB)`).join('; '), rows)
203
+ : yes(rows.map((row) => `${row.folder}: ${row.freeGB} GB free`).join('; '), rows);
204
+ },
205
+
206
+ async activation(context) {
207
+ const verdict = json(join(teamsHome(context.env), 'service', 'last-activation.json'));
208
+ if (!verdict?.state) return yes('no activation is recorded on this machine');
209
+ const release = verdict.candidate?.release ?? verdict.release ?? 'a release';
210
+ const at = verdict.at_ms ? new Date(verdict.at_ms).toISOString() : 'an unrecorded time';
211
+ if (['rolled_back', 'restore_failed', 'restarted'].includes(verdict.state)) {
212
+ return no(`${release} was ${verdict.state === 'rolled_back' ? `put back at ${at}; ${verdict.restored?.release ?? 'the release before'} serves` : verdict.state === 'restarted' ? `stopped and started again at ${at}` : `stopped at ${at} and nothing was restored`}: ${verdict.reason ?? verdict.detail ?? 'no reason recorded'}`,
213
+ { state: verdict.state, at_ms: verdict.at_ms ?? null, broken: verdict.promises?.broken ?? [] });
214
+ }
215
+ const broken = verdict.promises?.broken ?? [];
216
+ if (broken.length) return no(`${release} was kept at ${at} and broke ${broken.map((b) => b.name).join(', ')}: ${verdict.promises.kept_because ?? 'it was not put back'}`, { state: verdict.state, at_ms: verdict.at_ms ?? null, broken });
217
+ return yes(`${release} ${verdict.state} at ${at}`);
218
+ },
219
+ };
220
+
221
+ /**
222
+ * One pass: `{ version, at, machine, ms, items: [{ name, promise, state, reading, detail?, ms, bound_ms, activation }] }`.
223
+ * Every probe runs at once; one that has not answered inside its bound is `unknown` and says so, and the pass ends
224
+ * with the slowest probe. `only` reads some of the list by name.
225
+ */
226
+ export async function readPromises({ env = process.env, config = {}, machine = null, only = null, now = Date.now() } = {}) {
227
+ const started = performance.now();
228
+ const context = { env, now, bin: env.SUPERCODE_BIN || 'supercode', machine: machineName(machine || env.SUPERCODE_HEALTH_MACHINE || undefined), lines: { ...PROMISE_DEFAULTS, ...(config.promises ?? {}) }, machines: null };
229
+ const list = promiseList().promises.filter((entry) => !only || only.includes(entry.name));
230
+ const items = await Promise.all(list.map(async (entry) => {
231
+ const t0 = performance.now();
232
+ let timer;
233
+ const bound = new Promise((resolve) => { timer = setTimeout(() => resolve(unknown(`not read within its ${entry.bound_ms} ms bound`)), entry.bound_ms); });
234
+ const probe = PROBES[entry.probe];
235
+ const read = probe ? Promise.resolve().then(() => probe(context)).catch((error) => unknown(`could not be read: ${String(error.message ?? error).split('\n')[0].slice(0, 240)}`))
236
+ : Promise.resolve(unknown(`this release has no probe named ${entry.probe}`));
237
+ const result = await Promise.race([read, bound]);
238
+ clearTimeout(timer);
239
+ return { name: entry.name, promise: entry.promise, ...result, ms: Math.round(performance.now() - t0), bound_ms: entry.bound_ms, activation: entry.activation };
240
+ }));
241
+ return { version: 1, at: new Date(now).toISOString(), machine: context.machine, ms: Math.round(performance.now() - started), items };
242
+ }
243
+
244
+ /** The last pass the probe wrote into its snapshot (`latest.json`), when it is no older than `maxAgeMs`; else null. */
245
+ export function lastPromises({ env = process.env, maxAgeMs = 300_000, now = Date.now() } = {}) {
246
+ const pass = json(join(healthDir(env), 'latest.json'))?.readings?.promises;
247
+ return pass?.at && now - Date.parse(pass.at) <= maxAgeMs ? pass : null;
248
+ }
249
+
250
+ /**
251
+ * A pass against the one before it: `broken` are the promises true before and false now, `standing` false both times
252
+ * or false now with no true reading before, `unread` unknown now. With no pass before, nothing is `broken`.
253
+ */
254
+ export function comparePromises(before, after) {
255
+ const was = new Map((before?.items ?? []).map((item) => [item.name, item.state]));
256
+ const broken = [], standing = [], unread = [];
257
+ for (const item of after?.items ?? []) {
258
+ const row = { name: item.name, activation: item.activation, reading: item.reading, before: was.get(item.name) ?? 'unread' };
259
+ if (item.state === 'unknown') unread.push(row);
260
+ else if (item.state === 'false') (row.before === 'true' ? broken : standing).push(row);
261
+ }
262
+ return { broken, standing, unread };
263
+ }
264
+
265
+ /** A pass as a person reads it: one line a promise, its state, its reading and its time, then the pass's total. */
266
+ export function formatPromises(pass) {
267
+ if (!pass) return 'promises: no pass yet';
268
+ const width = Math.max(...pass.items.map((item) => item.name.length));
269
+ const lines = pass.items.map((item) => ` ${item.state === 'true' ? 'true ' : item.state === 'false' ? 'FALSE ' : 'unknown'} ${item.name.padEnd(width)} ${String(item.ms).padStart(4)} ms ${item.reading}`);
270
+ const count = (state) => pass.items.filter((item) => item.state === state).length;
271
+ return [`core promises · ${pass.machine} · ${pass.at} · ${count('false')} false, ${count('unknown')} unknown, ${count('true')} true · whole pass ${pass.ms} ms`, ...lines].join('\n');
272
+ }
273
+
274
+ export const promisesFile = fileURLToPath(new URL('../promises.json', import.meta.url));
package/lib/sample.mjs CHANGED
@@ -10,6 +10,7 @@ import { healthDir } from './paths.mjs';
10
10
  import { pinnedHomes, staleSupercode } from './drift.mjs';
11
11
  import { brokenRecorded } from './recorded.mjs';
12
12
  import { harnessMarks } from './markers.mjs';
13
+ import { readPromises } from './promises.mjs';
13
14
  import { join } from 'node:path';
14
15
 
15
16
  const MB = 1024 * 1024;
@@ -31,9 +32,9 @@ function argvSession(command) {
31
32
  ?? /\bresume\s+([0-9a-zA-Z][0-9a-zA-Z-]{7,})/.exec(command)?.[1] ?? null;
32
33
  }
33
34
  // The slow set's readings and alarm families, by the feature that produces them.
34
- const SLOW_FEATURE = { disk: 'disk', services: 'services', service: 'services', supercode: 'supercode', browserTabs: 'browser-tabs', tabs: 'browser-tabs', power: 'power' };
35
+ const SLOW_FEATURE = { promises: 'promises', promise: 'promises', disk: 'disk', services: 'services', service: 'services', supercode: 'supercode', browserTabs: 'browser-tabs', tabs: 'browser-tabs', power: 'power' };
35
36
  // Every alarm family, by the feature that observes it.
36
- const FAMILY_FEATURE = { load: 'load', memory: 'memory', process: 'processes', browser: 'renderers', daemon: 'daemons', 'disk-writer': 'disk-io', files: 'files', tcp: 'tcp', disk: 'disk', service: 'services', supercode: 'supercode', tabs: 'browser-tabs', power: 'power' };
37
+ const FAMILY_FEATURE = { promise: 'promises', load: 'load', memory: 'memory', process: 'processes', browser: 'renderers', daemon: 'daemons', 'disk-writer': 'disk-io', files: 'files', tcp: 'tcp', disk: 'disk', service: 'services', supercode: 'supercode', tabs: 'browser-tabs', power: 'power' };
37
38
  // The families a feature's read observes: when its read fails, each is unread (judged neither raised nor cleared).
38
39
  // `processes` also observes the renderer, daemon and disk-writer families it reads beside its own; `disk-io`'s own read
39
40
  // (device throughput) judges no family: the disk writers are the process read's, so a throughput read that fails leaves
@@ -348,6 +349,9 @@ export class Sampler {
348
349
  const alarm = (name) => cfg.alarms[name];
349
350
  const attempt = async (id, fn) => { try { await fn(); } catch (error) { errors[id] = error.message; slow.observations.push(...unreadFamilies(id, error.message)); } };
350
351
 
352
+ // The core promises: their own pass, every probe at once (lib/promises.mjs), started first and joined last so
353
+ // nothing else in the slow set holds it back or is held by it. A false promise raises on this reading (no window).
354
+ const promised = on.has('promises') ? readPromises({ env: this.env, config: cfg, machine: this.machine, now }) : null;
351
355
  if (on.has('disk')) await attempt('disk', async () => {
352
356
  const def = alarm('disk.free');
353
357
  const fill = alarm('disk.filling');
@@ -447,6 +451,17 @@ export class Sampler {
447
451
  });
448
452
  });
449
453
 
454
+ if (promised) await attempt('promises', async () => {
455
+ const pass = await promised;
456
+ slow.families.add('promise');
457
+ slow.readings.promises = pass;
458
+ for (const item of pass.items) {
459
+ const key = `promise:${item.name}`;
460
+ if (item.state === 'unknown') slow.observations.push({ unreadKey: key, family: 'promise', why: item.reading });
461
+ else slow.observations.push({ key, family: 'promise', def: alarm('promise'), value: item.state === 'false' ? 1 : 0, met: { warn: item.state === 'false', crit: item.state === 'false', clears: item.state === 'true' },
462
+ summary: item.state === 'false' ? `core promise ${item.name} is false: ${item.reading}` : `core promise ${item.name} holds: ${item.reading}`, detail: { promise: item.promise, name: item.name } });
463
+ }
464
+ });
450
465
  if (on.has('supercode')) await attempt('supercode', async () => {
451
466
  const bin = this.env.SUPERCODE_BIN || 'supercode';
452
467
  slow.families.add('supercode');
@@ -8,6 +8,8 @@ export const DEFAULTS = {
8
8
  quietMin: 15, // what changed is mailed at most this often (a new crit at once): a flapping alarm is no stream
9
9
  unknownMin: 15, // a reading unknown this long (raised or not) is mailed to the maintainers, and again each repeatMin
10
10
  historyHours: 24,
11
+ core: [], // the addresses a false core promise is mailed to (the install's manager agent), beside the maintainers
12
+ promises: {}, // the promise pass's lines (lib/promises.mjs PROMISE_DEFAULTS), any of them overridden here
11
13
  daemons: {
12
14
  darwin: ['dasd', 'fseventsd', 'mds', 'mds_stores', 'mdworker_shared', 'WindowServer', 'kernel_task', 'backupd', 'softwareupdated', 'photoanalysisd', 'cloudd', 'bird', 'nsurlsessiond', 'trustd', 'syspolicyd', 'XprotectService', 'logd', 'runningboardd', 'launchservicesd', 'coreduetd', 'spotlightknowledged'],
13
15
  linux: ['systemd-journald', 'kswapd0', 'dockerd', 'containerd', 'snapd', 'packagekitd', 'tracker-miner-fs-3', 'baloo_file', 'updatedb'],
@@ -48,6 +50,8 @@ export const DEFAULTS = {
48
50
  // one failure that keeps happening (the same command or door with the same outcome, the same mail expiry) in the
49
51
  // window; it clears when the window holds none of it (t_b3844842)
50
52
  'supercode.failing': { warn: 3, crit: 20, clear: 0, windowMin: 60, forSec: 0 },
53
+ // a core promise read false (promises.json): said on the pass that first reads it so, to the maintainers and to `core`
54
+ 'promise': { forSec: 0 },
51
55
  'browser.tabs': { warn: null },
52
56
  'power.thermal': { forSec: 60 },
53
57
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-health",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
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",
@@ -11,13 +11,15 @@
11
11
  "./paths": "./lib/paths.mjs",
12
12
  "./drift": "./lib/drift.mjs",
13
13
  "./recorded": "./lib/recorded.mjs",
14
- "./markers": "./lib/markers.mjs"
14
+ "./markers": "./lib/markers.mjs",
15
+ "./promises": "./lib/promises.mjs"
15
16
  },
16
17
  "bin": {
17
18
  "supercode-health": "./bin/health.mjs"
18
19
  },
19
20
  "files": [
20
21
  "index.mjs",
22
+ "promises.json",
21
23
  "lib",
22
24
  "bin",
23
25
  "README.md"
package/promises.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "version": 1,
3
+ "about": "The core promises: what every session on a machine stands on. One list, read in three places: at the push in the clean room (checks/system), at a release's activation on each machine (sdk/teams/bin/workspace/activation-watch.mjs: one that was true before and is false after puts the release back), and on every pass of the machine-health probe (a false one is mailed to the config's `core` addresses on the pass that first reads it false). `probe` names the reading in lib/promises.mjs; `bound_ms` is the most that reading may take before it is unknown; `activation` says what a break at activation does.",
4
+ "promises": [
5
+ { "name": "server-sees-machine", "probe": "server", "bound_ms": 3000, "activation": "rollback",
6
+ "promise": "The Teams server lists this machine online: it heard its heartbeat in the last 45 seconds over a held link." },
7
+ { "name": "restart-admitted", "probe": "restart", "bound_ms": 3000, "activation": "rollback",
8
+ "promise": "A restart of this machine's daemon would not be refused for state already standing here (the runtime, the pane wrappers, the Maildir against its retirement receipt)." },
9
+ { "name": "mail-serves", "probe": "mail", "bound_ms": 1500, "activation": "report",
10
+ "promise": "The daemon answers on its socket, serves mail, and no mail expired undelivered in the last 15 minutes." },
11
+ { "name": "harness-one-install", "probe": "harness", "bound_ms": 4000, "activation": "report",
12
+ "promise": "Each harness CLI on this machine (codex, claude) is one version wherever a launch could find it (the login shell's PATH, then the daemon's), and Codex's background server runs that version." },
13
+ { "name": "no-harness-marks", "probe": "marks", "bound_ms": 2000, "activation": "report",
14
+ "promise": "No terminal window or tmux server carries a harness's child-session marks, so a session a person starts in one is its own session." },
15
+ { "name": "no-stale-release", "probe": "stale", "bound_ms": 3000, "activation": "report",
16
+ "promise": "No supercode process has run a release for more than two hours after an install replaced it." },
17
+ { "name": "board-round", "probe": "board", "bound_ms": 500, "activation": "report",
18
+ "promise": "Where a board's dispatcher runs, its last round ended in the last ten minutes with nothing failed." },
19
+ { "name": "disk-free", "probe": "disk", "bound_ms": 500, "activation": "report",
20
+ "promise": "The volumes supercode's home, the person's home and the temporary folder are on each have more than 5 GB free." },
21
+ { "name": "release-kept", "probe": "activation", "bound_ms": 200, "activation": "report",
22
+ "promise": "The last release activated on this machine was kept: it was not put back, and it broke no promise that held before it." },
23
+ { "name": "fleet-online", "probe": "fleet", "bound_ms": 3000, "activation": "report",
24
+ "promise": "Every other machine enrolled with this server and heard from in the last 24 hours is online now." }
25
+ ]
26
+ }