instar 1.2.36 → 1.2.37

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-skew.d.ts","sourceRoot":"","sources":["../../src/core/version-skew.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAMH,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,cAAc,GAAG,YAAY,GAAG,gCAAgC,CAAC;IAC9E,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CASzG;AAED,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAC;IAClD,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,eAAe,GAAG,SAAS,GAAG,eAAe,GAAG,gBAAgB,CA6ChH;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,qBAAqB,GAAG,IAAI,CAkBlH;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAGjE;AAED,8DAA8D;AAC9D,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElE"}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Version-skew detection + lifeline-restart signal coordination.
3
+ *
4
+ * The signal file `state/lifeline-restart-requested.json` is written by THREE
5
+ * independent channels (no single-point-of-failure):
6
+ *
7
+ * 1. AutoUpdater.requestRestart — when an apply crosses major.minor.
8
+ * 2. Server /internal/telegram-forward 426 handler — direct evidence of skew.
9
+ * 3. PostUpdateMigrator — one-time bootstrap nudge for currently-stuck lifelines.
10
+ *
11
+ * The signal is consumed by THREE independent readers:
12
+ *
13
+ * 1. TelegramLifeline tick loop — calls initiateRestart('plannedUpgrade').
14
+ * 2. ServerSupervisor poll — force-restarts the lifeline if its tick hasn't
15
+ * acted within 60s of the signal write.
16
+ * 3. (Future) v3 Remediator probe — absorbs both channels.
17
+ *
18
+ * Spec: docs/specs/auto-updater-lifeline-coordination.md
19
+ */
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
22
+ import { SafeFsExecutor } from './SafeFsExecutor.js';
23
+ const SIGNAL_RELPATH = path.join('state', 'lifeline-restart-requested.json');
24
+ /**
25
+ * Predicate: does going from `prev` to `next` cross a major.minor boundary?
26
+ *
27
+ * Examples:
28
+ * crossesBreaking('1.1.0', '1.2.28') === true // minor crossed
29
+ * crossesBreaking('1.2.0', '1.2.28') === false // same major.minor
30
+ * crossesBreaking('1.2.28', '2.0.0') === true // major crossed
31
+ * crossesBreaking('1.2.28', '1.2.28') === false // identity
32
+ *
33
+ * Malformed inputs are treated as breaking (fail safe — prefer over-signal
34
+ * to under-signal, since the consequence of false-positive is a single
35
+ * harmless lifeline restart, and the consequence of false-negative is the
36
+ * exact incident class we're closing).
37
+ */
38
+ export function crossesBreaking(prev, next) {
39
+ if (!prev || !next)
40
+ return true;
41
+ const semverRe = /^(\d+)\.(\d+)\./;
42
+ const mp = semverRe.exec(prev);
43
+ const mn = semverRe.exec(next);
44
+ if (!mp || !mn)
45
+ return true;
46
+ const [, pMaj, pMin] = mp;
47
+ const [, nMaj, nMin] = mn;
48
+ return pMaj !== nMaj || pMin !== nMin;
49
+ }
50
+ /**
51
+ * Write the lifeline-restart signal atomically. Idempotent: if a non-expired
52
+ * signal already exists for the SAME targetVersion, no-ops.
53
+ *
54
+ * Returns 'written' / 'skipped-fresh' / 'replaced-stale'.
55
+ */
56
+ export function writeLifelineRestartSignal(opts) {
57
+ const now = opts.now ?? Date.now();
58
+ const ttlMs = opts.ttlMs ?? 60 * 60 * 1000;
59
+ const signalPath = path.join(opts.stateDir, SIGNAL_RELPATH);
60
+ // Idempotency: if an existing signal targets the same version and hasn't
61
+ // expired, don't overwrite. This prevents duplicate audit-log entries when
62
+ // multiple writers fire on the same boundary crossing.
63
+ let outcome = 'written';
64
+ try {
65
+ const existing = JSON.parse(fs.readFileSync(signalPath, 'utf-8'));
66
+ const existingExpires = Date.parse(existing.expiresAt);
67
+ if (existing.targetVersion === opts.targetVersion &&
68
+ Number.isFinite(existingExpires) &&
69
+ existingExpires > now) {
70
+ return 'skipped-fresh';
71
+ }
72
+ outcome = 'replaced-stale';
73
+ }
74
+ catch {
75
+ // No existing signal or unreadable — proceed to write a fresh one.
76
+ }
77
+ const signal = {
78
+ requestedAt: new Date(now).toISOString(),
79
+ requestedBy: opts.requestedBy,
80
+ reason: opts.reason,
81
+ previousVersion: opts.previousVersion,
82
+ targetVersion: opts.targetVersion,
83
+ expiresAt: new Date(now + ttlMs).toISOString(),
84
+ };
85
+ // Atomic write: tmp + rename to defend against partial reads under race.
86
+ const dir = path.dirname(signalPath);
87
+ fs.mkdirSync(dir, { recursive: true });
88
+ const tmpPath = `${signalPath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
89
+ try {
90
+ fs.writeFileSync(tmpPath, JSON.stringify(signal, null, 2));
91
+ fs.renameSync(tmpPath, signalPath);
92
+ }
93
+ catch (err) {
94
+ try {
95
+ SafeFsExecutor.safeUnlinkSync(tmpPath, { operation: 'src/core/version-skew.ts:writeLifelineRestartSignal-cleanup' });
96
+ }
97
+ catch { /* ignore */ }
98
+ throw err;
99
+ }
100
+ return outcome;
101
+ }
102
+ /**
103
+ * Read the signal. Returns null if missing, malformed, or expired.
104
+ * The reader is expected to delete the file as the first step of acting on it
105
+ * (so a respawn doesn't re-fire on the same signal).
106
+ */
107
+ export function readLifelineRestartSignal(stateDir, now = Date.now()) {
108
+ const signalPath = path.join(stateDir, SIGNAL_RELPATH);
109
+ let raw;
110
+ try {
111
+ raw = fs.readFileSync(signalPath, 'utf-8');
112
+ }
113
+ catch {
114
+ return null;
115
+ }
116
+ let parsed;
117
+ try {
118
+ parsed = JSON.parse(raw);
119
+ }
120
+ catch {
121
+ // Corrupt → treat as absent. The next write replaces it cleanly.
122
+ return null;
123
+ }
124
+ const expires = Date.parse(parsed.expiresAt);
125
+ if (!Number.isFinite(expires) || expires <= now)
126
+ return null;
127
+ return parsed;
128
+ }
129
+ /**
130
+ * Delete the signal. Called by readers as the first step of acting on the
131
+ * signal so a restart loop can't re-fire on the same write.
132
+ */
133
+ export function clearLifelineRestartSignal(stateDir) {
134
+ const signalPath = path.join(stateDir, SIGNAL_RELPATH);
135
+ try {
136
+ SafeFsExecutor.safeUnlinkSync(signalPath, { operation: 'src/core/version-skew.ts:clearLifelineRestartSignal' });
137
+ }
138
+ catch { /* ok — already gone */ }
139
+ }
140
+ /** Absolute path of the signal file for a given state dir. */
141
+ export function lifelineRestartSignalPath(stateDir) {
142
+ return path.join(stateDir, SIGNAL_RELPATH);
143
+ }
144
+ //# sourceMappingURL=version-skew.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-skew.js","sourceRoot":"","sources":["../../src/core/version-skew.ts"],"names":[],"mappings":"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"}
@@ -22,7 +22,7 @@ import { DegradationReporter } from '../monitoring/DegradationReporter.js';
22
22
  export type RestartState = 'idle' | 'quiescing' | 'persisting' | 'exiting';
23
23
  export interface RestartRequest {
24
24
  reason: string;
25
- bucket: 'watchdog' | 'versionSkew';
25
+ bucket: 'watchdog' | 'versionSkew' | 'plannedUpgrade';
26
26
  context?: Record<string, unknown>;
27
27
  }
28
28
  export interface OrchestratorDeps {
@@ -1 +1 @@
1
- {"version":3,"file":"RestartOrchestrator.d.ts","sourceRoot":"","sources":["../../src/lifeline/RestartOrchestrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAE3E,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,WAAW,GAAG,YAAY,GAAG,SAAS,CAAC;AAE3E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,UAAU,GAAG,aAAa,CAAC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,gBAAgB;IAC/B,qGAAqG;IACrG,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,4EAA4E;IAC5E,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,wEAAwE;IACxE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,qEAAqE;IACrE,YAAY,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,MAAM,OAAO,CAAC;IACxC,uDAAuD;IACvD,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,6DAA6D;IAC7D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+FAA+F;IAC/F,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,qBAAa,mBAAmB;IAOlB,OAAO,CAAC,QAAQ,CAAC,IAAI;IANjC,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsB;IAC/C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAC7B,cAAc,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,YAAY,CAAA;KAAE,GAAG,IAAI,CAAQ;gBAEvD,IAAI,EAAE,gBAAgB;IAMnD,IAAI,KAAK,IAAI,YAAY,CAExB;IAED;;;;OAIG;IACG,cAAc,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,GAAG,WAAW,CAAC;IAuF9E,4EAA4E;IAC5E,gBAAgB,IAAI,IAAI;CAIzB"}
1
+ {"version":3,"file":"RestartOrchestrator.d.ts","sourceRoot":"","sources":["../../src/lifeline/RestartOrchestrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAE3E,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,WAAW,GAAG,YAAY,GAAG,SAAS,CAAC;AAE3E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,UAAU,GAAG,aAAa,GAAG,gBAAgB,CAAC;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,gBAAgB;IAC/B,qGAAqG;IACrG,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,4EAA4E;IAC5E,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,wEAAwE;IACxE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,qEAAqE;IACrE,YAAY,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,MAAM,OAAO,CAAC;IACxC,uDAAuD;IACvD,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,6DAA6D;IAC7D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+FAA+F;IAC/F,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,qBAAa,mBAAmB;IAOlB,OAAO,CAAC,QAAQ,CAAC,IAAI;IANjC,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsB;IAC/C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAC7B,cAAc,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,YAAY,CAAA;KAAE,GAAG,IAAI,CAAQ;gBAEvD,IAAI,EAAE,gBAAgB;IAMnD,IAAI,KAAK,IAAI,YAAY,CAExB;IAED;;;;OAIG;IACG,cAAc,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,GAAG,WAAW,CAAC;IAuF9E,4EAA4E;IAC5E,gBAAgB,IAAI,IAAI;CAIzB"}
@@ -38,6 +38,7 @@ export declare class TelegramLifeline {
38
38
  private offsetPath;
39
39
  private stopHeartbeat;
40
40
  private replayInterval;
41
+ private restartSignalInterval;
41
42
  private lifelineTopicId;
42
43
  private lockPath;
43
44
  private consecutive409s;
@@ -136,6 +137,19 @@ export declare class TelegramLifeline {
136
137
  * match (loopback impostor), treat as transient.
137
138
  */
138
139
  private handleVersionSkew;
140
+ /**
141
+ * Per-tick check of the coordinated-restart signal file.
142
+ *
143
+ * Three independent writers (AutoUpdater on major.minor crossing; the
144
+ * server on a 426 forward; PostUpdateMigrator's one-time stale-lifeline
145
+ * bootstrap) drop a signal at `state/lifeline-restart-requested.json`.
146
+ * We read it, validate, delete it (so a respawn doesn't re-fire), and
147
+ * call initiateRestart on the `plannedUpgrade` bucket which bypasses
148
+ * the watchdog cooldown.
149
+ *
150
+ * Spec: docs/specs/auto-updater-lifeline-coordination.md
151
+ */
152
+ private checkLifelineRestartSignal;
139
153
  /** Watchdog-tracked counters/state. */
140
154
  private consecutiveForwardFailures;
141
155
  private lastForwardSuccessAt;
@@ -1 +1 @@
1
- {"version":3,"file":"TelegramLifeline.d.ts","sourceRoot":"","sources":["../../src/lifeline/TelegramLifeline.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TelegramLifeline.d.ts","sourceRoot":"","sources":["../../src/lifeline/TelegramLifeline.ts"],"names":[],"mappings":"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"}
@@ -36,6 +36,7 @@ import pc from 'picocolors';
36
36
  import { loadConfig, ensureStateDir, detectTmuxPath, getInstarVersion } from '../core/Config.js';
37
37
  import { registerAgent, unregisterAgent, startHeartbeat } from '../core/AgentRegistry.js';
38
38
  import { SafeFsExecutor } from '../core/SafeFsExecutor.js';
39
+ import { readLifelineRestartSignal, clearLifelineRestartSignal, } from '../core/version-skew.js';
39
40
  // setup.ts uses @inquirer/prompts which requires Node 20.12+
40
41
  // Dynamic import to avoid breaking the lifeline on older Node versions
41
42
  // import { installAutoStart } from '../commands/setup.js';
@@ -199,6 +200,7 @@ export class TelegramLifeline {
199
200
  offsetPath;
200
201
  stopHeartbeat = null;
201
202
  replayInterval = null;
203
+ restartSignalInterval = null;
202
204
  lifelineTopicId = null;
203
205
  lockPath;
204
206
  consecutive409s = 0;
@@ -360,6 +362,19 @@ export class TelegramLifeline {
360
362
  this.replayQueue();
361
363
  }
362
364
  }, 15_000);
365
+ // Coordinated restart channel: every 30s, check for a lifeline-restart
366
+ // signal written by the AutoUpdater (major.minor crossing), the server
367
+ // (426 evidence), or the PostUpdateMigrator (one-time stale-lifeline
368
+ // bootstrap). On match, restart via the planned-upgrade bucket which
369
+ // bypasses the watchdog cooldown.
370
+ // Spec: docs/specs/auto-updater-lifeline-coordination.md
371
+ this.restartSignalInterval = setInterval(() => {
372
+ this.checkLifelineRestartSignal();
373
+ }, 30_000);
374
+ // Run once immediately so a fresh lifeline picks up a signal written
375
+ // before it started (e.g. by PostUpdateMigrator during the previous
376
+ // launchctl cycle).
377
+ this.checkLifelineRestartSignal();
363
378
  // Stage B: install the restart orchestrator and health watchdog.
364
379
  // In unsupervised mode (no INSTAR_SUPERVISED=1 and no launchd parent),
365
380
  // the orchestrator emits signals and logs but skips process.exit.
@@ -429,6 +444,10 @@ export class TelegramLifeline {
429
444
  clearInterval(this.replayInterval);
430
445
  this.replayInterval = null;
431
446
  }
447
+ if (this.restartSignalInterval) {
448
+ clearInterval(this.restartSignalInterval);
449
+ this.restartSignalInterval = null;
450
+ }
432
451
  if (this.watchdog)
433
452
  this.watchdog.stop();
434
453
  try {
@@ -1161,6 +1180,47 @@ export class TelegramLifeline {
1161
1180
  lifelineVersion: this.lifelineVersion,
1162
1181
  });
1163
1182
  }
1183
+ /**
1184
+ * Per-tick check of the coordinated-restart signal file.
1185
+ *
1186
+ * Three independent writers (AutoUpdater on major.minor crossing; the
1187
+ * server on a 426 forward; PostUpdateMigrator's one-time stale-lifeline
1188
+ * bootstrap) drop a signal at `state/lifeline-restart-requested.json`.
1189
+ * We read it, validate, delete it (so a respawn doesn't re-fire), and
1190
+ * call initiateRestart on the `plannedUpgrade` bucket which bypasses
1191
+ * the watchdog cooldown.
1192
+ *
1193
+ * Spec: docs/specs/auto-updater-lifeline-coordination.md
1194
+ */
1195
+ checkLifelineRestartSignal() {
1196
+ let signal;
1197
+ try {
1198
+ signal = readLifelineRestartSignal(this.projectConfig.stateDir);
1199
+ }
1200
+ catch (err) {
1201
+ console.warn(`[Lifeline] failed to read restart signal: ${err}`);
1202
+ return;
1203
+ }
1204
+ if (!signal)
1205
+ return;
1206
+ // Same-version no-op: a respawned lifeline that already matches the
1207
+ // target version should clear the stale signal and not loop.
1208
+ if (signal.targetVersion === this.lifelineVersion) {
1209
+ clearLifelineRestartSignal(this.projectConfig.stateDir);
1210
+ return;
1211
+ }
1212
+ // First step: delete the file. If initiateRestart goes through, the
1213
+ // process exits and a fresh lifeline starts on the new version — it
1214
+ // must not re-read the same signal.
1215
+ clearLifelineRestartSignal(this.projectConfig.stateDir);
1216
+ console.log(`[Lifeline] coordinated restart signal received from ${signal.requestedBy}: ` +
1217
+ `v${signal.previousVersion} → v${signal.targetVersion} (${signal.reason})`);
1218
+ this.initiateRestart('plannedUpgrade', signal.reason, {
1219
+ requestedBy: signal.requestedBy,
1220
+ previousVersion: signal.previousVersion,
1221
+ targetVersion: signal.targetVersion,
1222
+ });
1223
+ }
1164
1224
  /** Watchdog-tracked counters/state. */
1165
1225
  consecutiveForwardFailures = 0;
1166
1226
  lastForwardSuccessAt = 0;