@volter/supercode-health 0.1.3 → 0.1.4

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/mail.mjs CHANGED
@@ -27,6 +27,19 @@ function line(t) {
27
27
  return `${LEVEL[t.to] ?? t.to}${t.from !== 'none' && t.to !== 'none' ? ` (was ${t.from})` : ''} ${t.key}: ${t.summary}${proc}`;
28
28
  }
29
29
 
30
+ /**
31
+ * One mail stating the whole current state: every alarm raised now, or that none is. Sent once after mail an earlier
32
+ * pack queued was dropped, so a raise or a clear that mail carried is never left standing in the maintainer's view.
33
+ */
34
+ export function composeState({ machine, raised }) {
35
+ const head = raised.length ? `${raised.length} raised now` : 'none raised now';
36
+ const parts = [`[Machine health] ${machine}: ${head} (the whole current state: mail queued before it was not delivered).`];
37
+ if (raised.length) parts.push('', ...raised.map((r) => `${LEVEL[r.level]} ${r.key} since ${r.since}: ${r.summary}`));
38
+ else parts.push('', 'Every alarm raised earlier has cleared.');
39
+ parts.push('', `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.');
40
+ return { subject: `machine health ${machine}: ${head}`, body: parts.join('\n') };
41
+ }
42
+
30
43
  /** One mail for a cycle's transitions and the reminders due; null when there is nothing to say. */
31
44
  export function compose({ machine, transitions, reminders, raised }) {
32
45
  if (!transitions.length && !reminders.length) return null;
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 } from './mail.mjs';
11
+ import { fileAlarm, compose, composeState } from './mail.mjs';
12
12
  import { run } from './run.mjs';
13
13
 
14
14
  export async function platformReader(platform = process.platform) {
@@ -93,8 +93,15 @@ export class Probe {
93
93
 
94
94
  /** Remove an earlier pack's outbox; answers how many mails it held, logged by recipient. */
95
95
  #dropOutbox() {
96
- const items = readJson(this.f.outbox, null);
97
- if (!Array.isArray(items)) return 0;
96
+ let items;
97
+ try { items = JSON.parse(readFileSync(this.f.outbox, 'utf8')); } catch (error) {
98
+ if (error.code === 'ENOENT') return 0;
99
+ // an outbox that cannot be read held mail all the same: it goes, and the current state is mailed
100
+ rmSync(this.f.outbox, { force: true });
101
+ this.log(`dropped an unreadable outbox.json an earlier pack left (${error.message}); the current state is mailed`);
102
+ return 1;
103
+ }
104
+ if (!Array.isArray(items)) { rmSync(this.f.outbox, { force: true }); this.log('dropped an outbox.json an earlier pack left that held no list; the current state is mailed'); return 1; }
98
105
  const counts = {};
99
106
  for (const item of items) counts[item?.to ?? '?'] = (counts[item?.to ?? '?'] ?? 0) + 1;
100
107
  rmSync(this.f.outbox, { force: true });
@@ -121,7 +128,9 @@ export class Probe {
121
128
  const repeatMs = this.config.repeatMin * 60_000;
122
129
  const reminders = transitions.length ? [] : alarms.filter((a) => now - (this.state[a.key].lastMailAt ?? 0) >= repeatMs);
123
130
  const to = subscribers(this.f, this.config);
124
- const mail = compose({ machine: this.machine, transitions, reminders, raised: alarms });
131
+ // after dropping an earlier pack's undelivered mail, the first mail states the whole current state, clears included
132
+ const mail = this.droppedOutbox && to.length ? composeState({ machine: this.machine, raised: alarms }) : compose({ machine: this.machine, transitions, reminders, raised: alarms });
133
+ if (this.droppedOutbox && to.length) this.droppedOutbox = 0;
125
134
  if (mail) {
126
135
  for (const a of alarms) this.state[a.key].lastMailAt = now;
127
136
  this.log(`${mail.subject}${to.length ? '' : ' (no maintainer subscribed)'}\n${mail.body}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-health",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
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",